Angular 2 @ViewChild en * ngIf

Pregunta

¿Cuál es la forma más elegante de obtener @ViewChild después de mostrar el elemento correspondiente en la plantilla? A continuación hay un ejemplo. También Plunker disponible.

Modelo:

Componente:

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

Tengo un componente con sus contenidos ocultos por defecto. Cuando alguien llama al método show() se vuelve visible. Sin embargo, antes de que se complete la detección de cambio angular 2, no puedo hacer referencia a viewContainerRef . Normalmente setTimeout(()=>{},1) todas las acciones requeridas en setTimeout(()=>{},1) como se muestra arriba. ¿Hay una forma más correcta?

Sé que hay una opción con ngAfterViewChecked , pero causa demasiadas llamadas inútiles.

RESPUESTA (Plunker)

La respuesta aceptada usando una QueryList no funcionó para mí. Sin embargo, lo que funcionó fue usar un setter para ViewChild:

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

El colocador se llama una vez * ngIf se convierte en verdadero.

Una alternativa para superar esto es ejecutar el detector de cambio manualmente.

Primero inyecta ChangeDetectorRef :

 constructor(private changeDetector : ChangeDetectorRef) {} 

Luego lo llama después de actualizar la variable que controla el * ngIf

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

Las respuestas anteriores no me funcionaron porque en mi proyecto, ngIf está en un elemento de entrada. Necesitaba acceder al atributo nativeElement para centrarme en la entrada cuando ngIf es verdadero. Parece que no hay ningún atributo nativeElement en ViewContainerRef. Esto es lo que hice (siguiendo la documentación de @ViewChild ):

  
... private assetInputElRef:ElementRef; @ViewChild('assetInput') set assetInput(elRef: ElementRef) { this.assetInputElRef = elRef; } ... showAsset() { this.showAssetInput = true; setTimeout(() => { this.assetInputElRef.nativeElement.focus(); }); }

Usé setTimeout antes de enfocarme porque ViewChild tarda un segundo en asignarse. De lo contrario, no estaría definido.

Como fue mencionado por otros, la solución más rápida y más rápida es usar [oculto] en lugar de * ngIf, de esta manera el componente se creará pero no será visible, para que pueda tener acceso a él, aunque podría no ser el más eficiente camino.

Esto podría funcionar, pero no sé si es conveniente para su caso:

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

Mi objective era evitar cualquier método hacky que asumiera algo (por ejemplo, setTimeout) y terminé implementando la solución aceptada con un poco de sabor RxJS en la parte superior:

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

Mi escenario: quería lanzar una acción en un elemento @ViewChild según el enrutador queryParams . Debido a un *ngIf es falso hasta que la solicitud HTTP devuelva los datos, la inicialización del elemento @ViewChild ocurre con un retraso.

Cómo funciona: combineLatest emite un valor por primera vez solo cuando cada uno de los Observables proporcionados emiten el primer valor desde el momento en que se suscribió combineLatest . My Subject tabSetInitialized emite un valor cuando se establece el elemento @ViewChild . Con eso, retrasaré la ejecución del código bajo subscribe hasta que *ngIf vuelva positivo y @ViewChild se inicialice.

Por supuesto, no se olvide de darse de baja en ngOnDestroy, lo hago usando el sujeto ngUnsubscribe :

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

Tuve un problema similar. Lo he arreglado usando hidden como este:

En lugar de:

 

Solía:

 

*ngIf quita un elemento de DOM, mientras que [hidden] alterna la propiedad CSS.

Esto estaba funcionando para mí. Simplemente aplique un poco de retraso después de vincular los datos a la forma.

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