Инициализация компонента с помощью ngIf не запускает ngOnInit

У меня есть родительский компонент с некоторыми дочерними элементами, которые отображаются на основе оценки ngIf.

Я использую Angular 8.1.3

Родительский компонент

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;
  }

}

и его шаблон

<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>

Это второй дочерний компонент (тот, который неправильно инициализирован)

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"
        } );

    }

}

И уменьшенный шаблон для тестирования

<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>

Родитель получает значение _stage с сервера, и первый дочерний элемент отображается правильно.
Затем этот компонент передает новое значение _stage своему родителю с помощью EventEmitter, и родитель соответственно обновляет значение своей переменной.
Новое значение (2) - это то, которое фактически превращает мой второй ngIf в true, и действительно вызывается второй дочерний конструктор (он называется третьей стадией, не путайте). Но его ngOnInit не запускается.
Значение по умолчанию «TEST» не отображается в моем поле ввода, и, проверяя дерево компонентов через Augury, я вижу, что две зависимости, введенные конструктором, присутствуют, в то время как formgroup, созданный внутри ngOnInit, не является.
Если я создаю новый маршрут, указывающий прямо на мой дочерний компонент, все будет работать, как ожидалось.


person 4javier    schedule 30.09.2019    source источник
comment
Stackblitz пожалуйста   -  person Phat Huynh    schedule 30.09.2019


Ответы (2)


Хавьер, когда ты пишешь

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

Вам нужно использовать 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))
  );
person Eliseo    schedule 30.09.2019
comment
Извините, но этот ответ совершенно не связан с вопросом: у меня не возникло проблем с получением идентификатора из пути, действительно, мой родительский компонент работает, и даже его первый дочерний элемент отображается правильно. Это тоже некорректно: ваш - просто сложный способ сделать то же самое: в моем коде this.id получает значение от анонимной функции. - person 4javier; 30.09.2019
comment
@ 4javier, в вашем коде, когда вы вызываете this.prodService.get, this.id не имеет значения, извините, я не читаю ваш вопрос, но ngOnInit вызывается, если вы имеет * ngIf, поэтому я полагаю, что это была ошибка - person Eliseo; 30.09.2019
comment
Я могу заверить, что это не так: как я уже сказал, переменная id правильно установлена ​​в значение params.id анонимной функцией. Я просто проверил, чтобы убедиться. - person 4javier; 30.09.2019
comment
Это заняло некоторое время (задумано хирони), но теперь я знаю, что вы были правы. Спасибо. - person 4javier; 29.10.2020

Вы используете асинхронные наблюдаемые объекты. Все, что находится внутри subscribe(), вызывается по завершении запроса, что может произойти в любое время.

Итак, ваш this.id устанавливается после того, как вы уже вызвали подписку на второй наблюдаемый объект. Вам нужно использовать некоторую карту, которая обеспечивает порядок подписок, например concatMap()

 ngOnInit() {
  this.route.params.pipe(
    concatMap(params => this.prodService.get(params.id))
  )
}
person brightpants    schedule 29.10.2020
comment
Я снова поднял этот забытый вопрос через год после вашего ответного уведомления, с менее нубеским знанием этого вопроса, и теперь я понял, что ответ Элисео тоже был правильным. Спасибо за вашу помощь. Я отмечу его вопрос как принятый только потому, что он ответил первым. - person 4javier; 29.10.2020
comment
Не стоит беспокоиться! Я искал что-то еще и остановился на этом вопросе, поэтому решил добавить ответ с небольшим объяснением, почему это не работает: D - person brightpants; 29.10.2020