[英]Angular 2 @ViewChild annotation returns undefined
我正在嘗試學習 Angular 2。
我想使用@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(){};
}
最后是模板(如評論中所建議):
正文內容.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標記中(確實我能夠看到標題)。
注意:使用 DynamicComponetLoader 將BodyContent
類注入到另一個模板 (Body) 中DynamicComponetLoader: dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector)
:
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" 數組中推送一些東西時,我得到一個異常: 'no property tiles for "undefined"' 。
還有一件事: FilterTiles
組件似乎已正確加載,因為我能夠看到它的 html 模板。
有什么建議么?
我有一個類似的問題,並認為我會發布以防其他人犯同樣的錯誤。 首先,要考慮的一件事是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
}
}
希望有幫助。
編輯
正如下面@Ashg所提到的,一種解決方案是使用@ViewChildren
而不是@ViewChild
。
前面提到的問題是導致視圖未定義的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
的孩子。 希望這可以幫助。
您可以為@ViewChild()
使用 setter
@ViewChild(FilterTiles) set ft(tiles: FilterTiles) {
console.log(tiles);
};
如果你有一個 ngIf 包裝器,setter 將使用 undefined 調用,然后在 ngIf 允許它渲染時再次使用引用。
我的問題是別的東西。 我沒有在我的 app.modules 中包含包含我的“FilterTiles”的模塊。 模板沒有拋出錯誤,但引用始終未定義。
解決我的問題是確保將static
設置為false
。
@ViewChild(ClrForm, {static: false}) clrForm;
關閉static
后, @ViewChild
引用會在*ngIf
指令更改時由 Angular 更新。
這對我有用。
例如,我的組件名為 'my-component' 使用 *ngIf="showMe" 顯示,如下所示:
<my-component [showMe]="showMe" *ngIf="showMe"></my-component>
因此,當組件初始化時,組件在“showMe”為真之前不會顯示。 因此,我的 @ViewChild 引用都是未定義的。
這是我使用@ViewChildren 和它返回的QueryList 的地方。 請參閱有關 QueryList 的 Angular 文章和 @ViewChildren 使用演示。
您可以使用 @ViewChildren 返回的 QueryList 並使用 rxjs 訂閱對引用項目的任何更改,如下所示。 @ViewChild 沒有這個能力。
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
希望這可以幫助某人節省一些時間和挫敗感。
我對此的解決方案是將*ngIf
替換為[hidden]
。 缺點是所有子組件都存在於代碼 DOM 中。 但為我的要求工作。
我的解決方法是使用[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";
}
}
....
就我而言,我有一個使用ViewChild
的輸入變量設置器,並且ViewChild
位於*ngIf
指令內,因此設置器試圖在*ngIf
呈現之前訪問它(如果沒有*ngIf
它將正常工作,但會如果它始終使用*ngIf="true"
設置為 true,則不起作用)。
為了解決這個問題,我使用 Rxjs 確保對ViewChild
的任何引用都等到視圖啟動。 首先,創建一個在視圖初始化后完成的主題。
export class MyComponent implements AfterViewInit {
private _viewInitWaiter$ = new Subject();
ngAfterViewInit(): void {
this._viewInitWaiter$.complete();
}
}
然后,創建一個函數,在主題完成后接受並執行一個 lambda。
private _executeAfterViewInit(func: () => any): any {
this._viewInitWaiter$.subscribe(null, null, () => {
return func();
})
}
最后,確保對 ViewChild 的引用使用此函數。
@Input()
set myInput(val: any) {
this._executeAfterViewInit(() => {
const viewChildProperty = this.viewChild.someProperty;
...
});
}
@ViewChild('viewChildRefName', {read: MyViewChildComponent}) viewChild: MyViewChildComponent;
它必須工作。
但正如Günter Zöchbauer所說,模板中肯定還有其他問題。 我創建了有點Relevant-Plunkr-Answer 。 請檢查瀏覽器的控制台。
引導.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");
}
}
過濾器Tiles.ts
@Component({
selector: 'filter',
template: '<div> <h4>Filter tiles </h4></div>'
})
export class FilterTiles {
public tiles = [];
public constructor(){};
}
它就像一個魅力。 請仔細檢查您的標簽和參考資料。
謝謝...
對我來說,使用ngAfterViewInit
而不是ngOnInit
解決了這個問題:
export class AppComponent implements OnInit {
@ViewChild('video') video;
ngOnInit(){
// <-- in here video is undefined
}
public ngAfterViewInit()
{
console.log(this.video.nativeElement) // <-- you can access it here
}
}
我對此的解決方案是將 ngIf 從子組件外部移動到包含整個 html 部分的 div 上的子組件內部。 這樣它仍然在需要時被隱藏,但能夠加載組件並且我可以在父級中引用它。
這對我有用,請參見下面的示例。
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(); }); } } }
我有一個類似的問題,其中ViewChild
位於switch
子句中,該子句在被引用之前沒有加載 viewChild 元素。 我以一種半駭人聽聞的方式解決了它,但將ViewChild
引用包裝在立即執行的setTimeout
中(即 0 毫秒)
一種通用方法:
您可以創建一個等待ViewChild
准備好的方法
function waitWhileViewChildIsReady(parent: any, viewChildName: string, refreshRateSec: number = 50, maxWaitTime: number = 3000): Observable<any> {
return interval(refreshRateSec)
.pipe(
takeWhile(() => !isDefined(parent[viewChildName])),
filter(x => x === undefined),
takeUntil(timer(maxWaitTime)),
endWith(parent[viewChildName]),
flatMap(v => {
if (!parent[viewChildName]) throw new Error(`ViewChild "${viewChildName}" is never ready`);
return of(!parent[viewChildName]);
})
);
}
function isDefined<T>(value: T | undefined | null): value is T {
return <T>value !== undefined && <T>value !== null;
}
用法:
// Now you can do it in any place of your code
waitWhileViewChildIsReady(this, 'yourViewChildName').subscribe(() =>{
// your logic here
})
如果 *ngIf="show" 阻止了 ViewChild 的呈現,並且您在show
變為 true 后立即需要 ViewChild,它可以幫助我在設置show
true 后立即觸發 ChangeDetectorRef.detectChanges()。
之后 *ngIf 創建組件並呈現 ViewChild,您可以在之后使用它。 只需鍵入一個快速示例代碼。
@ViewChild(MatSort) sort: MatSort;
constructor(private cdRef: ChangeDetectorRef) {}
ngOnInit() {
this.show = false;
this.someObservable()
.pipe(
tap(() => {
this.show = true;
this.cdRef.detectChanges();
})
)
.subscribe({
next: (data) => {
console.log(sort)
this.useResult(data);
}
});
}
這是不好的,還是為什么沒有人提出呢?
使用 [hidden] 而不是 *ngif 因為 *ngif 在條件不滿足時會終止您的代碼。
<div [hidden]="YourVariable">
Show Something
</div>
只需將 {static: true} 添加到 @View 即可解決我的問題。
@ViewChild(FilterTiles, { static : true }) ft: FilterTiles;
我修復它只是在設置可見組件后添加 SetTimeout
我的 HTML:
<input #txtBus *ngIf[show]>
我的組件 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);
}
}
就我而言,我知道子組件將始終存在,但想在子組件初始化之前更改狀態以節省工作。
我選擇測試子組件,直到它出現並立即進行更改,這為我節省了子組件的更改周期。
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));
}
}
Alertnatively,您可以添加最大嘗試次數或向setTimeout
添加幾毫秒的延遲。 setTimeout
有效地將函數拋出到掛起操作列表的底部。
對我來說,問題是我引用了元素上的 ID。
@ViewChild('survey-form') slides:IonSlides;
<div id="survey-form"></div>
而不是這樣:
@ViewChild('surveyForm') slides:IonSlides;
<div #surveyForm></div>
如果您使用的是 Ionic,則需要使用ionViewDidEnter()
生命周期掛鈎。 Ionic 運行一些額外的東西(主要是與動畫相關的),這通常會導致像這樣的意外錯誤,因此需要在ngOnInit
、 ngAfterContentInit
等之后運行的東西。
對於 Angular:在 HTML 中使用顯示樣式“block”或“none”更改 *ngIf。
selector: 'app',
template: `
<controls [style.display]="controlsOn ? 'block' : 'none'"></controls>
<slideshow (mousemove)="onMouseMove()"></slideshow>
`,
directives: [SlideshowComponent, ControlsComponent]
這是對我有用的東西。
@ViewChild('mapSearch', { read: ElementRef }) mapInput: ElementRef;
ngAfterViewInit() {
interval(1000).pipe(
switchMap(() => of(this.mapInput)),
filter(response => response instanceof ElementRef),
take(1))
.subscribe((input: ElementRef) => {
//do stuff
});
}
所以我基本上每秒設置一次檢查,直到*ngIf
變為真,然后我做與ElementRef
相關的事情。
我有一個類似的問題,其中ViewChild
位於有條件( *ngIf
)呈現的組件內。 這將在 api 調用的響應中呈現。 響應遲於執行@ViewChild
裝飾器時出現,因此所需的組件引用保持未定義(null)。 使用{static: false}
后,即使在一些(少量)時間后可以看到所需的組件, @ViewChild
裝飾器也不會再次觸發。 這違背了 Angular 的“承諾”😢(如本線程中的其他答案所述)
原因是ChangeDetectionStrategy
設置為OnPush
😧。 當將此更改為ChangeDetectionStrategy.Default
時,一切都按預期工作。
結論:
{ static: false }
&ChangeDetectionStrategy.Default
對於有條件 (*ngIf) 渲染的@ViewChild
組件,以“稍后”獲取它們的引用(當它們被渲染時)
我借助更改檢測以及延遲初始化視圖容器引用解決了這個問題。
HTML 設置:
<ng-container *ngIf="renderMode === 'modal'" [ngTemplateOutlet]="renderModal">
</ng-container>
<ng-container *ngIf="renderMode === 'alert'" [ngTemplateOutlet]="renderAlert">
</ng-container>
<ng-template #renderModal>
<div class="modal">
<ng-container appSelector></ng-container>
</div>
</ng-template>
<ng-template #renderAlert>
<div class="alert">
<ng-container appSelector></ng-container>
</div>
</ng-template>
零件:
@ViewChild(SelectorDirective, { static: true }) containerSelector!: SelectorDirective;
constructor(private cdr: ChangeDetectorRef) { }
ngOnInit(): void {
// step: 1
this.renderMode = someService.someMethod();
// step: 2
this.cdr.markForCheck();
// step: 3
const viewContainerRef = this.containerSelector?.viewContainerRef;
if (viewContainerRef) {
// logic...
}
}
*ngIf
) 應該首先更新ChangeDetection
ViewChild
獲取引用並繼續進行邏輯。除了其他答案之外,您還可以使用最后一個生命周期掛鈎:
ngAfterViewChecked() {}
即使在ngAfterViewChecked
之后也會調用ngAfterViewInit
生命周期鈎子: https ://angular.io/guide/lifecycle-hooks#lifecycle-event-sequence
對我有用的解決方案是在 app.module.ts 的聲明中添加指令
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.