2016-01-22 55 views
74

我想學習角2角2 @ViewChild註釋返回undefined

我想從使用@ViewChild註釋父組件訪問子組件。

下面的幾行代碼:

BodyContent.ts我:

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

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

最後她的Ë模板(如在評論建議):

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> 

FilterTiles.html模板正確加載到ico-filter-tiles標記(的確我能看到標題)。

注:dcl.loadAsRoot(的BodyContent, '#ICO-的BodyContent',噴油器):在的BodyContent類是另一個模板(正文)使用DynamicComponetLoader內注入

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

    } 
} 

的問題是,當我嘗試將ft寫入控制檯日誌中,我得到「undefined」,當然,當我嘗試在「tiles」數組內部推送某些內容(沒有屬性切片用於「undefined」)時,我會遇到異常。

還有一件事:FilterTiles組件似乎被正確加載,因爲我能夠看到它的html模板。

什麼建議嗎?謝謝

+0

看起來正確。也許與模板有關,但它不包含在你的問題中。 –

+1

同意Günter。我用你的代碼和簡單的關聯模板創建了一個plunkr,它可以工作。請參閱此鏈接:https://plnkr.co/edit/KpHp5Dlmppzo1LXcutPV?p=preview。我們需要這些模板;-) –

+1

'ft'不會在構造函數中設置,但是在點擊事件處理函數中它已經被設置了。 –

回答

131

我也有類似的問題,我想我會的情況下,有人張貼別人犯同樣的錯誤。首先,需要考慮的一件事是AfterViewInit;你需要等待視圖被初始化,然後才能訪問你的@ViewChild。不過,我的@ViewChild仍然返回null。問題是我的* ngIf。 * ngIf指令正在殺死我的控件組件,所以我不能引用它。

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

希望有所幫助。

+1

true ngIf會混淆事物。我的解決方案是從子組件中冒出一個事件,告訴父組件它已完成初始化。輕度hacky,但它的作品 – brando

+5

@kenecaswell所以你找到了解決問題的更好方法。我也面臨同樣的問題。我有很多* ngIf,所以這個元素將在唯一的真正的,但我需要的元素參考。任何方式來解決這個> – monica

+2

如果使用ngIf,我發現在ngAfterViewInit()中,子組件是'undefined'。我嘗試了很長的超時,但仍然沒有效果。但是,子組件稍後可用(即響應點擊事件等)。如果我不使用ngIf,並且它在ngAfterViewInit()中按預期定義。這裏https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child還有更多的父/子通信 –

1

它必須工作。

但作爲岡特Zöchbauer說,必須在模板中的一些其他問題。我創建了一個有點Relevant-Plunkr-Answer。請檢查瀏覽器的控制檯。

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

它的工作原理就像一個魅力。請仔細檢查您的標籤和參考。

謝謝...

47

前面提到的問題是ngIf導致視圖未定義。答案是使用ViewChildren而不是ViewChild。我有類似的問題,我不希望網格顯示,直到所有的參考數據已被加載。

HTML:

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

組件代碼

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


    } 
} 

在這裏,我們使用的是上,您可以收聽更改ViewChildren。在這種情況下,任何帶有#searchGrid參考的孩子。希望這可以幫助。

+1

我想補充說,在某些情況下,當你嘗試改變例如。 '這一點。SearchGrid'屬性你應該使用像'setTimeout(()=> {///你的代碼在這裏},1)的語法; '以避免異常:檢查後表達式已更改 – rafalkasa

+1

如果要將#searchGrid標記放置在普通HTML元素而不是Angular2元素上,您如何執行此操作? (例如,

,這是在一個* ngIf塊? –

+0

這是我的用例正確的答案!謝謝 我需要訪問一個組件,因爲它可以通過ngIf = –

9

這對我有效。

我提到的部件「我的成分」,例如,用* ngIf =「SHOWME」 像這樣顯示:

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

所以,當組件初始化組件尚未顯示,直到「showMe」是真實的。因此,我的@ViewChild引用都是未定義的。

這是我使用@ViewChildren和它返回的QueryList的地方。請參閱angular article on QueryList and a @ViewChildren usage demo

您可以使用@ViewChildren返回的QueryList,並使用rxjs預訂對引用項的任何更改,如下所示。 @ViewChid沒有這個能力。

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 

希望這可以幫助某人節省一些時間和挫折感。

+1

只需注意Ashg的答案與我的幾乎相同。哦, –

+1

你提供了一個更好的解釋,說明它的工作原理:) – rmcsharry

+0

約書亞!你是一個拯救生命的人!非常感謝! – Toolsmythe

4

我的解決方法是使用[style.display] =「getControlsOnStyleDisplay()」,而不是* ngIf =「controlsOn」。該塊在那裏,但不顯示。

@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

乾淨的破解:)不錯 – mp3por

0

我對這個解決方案是在ngIf從子組件的外移到子組件的內部上的div包裹HTML的整段。這樣,當它需要時它仍然隱藏起來,但是能夠加載組件,並且我可以在父項中引用它。

-1

這會很容易。如果你不想渲染元素並且仍然沒有任何錯誤地通過,那麼試試這個。只要將一個if條件檢查未定義:

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

+0

這只是隱藏了問題,並沒有解決它... – Nova

0

這個工作對我來說,見下面的例子。

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

我有一個類似的問題,其中ViewChild是在被引用之前,這是不加載viewChild元素的switch條款內。我解決它在一個半哈克的方式,但包裹在一個setTimeout立即執行的ViewChild參考(即0毫秒)

4

如果你有一個ngIf包裝你可以使用一個設置爲@ViewChild()

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

中, setter將被調用undefined,然後再次使用一個引用ngIf允許它渲染。

我的問題是,雖然別的東西。我沒有在我的app.modules中包含包含我的「FilterTiles」的模塊。該模板沒有引發錯誤,但引用始終未定義。

+0

正是我需要的。謝謝! –