使用 Angular 4 / 5 动态填充模板

Dynamically populating a template with Angular 4 / 5

我们的应用程序布局具有工具栏和实际内容区域。由于工具栏的结构有些复杂 DOM 我们决定从中创建一个组件。

我们可以在每个页面上重用这个组件,但工具栏位置实际上不在内容旁边(其他一些组件介于两者之间)。所以我们宁愿在父页面组件中放置一次工具栏,并根据需要在每个页面上声明工具栏内容。

<app>
    <toolbar>
        <ng-container #toolbar></ng-container>
    </toolbar>

    <some unrelated component>...</some unrelated component>

    <content>
        <ng-template #tbcontent>
            <button>Click me!</button>
        </ng-template>
        <p>Actual page content!</p>
    </content>
</app>

因此在上面的示例中,我们会将 #tbcontent 中的内容放入 #toolbar 中,有效地呈现如下内容:

<app>
    <toolbar>
        <button>Click me!</button>
    </toolbar>

    <some unrelated component>...</some unrelated component>

    <content>
        <p>Actual page content!</p>
    </content>
</app>

我们已尝试使用 @ViewChild 从根 AppComponent 查询 #toolbar#tbcontent 节点(为清楚起见,已简化代码):

@Component({
    /* ... */
})
export class AppComponent implements OnInit, AfterViewInit {
    @ViewChild("tbcontent") tbcontent: TemplateRef<any>;
    @ViewChild("toolbar", {read: ViewContainerRef}) toolbar: ViewContainerRef;

    /* ... */

    ngAfterViewInit() {
        console.log('ngAfterViewInit');
        console.log(this.tbcontent);
        console.log(this.toolbar);
        this.toolbar.createEmbeddedView(this.tbcontent);
    }
}

但是两个引用都是undefined。有没有办法正确访问元素?这实际上是一种不好的做法吗,也许有更好的方法来达到相同的结果?

更新: @martin-nuc 友善地建议使用 *ngTemplateOutlet,这似乎可以解决问题,但只有当被复制的节点位于同一节点时零件。这是一个 JSFiddle 显示这个:

https://jsfiddle.net/carlosafonso/k3oq56oq/3/

谢谢。

当您定义模板时,它不会插入到 DOM 中。您需要使用一些指令将其插入到 DOM.

此外,ng-container 基本上只是一些内容的占位符,但不会呈现为 DOM。

我认为你想做的是

<toolbar>
  <ng-container *ngTemplateOutlet="tbcontent"></ng-container>
</toolbar>

编辑:问题是如何在父组件中显示子组件的模板。我基本上将模板作为子组件的输出传递。这是实现它的方法。

父组件:

@Component({
  selector: 'app',
  template: `
    <div class="container">
      <toolbar>
         <ng-container *ngTemplateOutlet="toolbarContent"></ng-container>       
      </toolbar>
      <hr/>
      <content (toolbarLoaded)="displayToolbar($event)"></content>
    </div>
  `,
})
class AppComponent {

  constructor() {
  }

  displayToolbar($event) {
    this.toolbarContent = $event;
  }
}

带有工具栏模板的子组件:

@Component({
  selector: 'content',
  template: `
    <ng-template #tb>
        <button type="button" class="btn btn-sm btn-primary">Content-specific action</button>
    </ng-template>
    <p>These are the page contents.</p>
  `
})
class ContentComponent {
  @Output() toolbarLoaded = new EventEmitter<any>();
  @ViewChild('tb') tb;

  ngAfterViewInit() {
    // arrow function dont work in jsfiddle somehow :(
    let that = this;
    // we need timeout to avoid Expression had changed after it was checked (https://github.com/angular/angular/issues/6005)
    setTimeout(() => {
        that.toolbarLoaded.emit(that.tb);
    }, 0);
  }
}

Jsfiddle:https://jsfiddle.net/fdv1tesr/3/