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>
setFooter(config: FooterConfig) {
this.footerButtons = config;
}
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)!;
}
}
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.
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>
// 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 🚀
Top comments (0)