问题

在模板中显示相应的元素后获得@ViewChild的最优雅的方式是什么?

下面是一个例子。也可提供活塞。

Component.template.html:

<div id="layout" *ngIf="display">
  <div #contentPlaceholder></div>
</div>

Component.component.ts:

export class AppComponent {

    display = false;
    @ViewChild('contentPlaceholder', { read: ViewContainerRef }) viewContainerRef;

    show() {
        this.display = true;
        console.log(this.viewContainerRef); // undefined
        setTimeout(() => {
            console.log(this.viewContainerRef); // OK
        }, 1);
    }
}

我有一个默认隐藏其内容的组件。当有人调用show()方法时,它变得可见。然而,在Angular 2变更检测完成之前,我不能引用viewContainerRef。如上所示,我通常将所有必需的操作包装到setTimeout(()=>{},1)中。有没有更正确的方法?

我知道ngAfterViewChecked有一个选项,但它会导致太多无用的调用。

答案(设置)


当前回答

另一个快速的“技巧”(简单的解决方案)是使用[hidden]标签而不是*ngIf,重要的是要知道,在这种情况下,Angular会构建对象并在类下绘制它:隐藏,这就是ViewChild工作没有问题的原因。 所以重要的是要记住,你不应该在沉重或昂贵的物品上使用隐藏,这会导致性能问题

  <div class="addTable" [hidden]="CONDITION">

其他回答

我的目标是避免任何假定的方法(例如setTimeout),我最终实现了接受的解决方案,上面有一点RxJS的味道:

  private ngUnsubscribe = new Subject();
  private tabSetInitialized = new Subject();
  public tabSet: TabsetComponent;
  @ViewChild('tabSet') set setTabSet(tabset: TabsetComponent) {
    if (!!tabSet) {
      this.tabSet = tabSet;
      this.tabSetInitialized.next();
    }
  }

  ngOnInit() {
    combineLatest(
      this.route.queryParams,
      this.tabSetInitialized
    ).pipe(
      takeUntil(this.ngUnsubscribe)
    ).subscribe(([queryParams, isTabSetInitialized]) => {
      let tab = [undefined, 'translate', 'versions'].indexOf(queryParams['view']);
      this.tabSet.tabs[tab > -1 ? tab : 0].active = true;
    });
  }

我的场景:我想根据路由器queryParams在@ViewChild元素上触发一个动作。由于在HTTP请求返回数据之前包装*ngIf为false, @ViewChild元素的初始化发生延迟。

它是如何工作的:只有当每个提供的可观察对象在订阅了combineLatest后发出第一个值时,combineLatest才第一次发出一个值。当@ViewChild元素被设置时,我的Subject tabSetInitialized会发出一个值。因此,我延迟了订阅下代码的执行,直到*ngIf变为正数,@ViewChild被初始化。

当然,不要忘记取消订阅ngOnDestroy,我使用ngUnsubscribe主题:

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

如果我在Angular 9中使用ChangeDetectorRef,它就可以工作

@ViewChild('search', {static: false})
public searchElementRef: ElementRef;

constructor(private changeDetector: ChangeDetectorRef) {}

//then call this when this.display = true;
show() {
   this.display = true;
   this.changeDetector.detectChanges();
}

我自己在使用Angular 10时也遇到了同样的问题。

如果我尝试使用[hidden]或*ngIf,那么@ViewChild变量总是未定义的。

<p-calendar #calendar *ngIf="bShowCalendar" >
</p-calendar>

我没有把它从网页上删除。 我使用了一个[ngClass]使控件的透明度为:0,并将它完全移开。

<style>
  .notVisible {
    opacity: 0;
    left: -1000px;
    position: absolute !important;
  }
</style>

<p-calendar #calendar [ngClass]="{'notVisible': bShowCalendar }" >
</p-calendar>

我知道,这很蠢很丑,但它解决了问题。

我还必须让控制变成静态的。我不明白为什么…但是,再一次,它拒绝在没有改变的情况下工作:

export class DatePickerCellRenderer {
    @ViewChild('calendar', {static: true }) calendar: Calendar;

如果setter看起来不工作(根本没有被调用)@ViewChild尝试@ContentChild代替。

对于Angular 8——混合了null检查和@ViewChild static:错误的黑客

用于等待异步数据的分页控件

@ViewChild(MatPaginator, { static: false }) set paginator(paginator: MatPaginator) {
  if(!paginator) return;
  paginator.page.pipe(untilDestroyed(this)).subscribe(pageEvent => {
    const updated: TSearchRequest = {
      pageRef: pageEvent.pageIndex,
      pageSize: pageEvent.pageSize
    } as any;
    this.dataGridStateService.alterSearchRequest(updated);
  });
}