简体   繁体   中英

Angular 6 lifecycle hooks not running on dynamically created components

I have been researching this issue as well as experimenting, and I have not been able to come up with a conclusive answer on how to resolve this, or if it is the expected behaviour.

Some time ago I followed the Angular guide on creating dynamic components using the componentFactoryResolver ( https://v6.angular.io/guide/dynamic-component-loader ).

The problem I am experiencing is that lifecycle hooks (ngAfterViewInit, ngOnInit, etc.) in the dynamically created component do not run until the mouse is moved over the component in question. This seems like odd behaviour.

This does not appear to be related to change detection as running change detection manually ( this.cdRef.detectChanges() ) does not fire the hooks.

I have not been able to create a minimum repro as for some reason the example in the Angular docs is not working, so please see some snippets of my implementation below. Everything works fine, except the lifecycle hooks.

Usage in HTML:

<div class="modal-body modal-dataFlow animated fadeIn" [hidden]="showNotification">
  <ng-template appForms></ng-template>
</div>

forms.directive.ts:

import { Directive, ViewContainerRef} from '@angular/core';

@Directive({
  selector: '[appForms]'
})
export class FormsDirective {

  constructor(public viewContainerRef: ViewContainerRef) { }

}

form-item.ts:

import { Type } from '@angular/core';

export class FormItem {
  constructor(public component: Type<any>, public data: any) { };
}

forms.service.ts:

import { Injectable } from '@angular/core';

import { HttpcallerComponent } from '../views/dataflows/forms/httpcaller/httpcaller.component';
import { FormItem } from '../views/dataflows/form-item';
import { Constants } from '../views/dataflows/Constants';

import { Subject } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';

@Injectable()
export class FormsService {
  myBool$: Observable<boolean>;
  private boolSubject: Subject<boolean>;

  constructor() {
    this.boolSubject = new Subject<boolean>();
    this.myBool$ = this.boolSubject.asObservable();
  }

  emitValue(param: boolean) {
    this.boolSubject.next(param);
  }

  getForms() {
    return [

      new FormItem(HttpcallerComponent, { name: Constants.HTTP_CALLER_ENDPOINT_NAME, stepName: Constants.HTTP_CALLER_STEP_TYPE_NAME })

    ];
  }
}

Method responsible for loading component (I'm passing an instance of FormItem into this):

completeLoad(dataStepInfo, formItem) {

    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(formItem.component);

    this.viewContainerRef.clear();

    let componentRef = this.viewContainerRef.createComponent(componentFactory);

    formItem.data.dataStepInfo = dataStepInfo;
    formItem.data.flowId = this.flowID;

    (<IForms>componentRef.instance).data = formItem.data;
    (<IForms>componentRef.instance).parentForm = this.propertyForm;

    this.childComponent = componentRef.instance;

    this.componentCreated.next(this.maximiseRegex.test("app." + this.selectedEndpointType + "Step"));

    if (dataStepInfo.endpointName === Constants.MERGE_STEP_ENDPOINT_NAME) {
      this.dataFlowModal.show();
    }

  }

Example of lifecycle hooks in httpcaller.component.ts:

import { Component, OnInit, Input, OnDestroy, ChangeDetectorRef, ViewEncapsulation, AfterViewInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormGroupDirective } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';

import { ModalService } from '../../../../services/modal.service';

import { HighlightTag } from 'angular-text-input-highlight';

import { Result } from '../../../../common/result';


@Component({
  selector: 'app-httpcaller',
  templateUrl: './httpcaller.component.html',
  styleUrls: ['./httpcaller.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class HttpcallerComponent implements OnInit, OnDestroy, AfterViewInit {
  private confirmed: boolean = false;
  private errorText: string;
  private sub: Subscription;
  private transmission;
  public showConfirm: boolean = false;
  public alertsDismiss: any = [];
  public localHeaders;
  private previousContentType;
  private formSub: Subscription;
  private receivedAuth: number;
  private isViewInitialized: boolean = false;
  private urlSubscription: Subscription;
  @Input() parentForm: FormGroupDirective;
  @Input() data: any;

  tags: HighlightTag[] = [];

  tagClicked: HighlightTag;

  httpConfigForm: FormGroup;

  private httpConfigSettings = {
    httpConfig: null, httpHeaders: null, httpAuthorization: null
  }

  constructor(
    private formBuilder: FormBuilder,
    private modalService: ModalService,
    private cdRef: ChangeDetectorRef) {
  }

  public configSelection: string;
  public authList: any;
  public disableButtons: boolean = false;
  private confirmType: boolean = false;
  private readonly onPremAgentKey: string = '{{OnPremAgent}}';

  ngOnInit() {

    if (this.isViewInitialized) {
      return;
    }

    this.isViewInitialized = true;

    this.sub = this.modalService.myBool$.subscribe((newBool: boolean) => {
      this.confirmed = newBool;
      if (this.confirmed && !this.confirmType) {
        this.runDeleteAuth();
      }
      else {
        this.checkContentType(this.confirmed);
        this.confirmed = false;
      }

      this.confirmType = false;

    });

    //this.transmissionId
    this.getContentTypes();
    this.GetCommonFormatTypes();

    this.cdRef.detectChanges();

    this.addTags();

  }

  ngAfterViewInit() {
    console.log("AfterViewInit running");
  }

Do this to ignite factory view:

constructor(private appRef: ApplicationRef) {}

And below componentRef:

this.appRef.attachView(componentRef.hostView);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM