2016-01-22 16 views
74

Sto cercando di imparare angolare 2.angolari rendimenti annotazione 2 @ViewChild undefined

Vorrei accedere ad un componente bambino da un componente principale usando l'annotazione @ViewChild.

Ecco alcune righe di codice:

In BodyContent.ts ho:

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

mentre in 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(){}; 
} 

Infine sua all'e i modelli (come suggerito nei commenti):

BodyContent.html

<div (click)="onClickSidebar()" class="row" style="height:200px; background-color:red;"> 
     <ico-filter-tiles></ico-filter-tiles> 
    </div> 

FilterTiles.html

<h1>Tiles loaded</h1> 
<div *ngFor="#tile of tiles" class="col-md-4"> 
    ... stuff ... 
</div> 

Il modello FilterTiles.html è stato caricato correttamente nel tag ico-filter-tiles (infatti sono in grado di vedere l'intestazione).

Nota: la classe BodyContent viene iniettato all'interno di un altro modello (corpo) utilizzando DynamicComponetLoader: dcl.loadAsRoot (BodyContent, '# ico-bodyContent', iniettore):

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

    } 
} 

Il problema è che quando provo per scrivere ft nel log della console, ottengo "indefinito" e naturalmente ottengo un'eccezione quando provo a inserire qualcosa all'interno dell'array "tiles" (nessuna proprietà per "undefined").

Un'altra cosa: il componente FilterTiles sembra essere caricato correttamente, dal momento che sono in grado di vedere il modello html per esso.

Qualche suggerimento? Grazie

+0

Sembra corretto. Forse qualcosa con il modello, ma non è incluso nella tua domanda. –

+1

Concordato con Günter. Ho creato un plunkr con il tuo codice e semplici template associati e funziona. Vedi questo link: https://plnkr.co/edit/KpHp5Dlmppzo1LXcutPV?p=preview. Abbiamo bisogno dei modelli ;-) –

+1

'ft' non sarebbe stato impostato nel costruttore, ma in un gestore di eventi click sarebbe già stato impostato. –

risposta

131

ho avuto un problema simile e ho pensato di postare in caso qualcuno altro ha fatto lo stesso errore. Innanzitutto, una cosa da considerare è AfterViewInit; devi aspettare che la vista venga inizializzata prima di poter accedere a @ViewChild. Tuttavia, il mio @ViewChild restituiva ancora null. Il problema era il mio * ngIf. La direttiva * ngIf stava uccidendo il mio componente di controllo in modo da non poterlo fare riferimento.

import {Component, ViewChild, OnInit, AfterViewInit} from 'angular2/core'; 
import {ControlsComponent} from './controls/controls.component'; 
import {SlideshowComponent} from './slideshow/slideshow.component'; 

@Component({ 
    selector: 'app', 
    template: ` 
     <controls *ngIf="controlsOn"></controls> 
     <slideshow (mousemove)="onMouseMove()"></slideshow> 
    `, 
    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 
    } 
} 

Spero che questo aiuti.

+1

true il ngIf farà casino con le cose. la mia soluzione consisteva nel far comparire un evento dal componente figlio per dire al componente genitore che era stato fatto inizializzare. leggermente hacky ma funziona – brando

+5

@kenecaswell Quindi hai trovato il modo migliore per risolvere il problema. Anch'io sto affrontando lo stesso problema. Ho molti * ngIf in modo tale che l'elemento sarà nel solo dopo tutto vero, ma ho bisogno del riferimento all'elemento. Un modo per risolvere questo> – monica

+2

Ho trovato che il componente figlio è "non definito" in ngAfterViewInit() se si utilizza ngIf. Ho provato a mettere lunghi timeout ma ancora nessun effetto. Tuttavia, il componente figlio è disponibile in seguito (ovvero in risposta agli eventi di clic, ecc.). Se non utilizzo ngIf, viene definito come previsto in ngAfterViewInit(). C'è di più sulla comunicazione padre/figlio qui https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child –

1

Deve funzionare.

Ma, come Günter Zöchbauer ha detto che ci deve essere qualche altro problema nel modello. Ho creato un tipo Relevant-Plunkr-Answer. Fai controllare la console del browser.

boot.ts

@Component({ 
selector: 'my-app' 
, template: `<div> <h1> BodyContent </h1></div> 

     <filter></filter> 

     <button (click)="onClickSidebar()">Click Me</button> 
    ` 
, 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: '<div> <h4>Filter tiles </h4></div>' 
}) 


export class FilterTiles { 
    public tiles = []; 

    public constructor(){}; 
} 

Funziona come un fascino. Si prega di ricontrollare i tag e i riferimenti.

Grazie ...

47

Il problema come accennato in precedenza è il ngIf che causa la visualizzazione non definita. La risposta è usare ViewChildren invece di ViewChild. Ho avuto un problema simile in cui non volevo mostrare una griglia finché non sono stati caricati tutti i dati di riferimento.

html:

<section class="well" *ngIf="LookupData != null"> 
     <h4 class="ra-well-title">Results</h4> 
     <kendo-grid #searchGrid> </kendo-grid> 
    </section> 

Codice 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<GridComponent> 

    private SearchGrid: GridComponent 

    public ngAfterViewInit(): void 
    { 

     this.Grids.changes.subscribe((comps: QueryList <GridComponent>) => 
     { 
      this.SearchGrid = comps.first; 
     }); 


    } 
} 

Qui stiamo usando ViewChildren su cui si può ascoltare per le modifiche. In questo caso, ogni bambino con il riferimento #searchGrid. Spero che questo ti aiuti.

+1

Vorrei aggiungere che, in alcuni casi, quando provi a cambiare, ad es. 'Questo.Proprietà SearchGrid' dovresti usare la sintassi come 'setTimeout (() => {/// il tuo codice qui}, 1); 'per evitare Eccezioni: l'espressione è cambiata dopo il suo controllo – rafalkasa

+1

Come si fa se si desidera posizionare il tag #searchGrid su un normale elemento HTML anziché su un elemento Angular2? (Ad esempio,

e questo è all'interno di un blocco * ngIf? –

+0

questa è la risposta corretta per il mio caso d'uso! Grazie Ho bisogno di accedere a un componente come è disponibile attraverso ngIf = –

9

Questo ha funzionato per me.

mio componente denominato 'il mio componenti', per esempio, è stato mostrato con * ngIf = "ShowMe" in questo modo:

<my-component [showMe]="showMe" *ngIf="showMe"></my-component> 

Così, quando il componente viene inizializzato il componente non è ancora visualizzato fino a quando "showMe" è vero. Pertanto, i miei riferimenti @ViewChild erano tutti non definiti.

Qui è dove ho usato @ViewChildren e QueryList che restituisce. Vedi angular article on QueryList and a @ViewChildren usage demo.

È possibile utilizzare QueryList che @ViewChildren restituisce e sottoscrive qualsiasi modifica agli elementi di riferimento utilizzando rxjs come indicato di seguito. @ViewChid non ha questa capacità.

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<any>; // this reference is just pointing to a template reference variable in the component html file (i.e. <div #ref></div>) 
    @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 

Spero che questo aiuti qualcuno a risparmiare tempo e frustrazione.

+1

Ho appena notato che la risposta di Ashg era praticamente la stessa della mia Oh bene, –

+1

La tua fornisce una spiegazione migliore di come e perché funziona :) – rmcsharry

+0

Joshua !! Sei un risparmiatore di vita !!! Grazie mille! – Toolsmythe

4

La mia soluzione alternativa era usare [style.display] = "getControlsOnStyleDisplay()" invece di * ngIf = "controlsOn". Il blocco è lì ma non è visualizzato.

@Component({ 
selector: 'app', 
template: ` 
    <controls [style.display]="getControlsOnStyleDisplay()"></controls> 
... 

export class AppComponent { 
    @ViewChild(ControlsComponent) controls:ControlsComponent; 

    controlsOn:boolean = false; 

    getControlsOnStyleDisplay() { 
    if(this.controlsOn) { 
     return "block"; 
    } else { 
     return "none"; 
    } 
    } 
.... 
+0

Clean hack :) nice – mp3por

0

La mia soluzione a questo è stato quello di spostare il ngIf al di fuori del componente figlio verso l'interno della componente figlio su un div che ha avvolto l'intera sezione di HTML. In quel modo si stava ancora nascondendo quando doveva essere, ma era in grado di caricare il componente e potevo fare riferimento al genitore.

-1

Questo sarà facile. Prova questo se non vuoi eseguire il rendering dell'elemento e comunque passa senza errori.Basta posizionare una condizione if per verificare la presenza indefinita .:

@ViewChild(ControlsComponent) controls:ControlsComponent; 
 
    
 
..... 
 
if(controls){ 
 
    controls.yourChildFunction(); 
 
}

+0

questo nasconde il problema e non lo risolve ... – Nova

0

Questo funziona per me, vedere l'esempio di seguito.

import {Component, ViewChild, ElementRef} from 'angular2/core'; 
 

 
@Component({ 
 
    selector: 'app', 
 
    template: ` 
 
     <a (click)="toggle($event)">Toggle</a> 
 
     <div *ngIf="visible"> 
 
      <input #control name="value" [(ngModel)]="value" type="text" /> 
 
     </div> 
 
    `, 
 
}) 
 

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

 
}

0

Ho avuto un problema simile, dove il ViewChild era all'interno di una clausola switch che non è stato caricando l'elemento viewChild prima che fosse fatto riferimento. Ho risolto in maniera semi-hacky ma avvolgendo il riferimento ViewChild in un setTimeout che ha eseguito immediatamente (cioè 0 ms)

4

Si potrebbe utilizzare un setter per @ViewChild()

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

Se si dispone di un wrapper ngIf, il il setter verrà chiamato con undefined, e quindi di nuovo con un riferimento una volta che ngIf gli consentirà il rendering.

Il mio problema era qualcos'altro. Non avevo incluso il modulo contenente i miei "FilterTiles" nei miei app.modules. Il modello non ha generato un errore ma il riferimento era sempre indefinito.

+0

Esattamente quello di cui avevo bisogno. Grazie! –

Problemi correlati