Hook — 06 : 07 a.m., Puerto Plata (Dominican Republic) ↔ Panamá City code-review
Warm coastal wind rustled my hammock as Daniela, hacking from a rooftop cowork in Panamá, shared her screen: the CMS dashboard gasped under 80 dormant widgets shipped in the main bundle. “Drag-and-drop is smooth on fiber,” she said, “but hotel Wi-Fi turns it into molasses.” I opened DevTools over café-con-leche bandwidth, saw two megabytes of unused JavaScript, and introduced her to ViewContainerRef—Angular’s doorway to runtime component injection. Within half an hour we spawned widgets only when editors dropped them onto the canvas, trimmed the first paint by half a second, and spared every prepaid SIM along our Latin-American flight path.


Why Runtime Loading Still Matters

Single-page apps succeed when they feel quick and flexible. Shipping every potential component up front sabotages Core Web Vitals and burns bandwidth, yet users expect modular dashboards, plug-in editors, and A/B test slots. Angular offers a pair of low-level APIs—ViewContainerRef and ComponentRef—that let you instantiate any standalone or declared component at the exact moment you need it. Couple that with lazy imports and you have a recipe for both snappy loads and true runtime composability.


The Mental Model

Dynamic loading is a two-step dance:

  1. Resolve the component type (compile-time or via import()).
  2. Create an instance inside a ViewContainerRef, which acts like an outlet you control in code.

Every instance returned is a live ComponentRef, giving you access to inputs, outputs, and lifecycle hooks until you call destroy().

Imagine ViewContainerRef as an empty hotel room: you can check in any component, manage its stay, and free the room when the guest leaves—keeping housekeeping (change detection) automatic.


Setting Up the Host Directive

First you need a place where runtime components will live. A structural directive keeps templates clean and interacts directly with the host view.

tsCopyEdit@Directive({
  selector: '[widgetHost]',
  standalone: true,
})
export class WidgetHostDirective {
  constructor(public vcr: ViewContainerRef) {}
}

In your host component’s template:

htmlCopyEdit<section class="canvas">
  <ng-container widgetHost></ng-container>
</section>

The ng-container leaves no stray markup, yet grants code behind the scenes full control.


Loading a Component Synchronously

For components already compiled into the bundle—handy in admin-only builds—you inject the directive and call createComponent.

tsCopyEdit@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.html',
  standalone: true,
  imports: [WidgetHostDirective],
})
export class DashboardComponent {
  @ViewChild(WidgetHostDirective, { static: true })
  host!: WidgetHostDirective;

  loadClock() {
    const ref = this.host.vcr.createComponent(ClockWidgetComponent);
    ref.setInput('timezone', 'America/Santo_Domingo');
  }

  clear() {
    this.host.vcr.clear(); // destroys all
  }
}

Every call yields a new instance—ideal for multi-widget layouts.


Bringing in Lazy-Loaded Widgets

Synchronous loading still ships code up front. Combine import() with Angular’s Ivy JIT to keep bytes off the wire until the last second.

tsCopyEditasync loadSalesChart() {
  const { SalesChartComponent } = await import(
    './widgets/sales-chart/sales-chart.component'
  );
  const ref = this.host.vcr.createComponent(SalesChartComponent);
  ref.setInput('range', '30d');
}

Webpack splits the sales-chart chunk automatically. On 4 G, the pay-as-you-go traveler downloads it only if they open the analytics tab.


Communicating with Dynamically Created Components

Inputs are easy with setInput. Outputs use the changeDetectorRef or manual subscriptions:

tsCopyEditconst ref = this.host.vcr.createComponent(NotificationWidgetComponent);
ref.instance.dismiss.subscribe(() => ref.destroy());

Always remember to unsubscribe or destroy the whole ComponentRef to avoid memory leaks—especially when your code runs for hours in a kiosk.


Remote-Work Insight: Snapshot Previews over Spotty Wi-Fi

While reviewing Daniela’s PR from a rooftop bar in Cartagena, we noticed widget previews flickered during low bandwidth. We solved it by loading lightweight skeleton components first, then swapping them out when the heavy chart finished streaming.

tsCopyEditconst skeleton = this.host.vcr.createComponent(SkeletonWidgetComponent);

import('./widgets/heavy-chart/heavy-chart.component').then(({ HeavyChartComponent }) => {
  this.host.vcr.clear();                      // remove skeleton
  this.host.vcr.createComponent(HeavyChartComponent);
});

The user sees a shimmering card immediately, even on 1 Mbps hotel Wi-Fi.


Performance and Accessibility Checkpoints


Common Pitfalls and Their Fixes

SymptomUnderlying CauseQuick Repair
NG04157: Component is not standaloneForgot to declare component or mark standalone: true in Ivy worldExport through a shared NgModule or add standalone: true
Styles bleed between dynamic widgetsShadow DOM or encapsulation mismatchSwitch encapsulation to ViewEncapsulation.Emulated or provide unique CSS vars
Memory climbs after tab hoppingNever destroyed old refsCall ref.destroy() or vcr.clear() on hidden panels
Change detection doesn’t triggerMutation done outside Angular zoneWrap imperative updates in NgZone.run() or use signals

Call-Out Commands

CommandPurpose
ng g c widgets/sales-chart --standaloneGenerate lazy-friendly widget
ng build --stats-jsonInspect new chunk sizes
ng profiler timeChangesMeasure change-detection impact after refactor

Diagram Blueprint

Picture a flow where the host component presses a “+ Widget” button → import() fetches chart-chunk.jsViewContainerRef injects SalesChartComponent → Component emits data → Host manages lifecycle. A side-by-side before/after shows bundle size shrinkage.


Wrapping Up

Dynamic component loading marries flexibility with performance. By anchoring widgets in a ViewContainerRef, deferring heavy imports with import(), and minding lifecycle hygiene, you can deliver modular experiences that still respect mobile caps across Latin America. Next time a teammate asks to preview a feature without inflating the bundle, hand them a host directive, share this guide, and sip your espresso as their FCP trips Google’s “good” line.

–– Drop your runtime-rendering wins or woes in the comments; I’ll reply between airport layovers and late-night arepa sessions.

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x