我有以下模板:
@Component({
standalone: true,
imports: [DynamicFormComponent, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgTemplateOutlet],
template: `
<ng-container [ngSwitch]="method">
<ng-container *ngSwitchCase="file">
<ng-container *ngIf="!isFileUploaded; else form">
...some html...
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="manual">
<ng-container *ngTemplateOutlet="form"></ng-container>
</ng-container>
</ng-container>
<ng-template #form>
<app-dynamic-form></app-dynamic-form>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParentComponent implements OnInit, AfterViewInit {
@ViewChild(DynamicFormComponent, { static: false })
public form!: DynamicFormComponent;
private createForm(): void {
// method to run after DynamicFormComponent render...
const fields: any[] = this.createFields();
this.form.setFields(fields);
}
createFields(): any[] {
return [/*fields...*/]
}
}
我想使用其内部
<app-dynamic-form>
方法初始化 setFields()
内部的变量。
export class DynamicFormComponent {
public fields: any[];
public setFields(val: any[]): void {
if (val) {
this.fields = val;
}
}
}
当此组件通过
ng-template
渲染的时间晚于初始化父组件时,就会出现问题。我尝试在 ngAfterViewInit()
中执行此操作,但没有成功,我还尝试在 ngOnInit()
中发出事件,但这也不起作用。
您可以从 DynamicFormComponent 在 init 上发出事件
export class DynamicFormComponent {
public fields: any[];
@Output() onInit: EventEmitter<boolean> = new EventEmitter<boolean>();
ngOnInit() {
this.onInit.emit(true);
}
public setFields(val: any[]): void {
if (val) {
this.fields = val;
}
}
}
现在使用
绑定创建表单方法<app-dynamic-form (onInit)="createForm()" ></app-dynamic-form>
有两种方法可以做到这一点:
app-dynamic-form
组件中使用输出。在此解决方案中需要记住两件事:ExpressionChangedAfterItHasBeenCheckedError
。发生这种情况是因为 Angular 执行从父级到子级的单向更改检测流,并且如果您在此更改检测期间同步发出,那么父级中的状态可能会发生变化(取决于您要在处理程序中执行的操作),但由于 Angular 已经检查了父级并且这样做可能会导致重新渲染时出现不一致的情况。ngAfterViewChecked
这样的钩子的输出处理程序,以便捕获 viewchild。实际上使用异步输出可以解决这两件事:
应用程序动态形式:
@Output() init = new EventEmitter<void>(true); // true defines that eventemitter should trigger asynchronously in the next event loop tick
ngOnInit(): void {
this.init.emit();
}
之后,您应该能够在父级中捕获 init 并在
ViewChild
属性中定义它。
其他方式有点棘手,但只是为了让你知道另一种选择,不需要添加输出等,但需要通过钩子处理viewchild的变化
ngAfterViewChecked
:
家长:
@ViewChild(DynamicFormComponent, { static: false })
public form!: DynamicFormComponent;
private childInited = false;
ngAfterViewChecked(): void {
if(!this.childInited && this.form) {
setTimeout(() => this.onDynamicFormInited());
}
else if(!this.form) { // handle the case when there was form but then hid by ngIf
this.childInited = false;
}
}
private onDynamicFormInited(): void {
...
}
注意:如果添加了状态(模板中使用的内容)更改,请使用 setTimeout 来捕获更改检测期间表达式更改的异常
而且由于你的组件是OnPush,所以需要在那里添加markForCheck,否则setTimeout状态变化将不会被重新渲染