Search code examples
angulartemplatesreusability

Angular how to global / application scope template


I need to reuse a parameterized menu template in many components to build up the local menu

<ng-template #menubutton let-link="link" let-icon="icon" let-text="text" ...>  
... does magic :)
</ng-template>

I tried to extract this part

  1. into an injectable common-menu component
  2. adding to app.component.html

but doesn't work and no any error.

pls put aside that this menu solution is very poor because the question can be generalized: how can we make application scope templates ?

Thanks in advance,

Csaba


Solution

  • I know this is an old question but here's how I do it in my Angular project for years.

    We need a simple map of TemplateRef instances:

    import { TemplateRef } from "@angular/core";
    
    export class TemplateStore {
        private templates: Map<string, TemplateRef<any>> = new Map<string, TemplateRef<any>>();
    
        public add(key: string, template: TemplateRef<any>): void {
            // The argument validation is omitted for brevity
    
            this.templates.set(key, template);
        }
    
        public get(key: string): TemplateRef<any> {
            return this.templates.get(key);
        }
    }
    

    And a directive that assigns a key to templates:

    import { Directive, Input, TemplateRef } from "@angular/core";
    import { TemplateStore } from "./template-store";
    
    @Directive({
        selector: "[appTemplateKey]"
    })
    export class TemplateKey {
        constructor(
            private readonly template: TemplateRef<any>,
            private readonly templateStore: TemplateStore
        ) { }
    
        @Input("appTemplateKey")
        public set key(value: string) {
            this.templateStore.add(value, this.template);
        }
    }
    

    Then we put the global templates in the app component template:

    <ng-template appTemplateKey="avatar" let-user>
        <a routerLink="/{{user.id}}" aria-hidden="true" tabindex="-1">
            <img [src]="user.pictureUrl" alt="" loading="lazy">
        </a>
    </ng-template>
    

    And the usage:

    <ng-container *ngTemplateOutlet="templateStore.get('avatar'); context: {$implicit: currentUser}"></ng-container>
    

    The inconvinience here is that we need to inject the TemplateStore into every component that uses it. Since in my Angular project all the components inherit a base class, we can avoid the injection by putting in the base class a method like this:

    public template(key: string): TemplateRef<any> {
        return AppContext.current.templateStore.get(key);
    }
    

    The AppContext.current is a global object that holds a reference to the TemplateStore. So the usage become:

    <ng-container *ngTemplateOutlet="template('avatar'); context: {$implicit: currentUser}"></ng-container>