说我有一个@Component
使用@Attribute
装饰器
@Component(...)
export class ButtonComponent {
@Input()
disabled: boolean;
constructor(@Attribute('type') public type: ButtonType = 'default') {}
}
动态创建时如何正确设置其值?
const factory = this.factoryResolver.resolveComponentFactory(ButtonComponent);
仅通过设置其实例字段?
instance.type = 'primary';
那变化检测呢?创建后不检查@Attribute
。
答案 0 :(得分:2)
动态创建时如何正确设置其值?
你不能。
@Attribute()
装饰器是仅由AOT编译器使用的特殊功能。将模板编译为TypeScript时,组件的工厂功能(从父模板的角度来看)将属性值传递给组件构造函数。必须有一个父DOM元素,其中包含新子组件的DOM元素,并且从该元素读取属性。
由AOT编写的代码不同于用于动态创建组件的组件工厂,并且由于绕过了@Attribute()
值的依赖项注入器,因此没有数据绑定。在构造函数中使用属性的执行时间要快得多。我认为这是人们经常忽略的事情,很高兴知道。
您唯一可以做的就是在组件中同时支持属性和依赖注入器,并在构造函数中使用其中一个的值。
这是一个带有属性或DI值的按钮组件的示例。
export const MESSAGE_TOKEN: InjectionToken<string> = new InjectionToken<string>('MESSAGE_TOKEN');
@Component({
selector: 'app-button',
template: '<button>{{message}}</button>'
})
export class ButtonComponent {
public message: string;
public constructor(@Attribute('message') attrMessage: string,
@Inject(MESSAGE_TOKEN) @Optional() tokenMessage: string) {
this.message = attrMessage || tokenMessage;
}
}
您通常会在模板中以<app-button message="Hello World></app-button>
的形式使用它,但是必须动态创建它时,才使用MESSAGE_TOKEN
。
@Component({
selector: 'app-root',
template: ``
})
export class AppComponent implements OnInit {
public constructor(private _view: ViewContainerRef,
private _resolver: ComponentFactoryResolver,
private _injector: Injector) {
}
public ngOnInit(): void {
const button = this._resolver.resolveComponentFactory(ButtonComponent);
const injector = Injector.create({
providers: [
{provide: MESSAGE_TOKEN, useValue: 'Hello World'}
],
parent: this._injector
});
this._view.createComponent(button, undefined, injector);
}
}
当我第一次调查这个问题时。我假设@Attribute()
使用了依赖项注入器,并且是@Inject()
的替代方法,但是在查看Angular中的源代码时,我发现它已编码为如何呈现模板。这对我来说很有意义,因为这样可以减少性能开销。
现在,这是指Angular中的当前渲染引擎。我还没有时间查看Ivy的源代码,但是它如何处理该渲染中的属性似乎有所不同。我只是没有时间回顾它的这一部分。
如果其他人能弄清楚如何使@Attribute()
与组件工厂一起工作,请在评论中让我知道。
答案 1 :(得分:1)
我认为您需要结合使用@Input()
来进行更改检测和@HostBinding('attr.type')
来保持原生属性为最新状态
@Component({
selector: 'button[app-button]',
template: '<ng-content></ng-content><span> type: {{ type }}</span>',
styleUrls: ['./button.component.css'],
//inputs: ['type'],
host: {
//'[attr.type]': 'type'
}
})
export class ButtonComponent implements OnInit {
@Input()
@HostBinding('attr.type')
public type: string;
constructor(
@Attribute('type') type?: string
) {
// if created in template set initial
if (!!type) {
this.type = type;
}
}
ngOnInit() {
console.log(this)
}
}
然后
public ngOnInit(): void {
const buttonFactory = this.resolver.resolveComponentFactory(ButtonComponent);
const buttonRef = this.view.createComponent(buttonFactory, undefined, this.injector);
buttonRef.instance.type = 'submit';
}
在stackblitz中,您可以看到ngOnInit
,调试工具中的native元素的类型为Submit:
https://stackblitz.com/edit/angular-attribute-decorator-and-componentfactoryresolver