Skip to the solution
MediumFrontend9 min read

Explain the difference between Shadow DOM, Emulated, and None View Encapsulation with compile output.

715 views

The problem

Question presented to candidate: "Take one small component and compile it three times, with Emulated, ShadowDom and None. Walk me through what the compiler emits for each, and what ends up in the DOM at runtime."

What a strong answer should cover:

  • Emulated (default): the compiler rewrites each selector to require [_ngcontent-%COMP%], and :host becomes [_nghost-%COMP%]; at runtime %COMP% is replaced by a component id like ng-c3599773454, the host gets _nghost-… and template elements get _ngcontent-…; styles go into a <style> in <head>.
  • None: CSS is emitted unchanged with encapsulation: 2; it lands in <head> and is global; :host matches nothing.
  • ShadowDom: CSS also unchanged with encapsulation: 3; Angular calls attachShadow and puts styles inside the shadow root; no generated attributes.
  • Gotcha: in ShadowDom mode Angular also copies the other components' styles into the shadow root, so a None rule can still reach inside; ExperimentalIsolatedShadowDom (v21, experimental) stops that.
  • A component with no styles is compiled with encapsulation: 2 regardless, so no attributes are stamped.

Clarifying questions expected:

  • "Do you want the compiler's ɵcmp definition, the runtime DOM, or both?"
  • "Is the Shadow DOM component meant to be isolated from global styles too?"

Code / implementation expected: Yes — the three components plus a dump of the generated CSS and DOM.

  • styles
  • encapsulation

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read · 9 sections

Read the code

AngularPrint the styles and attributes Angular emits for Emulated, ShadowDom and None
import { Component, ViewEncapsulation, afterNextRender, signal } from "@angular/core";

@Component({
  selector: "emulated-card", // ViewEncapsulation.Emulated is the default
  template: `<p class="title">Emulated</p>`,
  styles: `:host { display: block; } .title { color: crimson; }`,
})
export class EmulatedCard {}

@Component({
  selector: "shadow-card",
  encapsulation: ViewEncapsulation.ShadowDom,
  template: `<p class="title">ShadowDom</p>`,
  styles: `:host { display: block; } .title { color: teal; }`,
})
export class ShadowCard {}

@Component({
  selector: "none-card",
  encapsulation: ViewEncapsulation.None,
  template: `<p class="title">None</p>`,
  styles: `:host { display: block; } .title { color: orange; }`,
})
export class NoneCard {}

@Component({
  selector: "app-root",
  imports: [EmulatedCard, ShadowCard, NoneCard],
  template: `
    <emulated-card /><shadow-card /><none-card />
    <h4>What Angular put in the page</h4>
    <pre>{{ dump() }}</pre>
  `,
})
export class AppComponent {
  dump = signal("");

  constructor() {
    afterNextRender(() => {
      const out: string[] = [];
      document.head.querySelectorAll("style").forEach((s) => {
        // Only show the styles that came from these three components.
        if (s.textContent?.includes(".title")) out.push(`<head> <style>: ${s.textContent}`);
      });
      for (const tag of ["emulated-card", "shadow-card", "none-card"]) {
        const host = document.querySelector(tag)!;
        out.push(`${tag} host attrs: [${[...host.attributes].map((a) => a.name).join(" ")}]`);
        out.push(`  light DOM: ${host.innerHTML || "(empty)"}`);
        if (host.shadowRoot) {
          out.push(`  shadowRoot (${host.shadowRoot.mode}):`);
          host.shadowRoot.querySelectorAll("style").forEach((s) => out.push(`    <style>: ${s.textContent}`));
          out.push(`    ${host.shadowRoot.querySelector("p")!.outerHTML}`);
        }
      }
      this.dump.set(out.join("\n"));
    });
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 39 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

What do @HostListener and @HostBinding do?

Continue