我有以下 Angular2“伪”代码,我想让用户类尽可能简单:
// ###### LIB CLASSES ######
@Component()
export abstract class Parent {
abstract childProvidedFactory;
factory_config;
ngOnInit() {
this.dataSource = new FactoryUser(this.childProvidedFactory, this.factory_config);
}
// this.dataSource initialized and may be used further
}
export class FactoryUser {
constructor (childProvidedFactory, factory_config) {
}
// ...
}
// ###### USER CLASS ######
@Component()
export class Child extends Parent {
@Input() config;
childProvidedFactory = () => {
// implementation
}
ngOnInit() {
this.factory_config = fnc(this.config);
super.ngOnInit();
}
}
我希望编写
Child
的用户不需要调用 super.ngOnInit()
。所以想法是将 Parent
的 ngOnInit
内容移动到构造函数。然而,在 constructor
执行时,所需的 childProvidedFactory
和 factory_config
尚不可用。因此我用 setTimeout
: 包装代码
export abstract class Parent {
abstract childProvidedFactory;
factory_config;
constructor() {
setTimeout(() => {
this.dataSource = new FactoryUser(this.childProvidedFactory, this.factory_config);
}, 0)
}
// this.dataSource initialized and may be used further
}
现在我可以从
super.ngOnInit()
类中删除 Child
。
在 Chromium 上,当使用
console.log
s 进行检测时,我得到:
Inside child's ngOnInit()
Inside child's ngAfterViewInit()
Inside parent's setTimeout() within constructor()
是否确保
setTimeout
的构造函数中的Parent
将在Child
的ngOnInit
之后分别在Child
的ngAfterViewInit
生命周期挂钩之后执行?如果是这样,您能解释一下原因吗?生命周期钩子是否在事件循环中与 setTimeout 相关“安排”?
代码将仅在主要 Web 引擎(WebKit、Blink、Gecko)和 Angular >= 17 上运行。
您还发现其他潜在问题吗?
重点是避免在 Child 中调用 super.ngOnInit() 。所以还有其他潜在的解决方案:
在 this 解决方案中,我不喜欢 Child 需要使用特殊名称而不是 ngOnInit。
最接近我的问题是this,但是构造函数没有添加到混合中。
我考虑使用一次性初始化,并在构造函数中调用 afterNextRender() 。从技术上讲它是有效的,但是由于在回调 fn 中初始化的变量在 Childs 模板中使用,因此出现运行时错误
Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError
。因此,它实际上获得了一个值,而不是 undefined
。使用此选项,需要将 ChangeDetectorRef
注入到抽象类的构造函数中,因此,如果我没有记错的话,Child
实际上需要将其传递给该构造函数。这又会不必要地使 Child
的代码变得复杂。
你尝试的方法确实不可靠,会导致难以调试的时序问题。
要解决这个问题,您可以使用装饰器,它将调用父级
ngOnInit
,它必须是不同的名称,因为我们无法在构造函数内访问 super。
export function CallParentHook() {
return function (constructor: any) {
const original = constructor.prototype.ngOnInit;
constructor.prototype.ngOnInit = function () {
this.parentNgOnInit();
if (original && typeof original === 'function') {
original.apply(this, arguments);
}
};
};
}
@Component({
selector: 'app-child',
standalone: true,
imports: [JsonPipe],
template: `{{ dataSource | json }}`,
})
@CallParentHook()
export class Child extends Parent {
@Input() override config: any;
childProvidedFactory = () => {};
ngOnInit() {
console.log(this.dataSource);
}
}
import { JsonPipe } from '@angular/common';
import { Component, Directive, Input } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
export const fnc = (data: any) => data;
export class FactoryUser {
config: any;
constructor(childProvidedFactory: any, factory_config: any) {
this.config = factory_config;
}
// ...
}
export function CallParentHook() {
return function (constructor: any) {
const original = constructor.prototype.ngOnInit;
constructor.prototype.ngOnInit = function () {
this.parentNgOnInit();
if (original && typeof original === 'function') {
original.apply(this, arguments);
}
};
};
}
@Directive()
export abstract class Parent {
@Input() config: any;
abstract childProvidedFactory: any;
dataSource: any;
parentNgOnInit() {
this.dataSource = new FactoryUser(this.childProvidedFactory, this.config);
}
}
@Component({
selector: 'app-child',
standalone: true,
imports: [JsonPipe],
template: `{{ dataSource | json }}`,
})
@CallParentHook()
export class Child extends Parent {
@Input() override config: any;
childProvidedFactory = () => {};
ngOnInit() {
console.log(this.dataSource);
}
}
@Component({
selector: 'app-root',
imports: [Child],
standalone: true,
template: `
<app-child [config]="config"/>
asd
`,
})
export class App {
config = { name: 'Angular' };
}
bootstrapApplication(App);