首页 文章

* ngIf中的Angular 2 @ViewChild

提问于
浏览
102

问题

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

模板:

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

零件:

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() 方法时,它变得可见 . 但是,在角度2变化检测完成之前,我无法参考 viewContainerRef . 我通常将所有必需的操作包装到 setTimeout(()=>{},1) 中,如上所示 . 有更正确的方法吗?

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

答案(Plunker)

8 回答

  • 175

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

    private contentPlaceholder: ElementRef;
    
     @ViewChild('contentPlaceholder') set content(content: ElementRef) {
        this.contentPlaceholder = content;
     }
    

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

  • 5

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

    你首先注入 ChangeDetectorRef

    constructor(private changeDetector : ChangeDetectorRef) {}
    

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

    show() {
            this.display = true;
            this.changeDetector.detectChanges();
        }
    
  • 54

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

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

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

  • -3

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

  • 8

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

    @ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs: QueryList;
    
    ngAfterViewInit() {
     this.viewContainerRefs.changes.subscribe(item => {
       if(this.viewContainerRefs.toArray().length) {
         // shown
       }
     })
    }
    
  • 3

    我的目标是避免任何假设某些东西的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;
        });
      }
    

    My scenario: 我想根据路由器 queryParams@ViewChild 元素上触发一个动作 . 由于在HTTP请求返回数据之前包装 *ngIf 为假,因此 @ViewChild 元素的初始化会发生延迟 .

    How does it work: combineLatest 仅在每个提供的Observables从 combineLatest 订阅的那一刻起发出第一个值时才首次发出一个值 . 我的主题 tabSetInitialized 在设置 @ViewChild 元素时发出一个值 . 因此,我延迟了 subscribe 下的代码执行,直到 *ngIf 变为正, @ViewChild 被初始化 .

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

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

    一个简化版本,我在使用Google Maps JS SDK时遇到了类似的问题 .

    我的解决方案是将 divViewChild 提取到它自己的子组件中,当在父组件中使用时,可以使用 *ngIf 隐藏/显示它 .

    Before

    HomePageComponent 模板

    <div *ngIf="showMap">
      <div #map id="map" class="map-container"></div>
    </div>
    

    HomePageComponent 组件

    @ViewChild('map') public mapElement: ElementRef; 
    
    public ionViewDidLoad() {
        this.loadMap();
    });
    
    private loadMap() {
    
      const latLng = new google.maps.LatLng(-1234, 4567);
      const mapOptions = {
        center: latLng,
        zoom: 15,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
      };
       this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
    }
    
    public toggleMap() {
      this.showMap = !this.showMap;
     }
    

    After

    MapComponent 模板

    <div>
      <div #map id="map" class="map-container"></div>
    </div>
    

    MapComponent 组件

    @ViewChild('map') public mapElement: ElementRef; 
    
    public ngOnInit() {
        this.loadMap();
    });
    
    private loadMap() {
    
      const latLng = new google.maps.LatLng(-1234, 4567);
      const mapOptions = {
        center: latLng,
        zoom: 15,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
      };
       this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
    }
    

    HomePageComponent 模板

    <map *ngIf="showMap"></map>
    

    HomePageComponent 组件

    public toggleMap() {
      this.showMap = !this.showMap;
     }
    
  • 12

    这对我有用 . 在将数据绑定到表单后应用一些延迟 .

    setTimeout(() => { this.changeDetector.detectChanges(); }, 500);
    

相关问题