0

I have a parent component with some childs that get rendered based on ngIf evaluation.

I'm on Angular 8.1.3

The parent component

import { ActivatedRoute } from '@angular/router';
import {FirstStageViewComponent} from "src/app/first-stage-view/first-stage-view.component";
import {SecondStageViewComponent} from "src/app/second-stage-view/second-stage-view.component";
import {SecondStageFormComponent} from "src/app/second-stage-form/second-stage-form.component";
import {ThirdStageFormComponent} from "src/app/third-stage-form/third-stage-form.component";
import {ThirdStageViewComponent} from "src/app/third-stage-view/third-stage-view.component";
import { ProdService } from "src/app/service/prod-service";


@Component({
  selector: 'app-prod-view',
  templateUrl: './prod-view.component.html',
  styleUrls: ['./prod-view.component.css']
})
export class ProdViewComponent implements OnInit 
{

  id;
  _stage: string;
  constructor(private prodService: ProdService, private route: ActivatedRoute) { }

  ngOnInit() {

    this.route.params.subscribe(params => this.id=params.id);

    this.prodService.get(this.id).subscribe((data: string) => (this._stage = data["_stage"]),
                                           error => (console.log(error)));

  }

  talkBack(_updatedStage: string) {

    this._stage=_updatedStage;
  }

}

and its template

<div class="container">


    <div *ngIf="_stage>0">
        <app-first-stage-view [id]=id></app-first-stage-view>
    </div>

    <div *ngIf="_stage==1 else stage2">
        <app-second-stage-form [id]=id (talk)=talkBack($event)></app-second-stage-form>
    </div>
    <ng-template #stage2>
    <div *ngIf="_stage>1">
        <app-second-stage-view [id]=id></app-second-stage-view>
    </div>
    </ng-template>


    <div *ngIf="_stage==2 else stage3">
        <app-third-stage-form [id]=id (talk)=talkBack($event)></app-third-stage-form>
    </div>
    <ng-template #stage3>
    <div *ngIf="_stage>2">
        <app-third-stage-view [id]=id></app-third-stage-view>
    </div>
    </ng-template>
</div>

This is the second child component (the one that doesn't get correctly initialized)

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, FormBuilder, FormArray, Validators } from "@angular/forms";
import { ThirdStageService } from "src/app/service/third-stage.service";

@Component( {
    selector: 'app-third-stage-form',
    templateUrl: './third-stage-form.component.html',
    styleUrls: ['./third-stage-form.component.css']
} )
export class ThirdStageFormComponent implements OnInit {

    tsForm: FormGroup;
    @Input() id: string;
    @Output() talk: EventEmitter<string> = new EventEmitter<string>();

    constructor( private tsService: ThirdStageService,
        private formBuilder: FormBuilder ) {}

    ngOnInit() {
        this.tsForm = this.formBuilder.group( {
            ingredient: "TEST"
        } );

    }

}

And a shrunk template for testing

<div class="container">
    <form [formGroup]="tsForm" (ngSubmit)="onSubmit()">
        <div class="form-group">
            <label for="name">{{ tsForm.controls[0].value }}</label> <input type="text"
                class="form-control" id="ingredient" formControlName="ingredient"
                required>
        </div>
    </form>
</div>

The parent retrieve the _stage value from server, and the first child gets correctly displayed.
Then this component talkback the new _stage value to its parent by an EventEmitter, and the parent update the value of its variable accordingly.
The new value (2) is the one that actually turns my second ngIf to true, and indeed second child (it's called third-stage-form, don't get confused) constructor gets called. But its ngOnInit doesn't fire up.
The default value "TEST" doesn't get shown inside my input field, and inspecting component tree through Augury, I can see that the two dependencies injected by constructor are present, while the formgroup created inside ngOnInit, is not.
If I create a new route pointing directly to my child component, everything works as expected.

4javier
  • 481
  • 2
  • 7
  • 22

2 Answers2

1

You're using observables, which are asynchronous. Whatever's inside subscribe() gets called when the request completes, which can happen at any time.

So, your this.id is being set after you've already called subscribe on the second observable. You need to use some map that ensures order of the subscribes, like concatMap()

 ngOnInit() {
  this.route.params.pipe(
    concatMap(params => this.prodService.get(params.id))
  )
}
brightpants
  • 415
  • 1
  • 4
  • 28
  • 1
    I came up again on this forgotten question after a year on your reply notification, with a less noobish knowledge of the matter, and now I realized that Eliseo answer was correct too. Thanks for your help. I'll mark his question as accepted just because he replied first. – 4javier Oct 29 '20 at 13:59
  • 1
    No worries! I was searching for something else and ended up in this question, so I decided to add an answer with a bit of an explanation on why it doesn't work :D – brightpants Oct 29 '20 at 14:03
0

Javier, when you write

this.route.params.subscribe(params => this.id=params.id);
//here this.id has no value
this.prodService.get(this.id).subscribe(...)

You need use swithMap

this.route.params.pipe(
  switchMap((params)=>{ 
    //here you has "params" 
    this.id=params.id
    //we want return 
    return this.prodService.get(this.id)
  })
  ).subscribe((data: string) => (this._stage = data["_stage"]),
              error => (console.log(error))
  );
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Sorry but this answer is totally unrelated to the question: I got no problem retrieving id from path, indeed my parent component works, and even its first child get correctly rendered. It's uncorrect too: your one is just a complex way of doing the same thing: in my code, `this.id` gets the value from the anonymous function. – 4javier Sep 30 '19 at 09:06
  • @4javier, in your code, when you make the call to this.prodService.get, this.id has **no value**, sorry, I don't read your question, but the ngOnInit is called if you has a *ngIf, so I supouse this was the error – Eliseo Sep 30 '19 at 09:10
  • I can assure that's not the case: as I already said, the id variable is correctly set to params.id value by anonymous function. I just checked to be sure. – 4javier Sep 30 '19 at 09:20
  • It took some time (hirony intended), but now I know that you were right. Thanks. – 4javier Oct 29 '20 at 14:00