【问题标题】:Angular2: Insert a dynamic component as child of a container in the DOMAngular2:将动态组件作为容器的子元素插入 DOM
【发布时间】:2016-10-31 19:33:36
【问题描述】:

有没有办法在 Angular 2 中动态插入一个组件作为 DOM 标签的 child(不是兄弟)?

有很多例子可以插入动态组件作为给定ViewContainerRef 标记的兄弟,例如(从 RC3 开始):

@Component({
  selector: '...',
  template: '<div #placeholder></div>'
})
export class SomeComponent {
  @ViewChild('placeholder', {read: ViewContainerRef}) placeholder;

  constructor(private componentResolver: ComponentResolver) {}

  ngAfterViewInit() {
    this.componentResolver.resolveComponent(MyDynamicComponent).then((factory) => {
        this.componentRef = this.placeholder.createComponent(factory);
    });
  }
}

但这会生成一个类似于:

<div></div>
<my-dynamic-component></my-dynamic-component>

预期结果:

<div>
    <my-dynamic-component></my-dynamic-component>
</div>

使用SomeComponentViewContainerRef 有相同的结果,它仍然将生成的组件作为兄弟插入,而不是子组件。如果模板为空并且在模板中插入动态组件(在组件选择器标记内),我可以接受。

在使用 ng2-dragula 等库从动态组件列表和benefit from the model updates 中拖动时,DOM 结构非常重要。额外的 div 在可拖动元素列表中,但在模型之外,破坏了拖放逻辑。

有人说这是不可能的 (c.f. this comment),但这听起来像是一个非常令人惊讶的限制。

【问题讨论】:

  • 感谢您的快速回答和有用的链接! @pkozlowski 在这个讨论中给出了我的问题的答案:使用 &lt;template #placeholder&gt;&lt;/template&gt; 而不是 &lt;div #placeholder&gt;&lt;/div&gt;
  • 您是否有适用于指令的解决方案?因为这是组件

标签: angular


【解决方案1】:

TL;DR:将 &lt;div #placeholder&gt;&lt;/div&gt; 替换为 &lt;div&gt;&lt;ng-template #placeholder&gt;&lt;/ng-template&gt;&lt;/div&gt; 以插入到 div 中。

这里是working stackblitz example(Angular 6),以及相关代码:

@Component({
  selector: 'my-app',
  template: `<div><ng-template #container></ng-template></div>`
})
export class AppComponent implements OnInit {

    @ViewChild('container', {read: ViewContainerRef}) viewContainer: ViewContainerRef;

    constructor(private compiler: Compiler) {}

    ngOnInit() {
      this.createComponentFactory(MyDynamicComponent).then(
        (factory: ComponentFactory<MyDynamicComponent>) => this.viewContainer.createComponent(factory),
        (err: any) => console.error(err));
    }

    private createComponentFactory(/*...*/) {/*...*/}

}

似乎&lt;ng-container #placeholder&gt;&lt;/ng-container&gt; 也在工作(将ng-template 替换为ng-container)。我喜欢这种方法,因为&lt;ng-container&gt; 显然是针对这个用例(一个不添加标签的容器),并且可以在其他情况下使用,例如NgIf,而无需包装在真正的标签中。


PS:@GünterZöchbauer 在评论中指导我进行正确的讨论,我终于回答了我自己的问题。


编辑 [2018-05-30]:更新为 stackblitz 链接以提供一个有效的最新示例。

【讨论】:

  • 占位符是未知的,是动态的。是否可以动态创建 ViewChild?
  • @Karthick 抱歉耽搁了。我相信有可能获得一个完全动态的 ViewChild,但我不知道该怎么做。 ViewChild 有不同的方法来识别要定位的组件。这可能是一个单独的问题:)
  • 但是动态组件的位置应该是
【解决方案2】:

我正在寻找相同问题的解决方案,但批准的答案对我不起作用。 但是我找到了更好和更合乎逻辑的解决方案,如何将动态创建的组件作为子元素附加到当前宿主元素。

想法是使用新创建的组件的元素引用,并通过渲染服务将其附加到当前元素引用。 我们可以通过它的注入器属性来获取组件对象。

代码如下:

@Directive({
    selector: '[mydirective]'
})
export class MyDirectiveDirective {
    constructor(
        private cfResolver: ComponentFactoryResolver,
        public vcRef: ViewContainerRef,
        private renderer: Renderer2
    ) {}

    public appendComponent() {
        const factory = 
        this.cfResolver.resolveComponentFactory(MyDynamicComponent);
        const componentRef = this.vcRef.createComponent(factory);
        this.renderer.appendChild(
           this.vcRef.element.nativeElement,
           componentRef.injector.get(MyDynamicComponent).elRef.nativeElement
        );
    }
}

【讨论】:

  • 你能添加一个stackblitz吗?我无法让它工作?
  • 为了完成这项工作,MyDynamicComponent 需要通过 elRef 对其 ElementRef 进行公共访问。
【解决方案3】:

我的肮脏方式,只是保存动态创建的组件(兄弟)的引用并使用 vanilla JS 移动它:

  constructor(
    private el: ElementRef,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
  ) {}

  ngOnInit() {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
      MyDynamicComponent,
    );
    const componentRef = this.viewContainerRef.createComponent(componentFactory);
    this.el.nativeElement.appendChild(componentRef.location.nativeElement);
  }

【讨论】:

  • 如果您的主机有一个 *ngIf 或其他将其转换为模板的指令,这将分开。
【解决方案4】:

我的解决方案与@Bohdan Khodakivskyi 非常相似。但是我尝试了Renderer2。

  constructor(
    private el: ElementRef,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private render: Renderer2
  ) {}

  ngOnInit() {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
      MyDynamicComponent,
    );
    const componentRef = this.viewContainerRef.createComponent(componentFactory);
    this.render.appendChild(this.el.nativeElement, componentRef.location.nativeElement)
  }

【讨论】:

  • 使用渲染器调整 DOM 结构可能会使您遇到内部更改检测问题,请参阅this blog post。我不知道它是否会在这种特殊情况下引起问题,但......
  • 如果您的主机有一个 *ngIf 或其他将其转换为模板的指令,这将分开。
【解决方案5】:

现在可以通过@angular/cdk 中的Portal 获得更简单的方法。

npm i @angular/cdk

app.module.ts:

import { PortalModule } from '@angular/cdk/portal';

@NgModule({
  imports: [PortalModule],
  entryComponents: [MyDynamicComponent]
})

一些组件:

@Component({
  selector: 'some-component',
  template: `<ng-template [cdkPortalOutlet]="myPortal"></ng-template>`
})
export class SomeComponent {
  ...
  this.myPortal = new ComponentPortal(MyDynamicComponent);
}

【讨论】:

    【解决方案6】:

    @Magicaner 的This answer 帮助了我,但缺少一些细节以使其正常工作。

    总之,是的,您可以使用Renderer2.appendChild 添加新的子组件。该指令如下所示:

    @Directive({
        selector: '[myDynamicDirective]'
    })
    export class MyDynamicDirective implements OnInit {
        constructor(
            private cfResolver: ComponentFactoryResolver,
            public vcRef: ViewContainerRef,
            private renderer: Renderer2
        ) { }
    
        ngOnInit() {
          this.appendComponent();
        }
    
        public appendComponent() {
            const factory =  this.cfResolver.resolveComponentFactory(MyDynamicComponent);
            const componentRef = this.vcRef.createComponent(factory);
            this.renderer.appendChild(
               this.vcRef.element.nativeElement,
               componentRef.injector.get(MyDynamicComponent).elRef.nativeElement
            );
        }
    }
    

    它将MyDynamicComponent 的一个新实例添加为一个孩子。要从该动态组件获取elRef 以发送到appendChild,您可以在组件定义中执行此操作:

    @Component({
      selector: 'my-dynamic-component',
      template: '...'
    })
    export class MyDynamicComponent {
      constructor(public elRef: ElementRef) { }
    }
    

    而且你还需要在模块中将动态组件添加到entryComponents,当然还有将指令和动态组件添加到模块declarations

    工作堆栈闪电战here

    【讨论】:

      猜你喜欢
      • 2019-01-05
      • 2018-05-31
      • 2016-03-14
      • 2016-08-17
      • 2016-12-16
      • 1970-01-01
      • 1970-01-01
      • 2014-06-16
      • 1970-01-01
      相关资源
      最近更新 更多