未在ng-content内触发ngAfterViewInit

时间:2019-11-14 22:07:47

标签: javascript angular typescript

对于使用ngAfterViewInit转换为另一个组件的组件,不会调用<ng-content>生命周期钩子:

<app-container [showContent]="showContentContainer">
    <app-input></app-input>
</app-container>

但是,如果没有<ng-content>,它就可以正常工作:

<app-input *ngIf="showContent"></app-input>

容器组件定义为:

@Component({
  selector: 'app-container',
  template: `
        <ng-container *ngIf="showContent">
              <ng-content></ng-content>
        </ng-container>
  `
})
export class AppContainerComponent {
  @Input()
  showContentContainer = false;

  @Input()
  showContent = false;
}

输入组件定义为:

@Component({
  selector: 'app-input',
  template: `<input type=text #inputElem />`
})
export class AppInputComponent implements AfterViewInit {
  @ViewChild("inputElem")
  inputElem: ElementRef<HTMLInputElement>;

  ngAfterViewInit() {
    console.info("ngAfterViewInit fired!");
    this.inputElem.nativeElement.focus();
  }
}

在此处查看实时示例:https://stackblitz.com/edit/angular-playground-vqhjuh

2 个答案:

答案 0 :(得分:1)

如果检查堆栈闪电的控制台,则会看到在按下任何按钮之前触发了该事件。 我只能想到投影的所有内容都会在您声明的地方进行初始化/构造。

所以在您的示例中,就在这两行之间

<app-container [showContent]="showContentContainer">
      {{test()}}
      <app-input></app-input>
</app-container>

如果在应用程序容器中添加测试功能,它将立即被调用。因此<app-input>也将立即构建。由于ngAfterVieWInit只会被调用一次(https://angular.io/guide/lifecycle-hooks),因此已经被调用了。

AppInputComponent内添加以下内容有点奇怪

ngOnDestroy() {
    console.log('destroy')
}

该组件实际上将立即销毁,并且不再进行初始化(添加构造函数或onInit日志进行检查)。

答案 1 :(得分:1)

这里有两个问题:

  1. 子组件与父组件一起实例化,而不是在实例化<ng-content>以包括它们的情况下。 (请参阅https://github.com/angular/angular/issues/13921
  2. ngAfterViewInit不会 not 表示该组件已附加到DOM,只是视图已实例化。 (请参阅https://github.com/angular/angular/issues/13925

在这种情况下,可以通过解决其中一个问题来解决该问题:

  1. 可以将容器指令重写为仅在适当时实例化内容的结构指令。在此处查看示例:https://stackblitz.com/edit/angular-playground-mrcokp
  2. 可以重新编写输入指令,以对实际附加到DOM做出反应。一种实现方法是编写一条指令来处理。在此处查看示例:https://stackblitz.com/edit/angular-playground-sthnbr

在很多情况下,两者都适合。

但是,使用自定义指令很容易处理选项#2,为完整起见,我将在此处包括它:

@Directive({
    selector: "[attachedToDom],[detachedFromDom]"
})
export class AppDomAttachedDirective implements AfterViewChecked, OnDestroy {
  @Output()
  attachedToDom = new EventEmitter();

  @Output()
  detachedFromDom = new EventEmitter();

  constructor(
    private elemRef: ElementRef<HTMLElement>
  ) { }

  private wasAttached = false;

  private update() {
    const isAttached = document.contains(this.elemRef.nativeElement);

    if (this.wasAttached !== isAttached) {
      this.wasAttached = isAttached;

      if (isAttached) {
        this.attachedToDom.emit();
      } else {
        this.detachedFromDom.emit();
      }
    }
  }

    ngAfterViewChecked() { this.update(); }
  ngOnDestroy() { this.update(); }
}

可以这样使用:

<input type=text 
       (attachedToDom)="inputElem.focus()"
       #inputElem />