简体   繁体   English

角动态组件和ExpressionChangedAfterItHasBeenCheckedError

[英]Angular Dynamic Components and ExpressionChangedAfterItHasBeenCheckedError

I'm trying to write a component that could contain different components dynamically. 我正在尝试编写一个可以动态包含不同组件的组件。 My goal is to be able to write an article where I could either write a paragraph or add a tweet. 我的目标是能够写一篇文章,在其中我可以写一个段落或添加一条推文。

This is the code for DynamicArticleComponent : 这是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; 
      });
    }
}

These are other classes used in the code above: 这些是上面的代码中使用的其他类:

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

export interface DynamicComponent {
    data: any;
}

I'm having trouble rendering the <ng-templates> . 我在渲染<ng-templates>遇到问题。 It just renders comments and it doesn't change after the view loads. 它仅呈现注释,并且在视图加载后不会更改。 This is what's rendered: 这是呈现的内容: 在此处输入图片说明

The elements are getting to the component correctly. 元素正确到达组件。 My idea is to render all the templates, then get them with the ViewChildren decorator, and render the elements where they are supposed to be. 我的想法是呈现所有模板,然后使用ViewChildren装饰器将其获取,然后将元素呈现在应有的位置。 Is there other solution to this problem? 还有其他解决方案吗?

Also, this is how the elements reach the DynamicArticleComponent : 同样,这也是元素到达DynamicArticleComponent

在此处输入图片说明

Thanks in advance. 提前致谢。

Ok, there were two main problems with my code. 好的,我的代码有两个主要问题。 The first one was pretty dumb. 第一个很傻。 I didn't add the directive to the app module declarations, so it was just like any other html property; 我没有将指令添加到应用程序模块的声明中,因此就像其他任何html属性一样; angular just didn't expect it, so it didn't look for it. 有角只是没想到它,所以它没有寻找它。 However, after adding it to the app module, it threw ExpressionChangedAfterItHasBeenCheckedError . 但是,将其添加到应用程序模块后,它抛出了ExpressionChangedAfterItHasBeenCheckedError This error is caused because I'm changing variables after the view has load. 导致此错误的原因是,视图加载后我正在更改变量。 For a more in depth explanation look this blog post . 有关更深入的解释,请参阅此博客文章

In summary, what I did was extracting what I was doing inside the ngAfterViewInit into its own function and calling it from a promise. 总而言之,我所做的就是将ngAfterViewInit内部的工作ngAfterViewInit到其自己的函数中,并从promise中调用它。 What this does, is creates a microtask queued after the syncronous code has fininshed executing. 这是在同步代码完成执行后创建一个排队的微任务。 To learn more about micro and macro tasks in angular, look at this post: I reverse-engineered Zones (zone.js) and here is what I've found . 要了解有关角度的微观和宏观任务的更多信息,请查看这篇文章: 我对Zones(zone.js)进行了反向工程,这是我发现的内容

Here is how the code ended up: 下面是代码的最终结果:

@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; 
      });
    }
}

This code totally works. 此代码完全有效。 Hopefully I help someone. 希望我能帮助某人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM