在通过ViewContainerRef创建的OnPush组件更改后,角度视图未更新

Spi*_*iff 2 angular

使用Angular 4.3和以下的Plunkr.

请考虑以下组件:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <button type="button" (click)="toggle()">Toggle</button>
      <div #anchor></div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class App {
  @ViewChild('anchor', {read: ViewContainerRef}) anchor: ViewContainerRef;
  dynamicRef: ComponentRef;
  value = true;

  constructor(private cfr: ComponentFactoryResolver, private cdr: ChangeDetectorRef) {}

  ngAfterViewInit(): void {
    let factory = this.cfr.resolveComponentFactory(Dynamic);
    this.dynamicRef = this.anchor.createComponent(factory);
    this.dynamicRef.instance.value = this.value;
    this.dynamicRef.changeDetectorRef.detectChanges();
  }

  toggle(): void {
    this.value = !this.value;
    this.dynamicRef.instance.value = this.value;
    this.dynamicRef.changeDetectorRef.detectChanges();
  }
}

@Component({
  template: `Value: {{value}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class Dynamic {
  @Input() value = true;
}
Run Code Online (Sandbox Code Playgroud)

App组件Dynamic使用标准ComponentFactoryResolver+ ViewContainerRef策略创建组件.两个组件都有OnPush变更检测策略.App.toggle()调用该方法时,它会切换App.value,传播此新值Dynamic.value并强制在动态组件的更改检测器上运行更改检测.我希望动态组件的模板能够显示正确的值,但实际上它永远不会改变.将两个组件切换到Default更改检测策略可提供预期的行为.

为什么动态组件模板不能正确重新渲染,如何修复?

Max*_*kyi 6

每个动态创建的组件都有一个主机视图.所以你有以下层次结构:

AppComponentView
   DynamicComponentHostView
       DynamicComponentView
Run Code Online (Sandbox Code Playgroud)

当你这样做时:

this.dynamicRef.changeDetectorRef
Run Code Online (Sandbox Code Playgroud)

你得到changeDetectorRefDynamicComponentHostView.当你运行时,detectChanges你运行变更检测DynamicComponentHostView,而不是DynamicComponentView.

然后,如果设置OnPush策略,则父组件将更新子组件的绑定,并决定是否对子组件运行更改检测.但是,重要的是,在模板编译期间定义输入绑定.动态组件不是这种情况.因此,如果要使用OnPush,则必须在此组件上手动触发更改检测.要做到这一点,你需要获得变化检测器DynamicComponentView.你可以这样做:

export class Dynamic {
  @Input() value = true;

  constructor(public cd: ChangeDetectorRef) {

  }
Run Code Online (Sandbox Code Playgroud)

然后触发更改检测,如下所示:

this.dynamicRef.instance.cd.detectChanges();
Run Code Online (Sandbox Code Playgroud)

这是掠夺者.

有关更改检测的更多信息,请阅读有关Angular中的更改检测的所有信息.

  • 神奇的答案,清晰而详尽。谢谢! (2认同)
  • @Spiff,不客气。我在 [AngularinDepth.com](https://blog.AngularinDepth.com/) 中写了很多关于此类的内容。您可能想关注该出版物。祝你好运 (2认同)