Angular2:在DOM

时间:2016-06-29 08:03:30

标签: angular

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

有很多例子可以将动态组件作为给定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);
    });
  }
}

但是这会产生类似于:

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

预期结果:

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

使用SomeComponent&#39; s ViewContainerRef具有相同的结果,它仍然将生成的组件插入为兄弟,而不是孩子。我可以使用一个解决方案,其中模板为空,动态组件插入模板中(在组件选择器标签内)。

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

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

5 个答案:

答案 0 :(得分:45)

TL; DR :将<div><ng-template #placeholder></ng-template></div>替换为div以插入@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(/*...*/) {/*...*/} }

这是sysdbslocale(Angular 6)和相关代码:

<ng-container #placeholder></ng-container>

似乎ng-template也有效(将ng-container替换为<ng-container>)。我喜欢这种方法,因为NgIf明确地针对此用例(不添加标记的容器),并且可以在其他情况下使用,例如def createWidgets(self): self.lbl = tk.Label(self, text="Pressing buttons is fun, isn't it?") self.hi_there = tk.Button(self, fg="green") self.hi_there["text"] = "Let's press" self.hi_there["command"] = self.lel self.lbl.pack() self.hi_there.pack(side="top") ,而不包含在真实标记中。

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

编辑[2018-05-30]:更新为stackblitz链接,以获得最新的工作示例。

答案 1 :(得分:7)

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

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

以下是代码:

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

答案 2 :(得分: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);
  }

答案 3 :(得分:1)

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

  

npm i @ angular / cdk

app.module.ts:

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

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

SomeComponent:

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

答案 4 :(得分:1)

@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