角动态组件和ExpressionChangedAfterItHasBeenCheckedError

时间:2018-04-30 22:39:21

标签: javascript angular

我正在尝试编写一个可以动态包含不同组件的组件。我的目标是能够写一篇文章,我可以写一个段落或添加一条推文。

这是DynamicArticleComponent的代码:

@Directive({
  selector: '[dynamic-query]'
})
export class QueryDirective {
  constructor(public viewContainerRef: ViewContainerRef) {}
}

@Component({
  selector: 'app-dynamic-article',
  template: 
  `<ng-container *ngFor="let element of elements">
      <ng-template dynamic-query></ng-template>
  </ng-container>`,
  styleUrls: ['dynamic-article.component.css']
})
export class DynamicArticleComponent implements AfterViewInit {

    @Input() elements: Element[];
    @ViewChildren(QueryDirective) queryDirectives;

    constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

    ngAfterViewInit() {
      this.queryDirectives.forEach((queryDirective: QueryDirective, index) => {
        const element = this.elements[index];
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(element.component);
        const containerRef = queryDirective.viewContainerRef;
        containerRef.clear();
        const newComponent = containerRef.createComponent(componentFactory);
        (<DynamicComponent>newComponent.instance).data = element.data; 
      });
    }
}

这些是上面代码中使用的其他类:

export class Element {
    constructor(public component: Type<any>, public data) {}
}

export interface DynamicComponent {
    data: any;
}

我在渲染<ng-templates>时遇到问题。它只是呈现注释,并且在视图加载后不会更改。这就是呈现的内容:enter image description here

元素正确地进入组件。我的想法是渲染所有模板,然后使用ViewChildren装饰器获取它们,并将元素渲染到它们应该的位置。这个问题有其他解决方案吗?

此外,这是元素到达DynamicArticleComponent

的方式

enter image description here

提前致谢。

1 个答案:

答案 0 :(得分:1)

好的,我的代码存在两个主要问题。第一个非常愚蠢。我没有将指令添加到app模块声明中,因此它就像任何其他html属性一样;棱角刚刚没想到它,所以它没有找到它。但是,在将其添加到app模块后,它会抛出ExpressionChangedAfterItHasBeenCheckedError。导致此错误的原因是我在视图加载后更改了变量。有关更深入的解释,请查看this blog post

总而言之,我所做的是将我在ngAfterViewInit内所做的事情提取到自己的函数中并从promise中调用它。这样做,是在同步代码完成执行后创建一个排队的微任务。要了解有关角度微观和宏观任务的更多信息,请查看以下文章:I reverse-engineered Zones (zone.js) and here is what I’ve found

以下是代码的结果:

@Directive({
  selector: '[dynamic-query]'
})
export class QueryDirective {
  constructor(public viewContainerRef: ViewContainerRef) {}
}

@Component({
  selector: 'app-dynamic-article',
  template: 
  `<ng-container *ngFor="let element of elements">
    <ng-template dynamic-query></ng-template>
  </ng-container>`,
  styleUrls: ['dynamic-article.component.css']
})
export class DynamicArticleComponent implements AfterViewInit {

    @Input() elements: Element[];
    @ViewChildren(QueryDirective) queryDirectives;

    constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

    ngAfterViewInit() {
        Promise.resolve(null).then(() => this.renderChildren());
    }

    private renderChildren() {
      this.queryDirectives.forEach((queryDirective: QueryDirective, index) => {
        const element = this.elements[index];
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(element.component);
        const containerRef = queryDirective.viewContainerRef;
        containerRef.clear();
        const newComponent = containerRef.createComponent(componentFactory);
        (<DynamicComponent>newComponent.instance).data = element.data; 
      });
    }
}

此代码完全有效。希望我帮助别人。