我正在尝试学习 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 标记中(确实我能够看到标题)。
注意:BodyContent
类使用 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对于“未定义”'。
还有一件事:FilterTiles
组件似乎已正确加载,因为我能够看到它的 html 模板。
有什么建议么?
ft
不会在构造函数中设置,但会在点击事件处理程序中设置。
loadAsRoot
。一旦我用 loadIntoLocation
替换,问题就解决了。如果您将评论作为答案,我可以将其标记为已接受
我有一个类似的问题,并认为我会发布以防其他人犯同样的错误。首先,要考虑的一件事是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 below 所述,一种解决方案是使用 @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
的子代。希望这可以帮助。
this.SearchGrid
属性,您应该使用类似 setTimeout(()=>{ ///your code here }, 1);
的语法来避免异常:检查后表达式已更改
*ngIf
有效,并且在渲染之后我们可以从动态组件中保存 ElementRef。
您可以为 @ViewChild()
使用 setter
@ViewChild(FilterTiles) set ft(tiles: FilterTiles) {
console.log(tiles);
};
如果你有一个 ngIf 包装器,setter 将使用 undefined 调用,然后在 ngIf 允许它渲染时再次使用引用。
我的问题是别的东西。我没有在我的 app.modules 中包含包含我的“FilterTiles”的模块。模板没有抛出错误,但引用始终未定义。
FilterTiles
。由于这个原因,我以前遇到过这个问题。
@ViewChild('paginator', {static: false})
等注释
解决我的问题的方法是确保将 static
设置为 false
。
@ViewChild(ClrForm, {static: false}) clrForm;
关闭 static
后,当 *ngIf
指令更改时,Angular 会更新 @ViewChild
引用。
{static:false}
是默认值(从 Angular9 iirc 开始)
这对我有用。
例如,我的组件名为 'my-component' 使用 *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 订阅对引用项目的任何更改,如下所示。 @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
希望这可以帮助某人节省一些时间和挫败感。
.take(1).subscribe()
,但是很好的答案,非常感谢!
我对此的解决方案是将 *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 上的子组件内部。这样它仍然在需要时被隐藏,但能够加载组件并且我可以在父级中引用它。
这对我有用,请参见下面的示例。
从 'angular2/core' 导入 {Component, ViewChild, ElementRef}; @Component({ selector: 'app', template: ` Toggle
我有一个类似的问题,其中 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));
}
}
或者,您可以添加最大尝试次数或向 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
组件(当它们被渲染时)
ChangeDetectionStrategy.OnPush
非常高效。如果这是使用的默认策略,那么在您之前编写代码的作者一定是经过深思熟虑的。 {static: false}
也是可用的默认选项。如果将其设置为 true
,则逻辑必须在 oninit
内执行,因此这是必要的。这个问题可能已经通过手动触发更改检测来解决。关于变更检测的有用文章:mokkapps.de/blog/…
我借助更改检测以及延迟初始化视图容器引用解决了这个问题。
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...
}
}
修改了代码,使得 HTML 依赖的条件 (*ngIf) 应该首先更新一旦条件更新,手动触发 ChangeDetection 在手动 cdr 触发后从 ViewChild 获取引用并继续进行逻辑。
除了其他答案之外,您还可以使用最后一个生命周期挂钩:
ngAfterViewChecked() {}
即使在 ngAfterViewInit
之后也会调用 ngAfterViewChecked
生命周期挂钩:https://angular.io/guide/lifecycle-hooks#lifecycle-event-sequence
对我有用的解决方案是在 app.module.ts 的声明中添加指令
不定期副业成功案例分享
ngClass
+hidden
类而不是ngIf
。那行得通。谢谢!