テンプレート内の対応する要素が表示された後に@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
のオプションがあることを知っていますが、それはあまりにも多くの無駄な呼び出しを引き起こします。
QueryListを使って受け入れられた答えは私にはうまくいきませんでした。しかし、作業はViewChildにセッターを使用することでした。
private contentPlaceholder: ElementRef;
@ViewChild('contentPlaceholder') set content(content: ElementRef) {
this.contentPlaceholder = content;
}
セッターは* ngIfがtrueになると呼び出されます。
これを克服するための代替策は、変化検出器を手動で実行することです。
最初にChangeDetectorRef
をインジェクトします。
constructor(private changeDetector : ChangeDetectorRef) {}
* ngIfを制御する変数を更新した後にそれを呼び出す
show() {
this.display = true;
this.changeDetector.detectChanges();
}
私のプロジェクトでは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を使用しました。そうでなければ未定義になります。
他の人が述べたように、最速かつ最速の解決策は* ngIfの代わりに[hidden]を使用することです。方法。
これはうまくいく可能性がありますが、それがあなたのケースにとって都合がよいかどうかはわかりません。
@ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs: QueryList;
ngAfterViewInit() {
this.viewContainerRefs.changes.subscribe(item => {
if(this.viewContainerRefs.toArray().length) {
// shown
}
})
}
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
もう1つの簡単な "トリック"(簡単な解決策)は* ngIfの代わりに[hidden]タグを使うことです。その場合は知っておくことが重要です Angular オブジェクトをビルドしてクラスの下にペイントします。これがViewChildが問題なく動作する理由です。 したがって、パフォーマンスの問題を引き起こす可能性がある重いアイテムや高価なアイテムにはhiddenを使用しないでください
<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
がサブスクライブされてから提供されたObservablesのそれぞれが最初の値を発行する場合にのみ、combineLatest
は初めて値を発行します。 @ViewChild
要素が設定されているとき、私の話題tabSetInitialized
は値を出します。それとともに、*ngIf
が正になり@ViewChild
が初期化されるまで、subscribe
の下でコードの実行を遅らせます。
もちろん、ngOnDestroyの購読を中止することを忘れないでください、私はそれをngUnsubscribe
Subjectを使ってします:
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
簡略化されたバージョンで、Google Maps JS SDKを使用しているときにこれと同じ問題が発生しました。
私の解決策は、div
とViewChild
をそれ自身の子コンポーネントに抽出することでした。これは親コンポーネントで使用されると*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;
}
をlodash から延期することは、特に私の@ViewChild()
がasync
パイプの内側にある場合に非常に意味があると思います
私の場合は、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>