La anotación angular 2 @ViewChild devuelve indefinido

Estoy tratando de aprender Angular 2.

Me gustaría acceder a un componente secundario desde un componente principal utilizando la anotación @ViewChild .

Aquí algunas líneas de código:

En BodyContent.ts tengo:

import {ViewChild, Component, Injectable} from 'angular2/core'; import {FilterTiles} from '../Components/FilterTiles/FilterTiles'; @Component({ selector: 'ico-body-content' , templateUrl: 'App/Pages/Filters/BodyContent/BodyContent.html' , directives: [FilterTiles] }) export class BodyContent { @ViewChild(FilterTiles) ft:FilterTiles; public onClickSidebar(clickedElement: string) { console.log(this.ft); var startingFilter = { title: 'cognomi', values: [ 'griffin' , 'simpson' ]} this.ft.tiles.push(startingFilter); } } 

mientras está en FilterTiles.ts :

  import {Component} from 'angular2/core'; @Component({ selector: 'ico-filter-tiles' ,templateUrl: 'App/Pages/Filters/Components/FilterTiles/FilterTiles.html' }) export class FilterTiles { public tiles = []; public constructor(){}; } 

Finalmente aquí las plantillas (como se sugiere en los comentarios):

BodyContent.html

 

FilterTiles.html

 

Tiles loaded

... stuff ...

La plantilla FilterTiles.html se carga correctamente en la etiqueta ico-filter-tiles (de hecho, puedo ver el encabezado).

Nota: la clase BodyContent se inyecta dentro de otra plantilla (Cuerpo) usando DynamicComponetLoader: dcl.loadAsRoot (BodyContent, ‘# ico-bodyContent’, inyector):

 import {ViewChild, Component, DynamicComponentLoader, Injector} from 'angular2/core'; import {Body} from '../../Layout/Dashboard/Body/Body'; import {BodyContent} from './BodyContent/BodyContent'; @Component({ selector: 'filters' , templateUrl: 'App/Pages/Filters/Filters.html' , directives: [Body, Sidebar, Navbar] }) export class Filters { constructor(dcl: DynamicComponentLoader, injector: Injector) { dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector); dcl.loadAsRoot(SidebarContent, '#ico-sidebarContent', injector); } } 

El problema es que cuando bash escribir ft en el registro de la consola, me vuelvo undefined y, por supuesto, obtengo una excepción cuando bash introducir algo dentro de la matriz “tiles”: “no hay propiedades para” undefined “‘ .

Una cosa más: el componente FilterTiles parece estar cargado correctamente, ya que puedo ver la plantilla html para él.

¿Cualquier sugerencia? Gracias

Tuve un problema similar y pensé que lo publicaría en caso de que alguien más cometiera el mismo error. Primero, una cosa a considerar es AfterViewInit ; necesita esperar a que la vista se inicialice antes de que pueda acceder a su @ViewChild . Sin embargo, mi @ViewChild todavía estaba devolviendo nulo. El problema era mi *ngIf . La directiva *ngIf estaba matando a mi componente de controles por lo que no pude referenciarlo.

 import {Component, ViewChild, OnInit, AfterViewInit} from 'angular2/core'; import {ControlsComponent} from './controls/controls.component'; import {SlideshowComponent} from './slideshow/slideshow.component'; @Component({ selector: 'app', template: `   `, directives: [SlideshowComponent, ControlsComponent] }) export class AppComponent { @ViewChild(ControlsComponent) controls:ControlsComponent; controlsOn:boolean = false; ngOnInit() { console.log('on init', this.controls); // this returns undefined } ngAfterViewInit() { console.log('on after view init', this.controls); // this returns null } onMouseMove(event) { this.controls.show(); // throws an error because controls is null } } 

Espero que ayude.

EDITAR
Como se menciona en @Ashg a continuación, una solución es usar @ViewChildren lugar de @ViewChild .

El problema mencionado anteriormente es el ngIf que causa que la vista no esté definida. La respuesta es usar ViewChildren en lugar de ViewChild. Tuve un problema similar en el que no quería que se mostrara una cuadrícula hasta que se hubieran cargado todos los datos de referencia.

html:

  

Results

Código de componente

 import { Component, ViewChildren, OnInit, AfterViewInit, QueryList } from '@angular/core'; import { GridComponent } from '@progress/kendo-angular-grid'; export class SearchComponent implements OnInit, AfterViewInit { //other code emitted for clarity @ViewChildren("searchGrid") public Grids: QueryList private SearchGrid: GridComponent public ngAfterViewInit(): void { this.Grids.changes.subscribe((comps: QueryList ) => { this.SearchGrid = comps.first; }); } } 

Aquí estamos usando ViewChildren en el que puedes escuchar los cambios. En este caso, cualquier niño con la referencia #searchGrid. Espero que esto ayude.

Podría usar un setter para @ViewChild()

 @ViewChild(FilterTiles) set ft(tiles: FilterTiles) { console.log(tiles); ); 

Si tiene un contenedor ngIf, se llamará al setter con undefined, y luego nuevamente con una referencia una vez que ngIf le permita renderizar.

Mi problema era algo más. No había incluido el módulo que contiene mi “FilterTiles” en mi app.modules. La plantilla no arrojó un error, pero la referencia siempre fue indefinida.

Esto funcionó para mí.

Mi componente llamado ‘my-component’, por ejemplo, se mostró usando * ngIf = “showMe” como así:

  

Entonces, cuando el componente se inicializa, el componente aún no se muestra hasta que “showMe” sea verdadero. Por lo tanto, mis referencias de @ViewChild no estaban definidas.

Aquí es donde utilicé @ViewChildren y la QueryList que devuelve. Vea el artículo angular en QueryList y una demostración de uso de @ViewChildren .

Puede usar QueryList que @ViewChildren devuelve y suscribirse a cualquier cambio en los elementos a los que se hace referencia usando rxjs como se ve a continuación. @ViewChid no tiene esta habilidad.

 import { Component, ViewChildren, ElementRef, OnChanges, QueryList, Input } from '@angular/core'; import 'rxjs/Rx'; @Component({ selector: 'my-component', templateUrl: './my-component.component.html', styleUrls: ['./my-component.component.css'] }) export class MyComponent implements OnChanges { @ViewChildren('ref') ref: QueryList; // this reference is just pointing to a template reference variable in the component html file (ie 
) @Input() showMe; // this is passed into my component from the parent as a ngOnChanges () { // ngOnChanges is a component LifeCycle Hook that should run the following code when there is a change to the components view (like when the child elements appear in the DOM for example) if(showMe) // this if statement checks to see if the component has appeared becuase ngOnChanges may fire for other reasons this.ref.changes.subscribe( // subscribe to any changes to the ref which should change from undefined to an actual value once showMe is switched to true (which triggers *ngIf to show the component) (result) => { // console.log(result.first['_results'][0].nativeElement); console.log(result.first.nativeElement); // Do Stuff with referenced element here... } ); // end subscribe } // end if } // end onChanges } // end Class

Espero que esto ayude a alguien a ahorrar algo de tiempo y frustración.

Mi solución fue usar [style.display] = “getControlsOnStyleDisplay ()” en lugar de * ngIf = “controlsOn”. El bloque está ahí pero no se muestra.

 @Component({ selector: 'app', template: `  ... export class AppComponent { @ViewChild(ControlsComponent) controls:ControlsComponent; controlsOn:boolean = false; getControlsOnStyleDisplay() { if(this.controlsOn) { return "block"; } else { return "none"; } } .... 

Debe funcionar.

Pero como dijo Günter Zöchbauer , debe haber algún otro problema en la plantilla. He creado algo Relevante-Plunkr-Answer . Por favor, verifique la consola del navegador.

boot.ts

 @Component({ selector: 'my-app' , template: `

BodyContent

` , directives: [FilterTiles] }) export class BodyContent { @ViewChild(FilterTiles) ft:FilterTiles; public onClickSidebar() { console.log(this.ft); this.ft.tiles.push("entered"); } }

filterTiles.ts

 @Component({ selector: 'filter', template: '

Filter tiles

' }) export class FilterTiles { public tiles = []; public constructor(){}; }

Funciona a las mil maravillas. Por favor revise sus tags y referencias.

Gracias…

Mi solución a esto fue mover el ngIf desde fuera del componente hijo al interior del componente secundario en un div que envolvió toda la sección de html. De esa manera, aún se ocultaba cuando era necesario, pero podía cargar el componente y podía hacer referencia al padre.

Esto funciona para mí, mira el ejemplo a continuación.

 import {Component, ViewChild, ElementRef} from 'angular2/core'; @Component({ selector: 'app', template: ` Toggle 
`, }) export class AppComponent { private elementRef: ElementRef; @ViewChild('control') set controlElRef(elementRef: ElementRef) { this.elementRef = elementRef; } visible:boolean; toggle($event: Event) { this.visible = !this.visible; if(this.visible) { setTimeout(() => { this.elementRef.nativeElement.focus(); }); } } }

Mi solución a esto fue reemplazar * ngIf con [hidden]. Lo malo fue que todos los componentes secundarios estaban presentes en el código DOM. Pero funcionó para mis requerimientos.

Tuve un problema similar, donde ViewChild estaba dentro de una cláusula switch que no estaba cargando el elemento viewChild antes de que se estuviera haciendo referencia a él. Lo resolví de una manera semi-hacky pero envolviendo la referencia de ViewChild en un setTimeout que se ejecutó inmediatamente (es decir, 0ms)

En mi caso, sabía que el componente secundario siempre estaría presente, pero quería modificar el estado antes de que el niño se inicializara para guardar el trabajo.

Elijo probar para el niño hasta que apareció y hacer cambios inmediatamente, lo que me salvó un ciclo de cambio en el componente infantil.

 export class GroupResultsReportComponent implements OnInit { @ViewChild(ChildComponent) childComp: ChildComponent; ngOnInit(): void { this.WhenReady(() => this.childComp, () => { this.childComp.showBar = true; }); } /** * Executes the work, once the test returns truthy * @param test a function that will return truthy once the work function is able to execute * @param work a function that will execute after the test function returns truthy */ private WhenReady(test: Function, work: Function) { if (test()) work(); else setTimeout(this.WhenReady.bind(window, test, work)); } } 

De forma análoga, podría agregar un número máximo de bashs o agregar unos cuantos ms de retraso al setTimeout . setTimeout efectivamente arroja la función al final de la lista de operaciones pendientes.

En mi caso, tenía un setter de variables de entrada usando ViewChild, y ViewChild estaba dentro de una directiva * ngIf, por lo que el setter estaba tratando de acceder antes de la * ngIf prestada (funcionaría bien sin * ngIf, pero lo haría no funciona si siempre se configuró en verdadero con * ngIf = “true”).

Para resolverlo, utilicé Rxjs para asegurarme de que cualquier referencia a ViewChild esperó hasta que se inició la vista. Primero, crea un Sujeto que se complete cuando después de ver init.

 export class MyComponent implements AfterViewInit { private _viewInitWaiter$ = new Subject(); ngAfterViewInit(): void { this._viewInitWaiter$.complete(); } } 

A continuación, cree una función que ejecute ejecuta un lambda después de que el sujeto complete.

 private _executeAfterViewInit(func: () => any): any { this._viewInitWaiter$.subscribe(null, null, () => { return func(); }) } 

Finalmente, asegúrese de que las referencias a ViewChild usen esta función.

 @Input() set myInput(val: any) { this.executeAfterViewInit(() => { const viewChildProperty = this.viewChild.someProperty; ... }); } @ViewChild('viewChildRefName', {read: MyViewChildComponent}) viewChild: MyViewChildComponent; 

Lo soluciono simplemente agregando SetTimeout después de configurar el componente visible

Mi HTML:

  

Mi Componente JS

 @Component({ selector: "app-topbar", templateUrl: "./topbar.component.html", styleUrls: ["./topbar.component.scss"] }) export class TopbarComponent implements OnInit { public show:boolean=false; @ViewChild("txtBus") private inputBusRef: ElementRef; constructor() { } ngOnInit() {} ngOnDestroy(): void { } showInput() { this.show = true; setTimeout(()=>{ this.inputBusRef.nativeElement.focus(); },500); } }