web-dev-qa-db-ja.com

* ngIfの@ViewChild

質問

テンプレート内の対応する要素が表示された後に@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()メソッドを呼び出すと、それが見えるようになります。ただし、Angular 2変更検出が完了する前は、viewContainerRefを参照できません。私は通常、上記のように必要なすべてのアクションをsetTimeout(()=>{},1)にラップします。もっと正しい方法はありますか?

私はngAfterViewCheckedのオプションがあることを知っていますが、それはあまりにも多くの無駄な呼び出しを引き起こします。

ANSWER(プランカー)

142
sinedsem

QueryListを使って受け入れられた答えは私にはうまくいきませんでした。しかし、作業はViewChildにセッターを使用することでした。

 private contentPlaceholder: ElementRef;

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

セッターは* ngIfがtrueになると呼び出されます。

230
parliament

これを克服するための代替策は、変化検出器を手動で実行することです。

最初にChangeDetectorRefをインジェクトします。

constructor(private changeDetector : ChangeDetectorRef) {}

* ngIfを制御する変数を更新した後にそれを呼び出す

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

私のプロジェクトではngIfがinput要素にあるので、上記の答えは私にはうまくいきませんでした。 ngIfがtrueのときに入力に焦点を合わせるためには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(); });
}

ViewChildが割り当てられるのに1秒かかるので、フォーカスする前にsetTimeoutを使用しました。そうでなければ未定義になります。

19
zebraco

他の人が述べたように、最速かつ最速の解決策は* ngIfの代わりに[hidden]を使用することです。方法。

10
user3728728

これはうまくいく可能性がありますが、それがあなたのケースにとって都合がよいかどうかはわかりません。

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

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

Angular 8

{ static: false }の2番目のオプションとして@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);
  }
}

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

5

もう1つの簡単な "トリック"(簡単な解決策)は* ngIfの代わりに[hidden]タグを使うことです。その場合は知っておくことが重要です Angular オブジェクトをビルドしてクラスの下にペイントします。これがViewChildが問題なく動作する理由です。 したがって、パフォーマンスの問題を引き起こす可能性がある重いアイテムや高価なアイテムにはhiddenを使用しないでください

  <div class="addTable" [hidden]="CONDITION">
4
Or Yaacov

私の目標は、何かを想定したハックな方法(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がサブスクライブされてから提供されたObservablesのそれぞれが最初の値を発行する場合にのみ、combineLatestは初めて値を発行します。 @ViewChild要素が設定されているとき、私の話題tabSetInitializedは値を出します。それとともに、*ngIfが正になり@ViewChildが初期化されるまで、subscribeの下でコードの実行を遅らせます。

もちろん、ngOnDestroyの購読を中止することを忘れないでください、私はそれをngUnsubscribe Subjectを使ってします:

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }
3
Filip Juncu

簡略化されたバージョンで、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;
 }
1
Eugene

をlodash から延期することは、特に私の@ViewChild()asyncパイプの内側にある場合に非常に意味があると思います

0
Timo

私の場合は、divがテンプレート内に存在する場合にのみモジュール全体をロードする必要がありました。これは、アウトレットがngif内にあることを意味します。このようにして、角度が要素#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>
0
Gabb1995