ViewChild returns 'undefined' - Angular2
ViewChild returns 'undefined' - Angular2
我正在尝试通过按父组件上的按钮来执行子组件的功能,但由于某种原因它未定义。
家长:
.. com1.html
<ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Title</h4>
</div>
<div class="modal-body" style="display: flex; justify-content: center;">
<app-com2 [var1]="value" #traceRef></app-com2>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="savePNG()"><i class="fa fa-download"></i>PNG</button>
<button type="button" class="btn btn-outline-dark" (click)="c()">Close</button>
</div>
</ng-template>
<button class="btn btn-sm btn-secondary" (click)="open(content)">Button</button>
...com1.ts
export class com1 implements OnInit {
@ViewChild('traceRef') traceRef;
value = "something";
constructor(private modalService: NgbModal) {}
savePNG() {
this.traceRef.savePNG();
}
open(content: any) {
this.modalService.open(content, { windowClass: 'temp-modal' });
}
}
任何人都可以指出我正确的方向,因为其他线程没有帮助:(
即使使用 ngAfterViewInit,它仍然未定义。
我认为您需要以下内容。将 ChildComponent 替换为您的 child class 名称
@ViewChild('traceRef')
traceRef:ChildComponent
我注意到您的 parent 模板是模态模板,您可以在此处使用模板变量打开它:((click)="open(content)"
通过模态服务。所以这意味着它会动态添加到 DOM。
所以这可能是一个时间问题,因为 parent 是动态加载的。
我会尝试这个,在 CHILD 的 ngAfterViewInit 中,使用 EventEmitter 触发一个事件。抓取 parent 中的事件,然后将标志设置为 true。那么理论上你可以在 parent:
savePNG() {
if (this.childLoaded) this.traceRef.savePNG();
}
您肯定知道 child 已加载,因此 savePNG 可用。但是,请参阅我的说明 - traceRef 仍将未定义。
因此您需要在 EventEmitter 中传递对 child 函数的引用,并使用它代替 #traceRef。
例如 - 在 child:
export class app-com2 implements blah blah {
@Output() childLoaded: EventEmitter<any> = new EventEmitter<any>();
savePNG() {
// code to save the image
}
ngAfterViewInit() {
this.childLoaded.emit(this.savePNG);
}
}
Parent:
..com1.html
<ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Title</h4>
</div>
<div class="modal-body" style="display: flex; justify-content: center;">
<app-com2 [var1]="value" (childLoaded)="handleChildLoaded($event)"></app-com2>
</div>
..com1.ts
handleChildLoaded(save: any) {
const result = save;
console.log(result);
}
Docs 在 child 到 parent 通讯上。
注意
再考虑一下 - 这是一个 lifecycle/timing 问题,因此您的代码将永远无法运行。这可能是为什么:
您的 parent 组件加载,触发所有生命周期挂钩(例如 ngOnInit 和 ngAfterViewInit 等)。 #content 得到处理并在 ngAfterViewInit 和更高版本的挂钩中可用。
您现在单击按钮加载#content(可用)。但它有 <app-com2>
组件,它由 #traceRef 模板变量引用。但是模板变量仅在 ngAfterViewInit 钩子触发之前处理 - 并且已经为 parent 触发,因此 #traceRef 到达太晚 Angular 无法连接它。因此它将永远是未定义的。
请参阅下面关于 (2) 的评论 - 不太正确,但我怀疑这是因为您加载模态的方式导致 #traceRef 始终未定义。
它无法像在 ng-template
的常见场景中那样工作的原因是 NgbModal
服务创建模板并且没有将其附加到父视图而是附加到根 ApplicationRef
const viewRef = content.createEmbeddedView(context);
this._applicationRef.attachView(viewRef);
这意味着您的查询对于您的组件将始终是干净的。
在一般情况下(见@ConnorsFan提供的example)我们使用vcRef.createEmbeddedView
负责在父视图中检查查询:
vcRef.createEmbeddedView
||
\/
function attachEmbeddedView(
...
Services.dirtyParentQueries(view); <========== set dirty for queries
我在这里看到的简单解决方案是直接在模板中传递或调用引用:
(click)="traceRef.savePNG()"
我正在尝试通过按父组件上的按钮来执行子组件的功能,但由于某种原因它未定义。
家长:
.. com1.html
<ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Title</h4>
</div>
<div class="modal-body" style="display: flex; justify-content: center;">
<app-com2 [var1]="value" #traceRef></app-com2>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="savePNG()"><i class="fa fa-download"></i>PNG</button>
<button type="button" class="btn btn-outline-dark" (click)="c()">Close</button>
</div>
</ng-template>
<button class="btn btn-sm btn-secondary" (click)="open(content)">Button</button>
...com1.ts
export class com1 implements OnInit {
@ViewChild('traceRef') traceRef;
value = "something";
constructor(private modalService: NgbModal) {}
savePNG() {
this.traceRef.savePNG();
}
open(content: any) {
this.modalService.open(content, { windowClass: 'temp-modal' });
}
}
任何人都可以指出我正确的方向,因为其他线程没有帮助:( 即使使用 ngAfterViewInit,它仍然未定义。
我认为您需要以下内容。将 ChildComponent 替换为您的 child class 名称
@ViewChild('traceRef')
traceRef:ChildComponent
我注意到您的 parent 模板是模态模板,您可以在此处使用模板变量打开它:((click)="open(content)"
通过模态服务。所以这意味着它会动态添加到 DOM。
所以这可能是一个时间问题,因为 parent 是动态加载的。
我会尝试这个,在 CHILD 的 ngAfterViewInit 中,使用 EventEmitter 触发一个事件。抓取 parent 中的事件,然后将标志设置为 true。那么理论上你可以在 parent:
savePNG() {
if (this.childLoaded) this.traceRef.savePNG();
}
您肯定知道 child 已加载,因此 savePNG 可用。但是,请参阅我的说明 - traceRef 仍将未定义。
因此您需要在 EventEmitter 中传递对 child 函数的引用,并使用它代替 #traceRef。
例如 - 在 child:
export class app-com2 implements blah blah {
@Output() childLoaded: EventEmitter<any> = new EventEmitter<any>();
savePNG() {
// code to save the image
}
ngAfterViewInit() {
this.childLoaded.emit(this.savePNG);
}
}
Parent:
..com1.html
<ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Title</h4>
</div>
<div class="modal-body" style="display: flex; justify-content: center;">
<app-com2 [var1]="value" (childLoaded)="handleChildLoaded($event)"></app-com2>
</div>
..com1.ts
handleChildLoaded(save: any) {
const result = save;
console.log(result);
}
Docs 在 child 到 parent 通讯上。
注意
再考虑一下 - 这是一个 lifecycle/timing 问题,因此您的代码将永远无法运行。这可能是为什么:
您的 parent 组件加载,触发所有生命周期挂钩(例如 ngOnInit 和 ngAfterViewInit 等)。 #content 得到处理并在 ngAfterViewInit 和更高版本的挂钩中可用。
您现在单击按钮加载#content(可用)。但它有
<app-com2>
组件,它由 #traceRef 模板变量引用。但是模板变量仅在 ngAfterViewInit 钩子触发之前处理 - 并且已经为 parent 触发,因此 #traceRef 到达太晚 Angular 无法连接它。因此它将永远是未定义的。
请参阅下面关于 (2) 的评论 - 不太正确,但我怀疑这是因为您加载模态的方式导致 #traceRef 始终未定义。
它无法像在 ng-template
的常见场景中那样工作的原因是 NgbModal
服务创建模板并且没有将其附加到父视图而是附加到根 ApplicationRef
const viewRef = content.createEmbeddedView(context);
this._applicationRef.attachView(viewRef);
这意味着您的查询对于您的组件将始终是干净的。
在一般情况下(见@ConnorsFan提供的example)我们使用vcRef.createEmbeddedView
负责在父视图中检查查询:
vcRef.createEmbeddedView
||
\/
function attachEmbeddedView(
...
Services.dirtyParentQueries(view); <========== set dirty for queries
我在这里看到的简单解决方案是直接在模板中传递或调用引用:
(click)="traceRef.savePNG()"