Back to blog

Dynamic Template Injection in Angular: Clean & Scalable

Sanket Munot
angular frontend architecture design-patterns react-to-angular ng-template rxjs

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>
setFooter(config: FooterConfig) {
  this.footerButtons = config;
}

It works… but the layout becomes a dumping ground.


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

Step 2: the layout renders whatever it gets

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

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

The layout has no idea who owns the footer.


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

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

ngOnDestroy() {
  this.slotService.clear('footer');
}

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 🚀

© 2026 Sanket Munot