Search code examples
angularangular-content-projection

Angular 8: How to use a Multi-Content-Projection / Multiple-Transclusion slot twice?


  • I have an Angular 8.2 project that has two different display modes (landscape|portrait)
  • And within each, there's a toolbar at top and at bottom, surrounding the main content.
  • The menu as well as the main content is module specific, thus I am using slots. In other words: One out of 5 different possible parent component uses this component and provides the main content and an (individually different) menu, using angular content projection.
    <ng-template #menu><ng-content select="[slot='menu']"></ng-content></ng-template>
    <ng-template #content><ng-content select="[slot='content']"></ng-content></ng-template>

    <section *ngIf="landscape">
        <div class="global-ui-top">
            <ng-container *ngTemplateOutlet="menu"></ng-container> // 1st
        </div>
        <some-main-content>
            <ng-container *ngTemplateOutlet="content"></ng-container>
        </some-main-content>
        <div class="global-ui-bottom">
            <ng-container *ngTemplateOutlet="menu"></ng-container> // 2nd
        </div>
    </section>

    // portrait mode
    <section *ngIf="!landscape">
        ... pretty similar to above, also 2× menu, 1× content...

Problem: How can I use a slot twice?** No error comes up, if I use e.g...

A <ng-container *ngTemplateOutlet="menu"></ng-container> A
B <ng-container *ngTemplateOutlet="menu"></ng-container> B

...however the slot get's only "picked once" on last occasion (the tag between the two A's remains empty). In other words, my 1st .global-ui-top remains empty.

nb: This ng-detour in line 1+2 helps around a certain bug. Is is not doing harm, but also not helping (sadly the ng-template-contents don't get frozen after “first fill” ). Apparently there is a „uniqueness in the selection“ principle regarding slots.

Is there a way to stuff the contents of the menu-slot into a template of whatever kind, and re-use it multiple times (all visible at the same time)?**

  • perhaps a fancy flag on either <ng-content> or <ng-template? ("static"?!?)
  • or first somehow whacking the slot's contents into a @viewChild() (in component.ts)...
  • else...?

minor update

It seems possible to capture the ng-template reference into a View Child:

   @ViewChild('menu', {static: true}) menuA: TemplateRef<any>;

...a console.dir() shows me a valid ElementRef, but I don't manage to output that in the .html-Template, i.e. <ng-template [ngTemplateOutlet]="menuA"></ng-template>


Solution

  • Maybe this talk is going to be useful.

    My understanding is that ng-content does not create content, it simply moves content from one part to another.

    I think the solution to your problem is to use ng-template. For example:

    projected.component.ts

    let instances = 0;
    
    @Component({
      selector: 'app-projected',
    template: `projected instance nr: {{ instanceId }}`
    })
    export class ProjectedComponent implements OnInit {
      instanceId: number;
    
      constructor() { }
    
      ngOnInit() {
        this.instanceId = ++instances;
      }
    
    }
    

    parent.component.ts

    @Component({
      selector: 'app-parent',
      template: `
        <h3>Parent component</h3>
    
        <p>Using content #1</p>
        <ng-container *ngTemplateOutlet="content"></ng-container>
    
        <p>Using content #2</p>
        <ng-container *ngTemplateOutlet="content"></ng-container>
      `,
    })
    export class ParentComponent implements OnInit {
      @ContentChild('content', { read: TemplateRef }) content: TemplateRef<any>;
    
      constructor() { }
    
      ngOnInit() {
      }
    
    }
    

    And here's the important thing:

    <app-parent>
      <ng-template #content>
        <app-projected></app-projected>
      </ng-template>
    </app-parent>
    

    As far as I understand, what's inside an ng-template can be thought of as a blueprint. So every time you use ng-template, you'd create a new instance of that blueprint.

    Demo