角@Attribute装饰器和ComponentFactoryResolver

时间:2019-05-14 15:11:32

标签: angular

说我有一个@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

2 个答案:

答案 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