*ngIf中的Angular 2 @ViewChild

sin*_*sem 172 angular2-changedetection angular

显示模板中相应元素后获取@ViewChild最优雅的方法是什么?以下是一个例子.也有Plunker可用.

模板:

<div id="layout" *ngIf="display">
    <div #contentPlaceholder></div>
</div>
Run Code Online (Sandbox Code Playgroud)

零件:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我有一个默认隐藏其内容的组件.当有人调用@ViewChild方法时,它变得可见.但是,在角度2变化检测完成之前,我无法参考show().我通常将所有必需的操作包装成viewContainerRef如上所示.有更正确的方法吗?

我知道有一个选项setTimeout(()=>{},1),但它会导致太多无用的通话.

答案(Plunker)

par*_*ent 279

使用QueryList接受的答案对我不起作用.然而,有什么工作是使用ViewChild的setter:

 private contentPlaceholder: ElementRef;

 @ViewChild('contentPlaceholder') set content(content: ElementRef) {
    this.contentPlaceholder = content;
 }
Run Code Online (Sandbox Code Playgroud)

一旦*ngIf变为真,就会调用setter.

  • 请注意,此setter最初是使用未定义的内容调用的,因此如果在setter中执行某些操作,请检查null (24认同)
  • 你怎么称呼二传手? (5认同)
  • setter 名称 `content` 很重要,或者我们可以重命名它吗?如果你有很多这样的元素怎么办?您将看到“重复的标识符‘内容’” (3认同)
  • 对于现在遇到此问题的任何人来说,这只是一个有用的说明 - 您需要 @ViewChild('myComponent', {static: false}) ,其中关键位是 static: false,这允许它采用不同的输入。 (3认同)
  • @LeandroCusack 当 Angular 找到 ```&lt;div #contentPlaceholder&gt;&lt;/div&gt;``` 时会自动调用它。从技术上讲,您可以像任何其他 setter ```this.content = someElementRef``` 一样手动调用它,但我不明白您为什么要这样做。 (2认同)
  • 除了 static: false 之外,我还必须添加 {read: ElementRef} 到我的 @ViewChild 中,以便它适用于自定义组件。请参阅/sf/ask/3214527361/ (2认同)

Jef*_*ima 94

克服这个问题的另一种方法是手动运行变化检测器.

你先注入ChangeDetectorRef:

constructor(private changeDetector : ChangeDetectorRef) {}
Run Code Online (Sandbox Code Playgroud)

然后在更新控制*ngIf的变量后调用它

show() {
        this.display = true;
        this.changeDetector.detectChanges();
    }
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!我正在使用已接受的答案,但它仍然导致错误,因为当我在“onInit()”之后的某个时间尝试使用子函数时,子函数仍然未定义,因此我在调用任何子函数之前添加了“detectChanges”并修复了它。(我同时使用了已接受的答案和此答案) (2认同)

小智 21

上面的答案对我不起作用,因为在我的项目中,ngIf在输入元素上.我需要访问nativeElement属性,以便在ngIf为true时关注输入.ViewContainerRef上似乎没有nativeElement属性.这是我做的(遵循@ViewChild文档):

<button (click)='showAsset()'>Add Asset</button>
<div *ngIf='showAssetInput'>
    <input #assetInput />
</div>

...

private assetInputElRef:ElementRef;
@ViewChild('assetInput') set assetInput(elRef: ElementRef) {
    this.assetInputElRef = elRef;
}

...

showAsset() {
    this.showAssetInput = true;
    setTimeout(() => { this.assetInputElRef.nativeElement.focus(); });
}
Run Code Online (Sandbox Code Playgroud)

我在聚焦之前使用了setTimeout,因为ViewChild需要一秒钟来分配.否则它将是未定义的.

  • setTimeout()为0对我有效.我的ngIf隐藏的元素在setTimeout之后被正确绑定,而不需要在中间设置assetInput()函数. (2认同)

Svi*_*siv 19

角度8

您应该添加{ static: false }第二个选项@ViewChild

例:

export class AppComponent {

    @ViewChild('contentPlaceholder', { static: false }) contentPlaceholder: ElementRef;

    display = false;

    constructor(private changeDetectorRef: ChangeDetectorRef) {

    }

    show() {
        this.display = true;
        this.changeDetectorRef.detectChanges();
        console.log(this.contentPlaceholder);
    }
}
Run Code Online (Sandbox Code Playgroud)

Stackblitz示例:https://stackblitz.com/edit/angular-d8ezsn

  • 谢谢斯维亚托斯拉夫。尝试了上述所有内容,但只有您的解决方案有效。 (3认同)
  • 可能是 Angular 8+ 的最佳解决方案,但确实需要 `this.changeDetectorRef.detectChanges();` (3认同)
  • 这应该是最新版本的公认答案。 (2认同)
  • 答案的文本遗漏了这样一个事实,即您必须调用“detectChanges”,这似乎不是您应该做的事情,我宁愿有一个设置器,而不必在我的组件中注入额外的东西。更不用说上面的两条评论说它不起作用......所以我不同意这应该是公认的答案,它是一种替代方案。 (2认同)
  • “从版本 9 开始,静态标志将默认为 false。” 来自 https://angular.io/guide/static-query-migration (2认同)

use*_*728 11

正如其他人所提到的,最快和最快的解决方案是使用[hidden]而不是*ngIf,这样组件将被创建但不可见,因为你可以访问它,尽管它可能不是最有效的办法.

  • 您必须注意,如果元素不是“display: block”,则使用“[hidden]”可能不起作用。更好地使用 [style.display]="condition ? '' : 'none'" (3认同)

Gün*_*uer 9

这可行,但我不知道你的情况是否方便:

@ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs: QueryList;

ngAfterViewInit() {
 this.viewContainerRefs.changes.subscribe(item => {
   if(this.viewContainerRefs.toArray().length) {
     // shown
   }
 })
}
Run Code Online (Sandbox Code Playgroud)


Or *_*cov 9

另一个快速的“技巧”(简单的解决方案)只是使用[hidden]标记而不是* ngIf,重要的是要知道在这种情况下Angular构建对象并将其绘制在class下:hidden,这就是为什么ViewChild可以正常工作的原因。 因此,请务必记住,不要在可能导致性能问题的笨重或昂贵物品上使用隐藏物品

  <div class="addTable" [hidden]="CONDITION">
Run Code Online (Sandbox Code Playgroud)


Fil*_*ncu 6

我的目标是避免任何假设某些东西的 hacky 方法(例如 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;
    });
  }
Run Code Online (Sandbox Code Playgroud)

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

它是如何工作的: combineLatest仅当每个提供的 Observable 发出自combineLatest订阅时刻以来的第一个值时,才第一次发出一个值。tabSetInitialized当设置@ViewChild元素时,我的主题会发出一个值。因此,我延迟了代码的执行,subscribe直到*ngIf变为正值并@ViewChild初始化。

当然不要忘记在 ngOnDestroy 上取消订阅,我是使用ngUnsubscribeSubject来做的:

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }
Run Code Online (Sandbox Code Playgroud)