簡體   English   中英

*ngIf 中的@ViewChild

[英]@ViewChild in *ngIf

問題

顯示模板中的相應元素后獲取@ViewChild的最優雅方法是什么?

下面是一個例子。 Plunker也可用。

組件.模板.html:

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

組件.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有一個選項,但它會導致太多無用的調用。

答案(Plunker)

為 ViewChild 使用 setter:

 private contentPlaceholder: ElementRef;

 @ViewChild('contentPlaceholder') set content(content: ElementRef) {
    if(content) { // initially setter gets called with undefined
        this.contentPlaceholder = content;
    }
 }

一旦*ngIf變為true ,就會使用元素引用調用 setter。

請注意,對於 Angular 8,您必須確保設置{ static: false } ,這是其他 Angular 版本中的默認設置:

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

注意:如果 contentPlaceholder 是一個組件,您可以將 ElementRef 更改為您的組件類:

  private contentPlaceholder: MyCustomComponent;

  @ViewChild('contentPlaceholder') set content(content: MyCustomComponent) {
     if(content) { // initially setter gets called with undefined
          this.contentPlaceholder = content;
     }
  }

克服這個問題的另一種方法是手動運行變化檢測器。

您首先注入ChangeDetectorRef

constructor(private changeDetector : ChangeDetectorRef) {}

然后在更新控制 *ngIf 的變量后調用它

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

角度 8+

您應該添加{ static: false }作為@ViewChild的第二個選項。 這會導致在更改檢測運行解析查詢結果,從而允許您的@ViewChild在值更改后更新。

例子:

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

    display = false;

    constructor(private changeDetectorRef: ChangeDetectorRef) {
    }

    show() {
        this.display = true;

        // Required to access this.contentPlaceholder below,
        // otherwise contentPlaceholder will be undefined
        this.changeDetectorRef.detectChanges();

        console.log(this.contentPlaceholder);
    }
}

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

上面的答案對我不起作用,因為在我的項目中,ngIf 位於輸入元素上。 當 ngIf 為真時,我需要訪問 nativeElement 屬性以便專注於輸入。 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(); });
}

我在聚焦之前使用了 setTimeout,因為 ViewChild 需要一秒鍾才能被分配。 否則它將是未定義的。

正如其他人提到的,最快最快的解決方案是使用 [hidden] 而不是 *ngIf。 采用這種方法,組件將被創建但不可見,因此您可以訪問它。 這可能不是最有效的方法。

這可能有效,但我不知道它是否適合您的情況:

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

ngAfterViewInit() {
 this.viewContainerRefs.changes.subscribe(item => {
   if(this.viewContainerRefs.toArray().length) {
     // shown
   }
 })
}

另一個快速的“技巧”(簡單的解決方案)就是使用 [hidden] 標簽而不是 *ngIf,重要的是要知道在這種情況下 Angular 構建對象並將其繪制在 class:hidden 這就是 ViewChild 工作沒有問題的原因. 所以重要的是要記住,你不應該在可能導致性能問題的沉重或昂貴的物品上使用隱藏

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

我的目標是避免任何假設某些東西的 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;
    });
  }

我的場景:我想根據路由器queryParams@ViewChild元素上觸發一個操作。 由於在 HTTP 請求返回數據之前包裝*ngIf為 false, @ViewChild元素的初始化發生延遲。

它是如何工作的: combineLatest僅在每個提供的 Observable 發出自combineLatest訂閱時刻以來的第一個值時才第一次發出一個值。 當設置@ViewChild元素時,我的 Subject tabSetInitialized會發出一個值。 因此,我延遲了subscribe下代碼的執行,直到*ngIf變為正並且@ViewChild被初始化。

當然不要忘記在 ngOnDestroy 上取消訂閱,我使用ngUnsubscribe主題來完成:

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

一個簡化版本,我在使用 Google Maps JS SDK 時遇到了類似的問題。

我的解決方案是將divViewChild提取到它自己的子組件中,當在父組件中使用時,可以使用*ngIf隱藏/顯示。

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

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

如果我在 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();
}

在我的情況下,只有當模板中存在 div 時,我才需要加載整個模塊,這意味着插座位於 ngif 中。 這樣,每次 angular 檢測到元素 #geolocalisationOutlet 時,它都會在其中創建組件。 該模塊也只加載一次。

constructor(
    public wlService: WhitelabelService,
    public lmService: LeftMenuService,
    private loader: NgModuleFactoryLoader,
    private injector: Injector
) {
}

@ViewChild('geolocalisationOutlet', {read: ViewContainerRef}) set geolocalisation(geolocalisationOutlet: ViewContainerRef) {
    const path = 'src/app/components/engine/sections/geolocalisation/geolocalisation.module#GeolocalisationModule';
    this.loader.load(path).then((moduleFactory: NgModuleFactory<any>) => {
        const moduleRef = moduleFactory.create(this.injector);
        const compFactory = moduleRef.componentFactoryResolver
            .resolveComponentFactory(GeolocalisationComponent);
        if (geolocalisationOutlet && geolocalisationOutlet.length === 0) {
            geolocalisationOutlet.createComponent(compFactory);
        }
    });
}

<div *ngIf="section === 'geolocalisation'" id="geolocalisation">
     <div #geolocalisationOutlet></div>
</div>

我認為使用lodash 中的 defer很有意義,尤其是在我的@ViewChild()位於async管道內的情況下

使用 Angular 8 無需導入 ChangeDector

ngIf 允許您不加載元素並避免給您的應用程序增加更多壓力。 這是我在沒有 ChangeDetector 的情況下運行它的方法

elem: ElementRef;

@ViewChild('elemOnHTML', {static: false}) set elemOnHTML(elemOnHTML: ElementRef) {
    if (!!elemOnHTML) {
      this.elem = elemOnHTML;
    }
}

然后,當我將 ngIf 值更改為真實時,我會像這樣使用 setTimeout 來等待下一個更改周期:

  this.showElem = true;
  console.log(this.elem); // undefined here
  setTimeout(() => {
    console.log(this.elem); // back here through ViewChild set
    this.elem.do();
  });

這也讓我避免使用任何額外的庫或導入。

對於Angular 8 - null 檢查和@ViewChild static: false hackery 的混合

用於等待異步數據的分頁控制

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

只需確保將靜態選項設置為 false

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

我自己也有同樣的問題,使用 Angular 10。

如果我嘗試使用[hidden]*ngIf ,則@ViewChild變量始終為undefined

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

我通過將其從網頁中刪除來修復它。
我使用[ngClass]使控件具有opacity:0 ,並將其完全移開。

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

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

是的,我知道,它既愚蠢又丑陋,但它解決了問題。

我還必須使控件static 我不明白為什么.. 但是,如果沒有這個改變,它又拒絕工作:

export class DatePickerCellRenderer {
    @ViewChild('calendar', {static: true }) calendar: Calendar;
We had a situation to set tabindex on ngif

html:

<div #countryConditional1 *ngIf="country=='USA'">                        
<input id="streetNumber"  [(ngModel)]="streetNumber" pInputText>
</div>
             
        

ts:

@ViewChild('countryConditional1') set countryConditional1(element){
        if (element){
            const container2 = document.querySelector("#someElement");
            container2.querySelector("span > input").setAttribute("tabindex", "18");}

閱讀並嘗試這個

Make sure passing the param { static: false } to @ViewChild resolve the problem.

**template.html code**

  <div *ngIf="showFirtChild">
    <first-child #firstchildComponent ></first-child>
  </div>

**in .ts file**

export class Parent implements 
{
  private firstChild: FirstchildComponent;

  @ViewChild('firstchildComponent', { static: false }) set content(content: 
  FirstchildComponent) {
     if(content) { 
          this.firstchildComponent = content;
     }
  }

 constructor(){}

  ShowChild(){
     this.showFirtChild = true;
     if(this.firstchildComponent){
        this.firstchildComponent.YourMethod()
     }
  }

}

如果 setter 似乎無法與 @ViewChild 一起工作(根本沒有被調用),請嘗試@ContentChild @ViewChild

這為我工作。 將數據綁定到表單后,請稍加延遲。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM