DEV Community

sanketmunot
sanketmunot

Posted on

Dynamic Template Injection in Angular: Clean & Scalable

Discover how to inject feature-specific templates into a parent layout using ng-template, RxJS, and a slot service — no inputs or outputs needed.


I’m new to Angular.

I come from React.

So when I needed a layout with dynamic footer actions, my first solution was… chaos.

The setup

  • 1 LayoutComponent
  • 3 feature components
  • A shared footer area
  • Each feature needs different buttons & info

The bad way 👎

Passing configs, callbacks, and flags up and down:

<feature-a (footerChange)="setFooter($event)"></feature-a>
<feature-b (footerChange)="setFooter($event)"></feature-b>
Enter fullscreen mode Exit fullscreen mode
setFooter(config: FooterConfig) {
  this.footerButtons = config;
}
Enter fullscreen mode Exit fullscreen mode

It works… but the layout becomes a dumping ground.


The idea: make the footer a slot

Instead of sending data up,
let the feature inject UI into the layout.

Think: React portals + refs, but Angular style.


Step 1: a tiny slot service

@Injectable({ providedIn: 'root' })
export class SlotService {
  private slots = new Map<string, BehaviorSubject<TemplateRef<any> | null>>();

  slot$(key: string) {
    return this.getSlot(key).asObservable();
  }

  setTemplate(key: string, tpl: TemplateRef<any>) {
    this.getSlot(key).next(tpl);
  }

  clear(key: string) {
    this.getSlot(key).next(null);
  }

  private getSlot(key: string) {
    if (!this.slots.has(key)) {
      this.slots.set(key, new BehaviorSubject<TemplateRef<any> | null>(null));
    }
    return this.slots.get(key)!;
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: the layout renders whatever it gets

<!-- layout.component.html -->
<div class="footer">
  <ng-container *ngTemplateOutlet="footerTpl"></ng-container>
</div>
Enter fullscreen mode Exit fullscreen mode
// layout.component.ts
footerTpl!: TemplateRef<any> | null;

ngOnInit() {
  this.slotService.slot$('footer').subscribe(tpl => {
    this.footerTpl = tpl;
  });
}
Enter fullscreen mode Exit fullscreen mode

The layout has no idea who owns the footer.


Step 3: a feature plugs in its footer

<!-- feature.component.html -->
<ng-template #footerTemplate>
  <button (click)="save()">Save</button>
  <button (click)="cancel()">Cancel</button>
</ng-template>
Enter fullscreen mode Exit fullscreen mode
// feature.component.ts
@ViewChild('footerTemplate') footerTemplate!: TemplateRef<any>;

ngAfterViewInit() {
  this.slotService.setTemplate('footer', this.footerTemplate);
}

ngOnDestroy() {
  this.slotService.clear('footer');
}
Enter fullscreen mode Exit fullscreen mode

What just happened?

Your feature registered UI.
Your layout rendered it.

No props.
No events.
No tight coupling.


The takeaway

If Angular feels awkward, it’s often because we try to use React patterns directly.

Translate the idea, not the code —
and Angular suddenly becomes powerful 🚀

Top comments (0)