Skip to the solution
HardFrontend7 min read

What is ViewEncapsulation in Angular?

485 views

The problem

Question presented to candidate: "Two teams both style a .label class in their own components, and one team's styles keep showing up in the other team's screens. Explain what ViewEncapsulation is, what the options are, and which component is the likely culprit."

What a strong answer should cover:

  • ViewEncapsulation controls whether a component's styles apply only to its own template or to the whole page; it is set per component in @Component({ encapsulation }).
  • Emulated (default): Angular rewrites selectors with generated attributes so they only match elements in that component's template.
  • ShadowDom: uses the browser's real Shadow DOM, styles live inside the component's shadow root; None: styles are added to the document as written and are global.
  • The likely culprit is a component with ViewEncapsulation.None (or ::ng-deep); its rules apply everywhere, including inside other Emulated components.
  • Encapsulation stops styles leaking out; it does not stop global styles leaking in (except Shadow DOM, partly).
  • Mention ExperimentalIsolatedShadowDom (experimental since v21) and that styles must be statically analysable for AOT.

Clarifying questions expected:

  • "Are any components using encapsulation: ViewEncapsulation.None or ::ng-deep?"
  • "Do the leaking rules come from component styles or from global stylesheets?"

Code / implementation expected: Optional β€” two components with the same class name, one Emulated and one None, showing the leak.

  • view-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

7 min read Β· 8 sections

Read the code

AngularEmulated styles stay scoped, None styles leak to the whole page
import { Component, ViewEncapsulation, afterNextRender, signal } from "@angular/core";

// Emulated (the default): ".label" is rewritten so it only matches this template.
@Component({
  selector: "scoped-badge",
  template: `<span class="label">scoped badge</span>`,
  styles: `.label { color: rgb(0, 128, 0); }`,
})
export class ScopedBadge {}

// None: the CSS goes into <head> untouched and applies to the whole page.
@Component({
  selector: "global-badge",
  encapsulation: ViewEncapsulation.None,
  template: `<span class="label">global badge</span>`,
  styles: `.label { font-weight: 700; }`,
})
export class GlobalBadge {}

@Component({
  selector: "app-root",
  imports: [ScopedBadge, GlobalBadge],
  template: `
    <scoped-badge /> <global-badge />
    <span class="label">plain span in app-root</span>
    <ul>@for (row of report(); track $index) { <li>{{ row }}</li> }</ul>
  `,
})
export class AppComponent {
  report = signal<string[]>([]);

  constructor() {
    afterNextRender(() => {
      const rows = [...document.querySelectorAll("app-root span.label")].map((el) => {
        const css = getComputedStyle(el);
        const attrs = [...el.attributes].map((a) => a.name).join(" ");
        return `${el.textContent}: color=${css.color} weight=${css.fontWeight} attrs=[${attrs}]`;
      });
      this.report.set(rows);
    });
  }
}

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 102 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 nextHard

What is the difference between forkJoin, zip, combineLatest, and withLatestFrom?

Continue