skip to content

How does Angular's default Emulated view encapsulation scope a component's CSS, and where does that scoping stop?

level: middleimportance: must knowfreq 58%

answer

  1. compile-time selector rewrite
  2. _nghost and _ngcontent
  3. one suffix per component class
  4. leaks in, not out
  5. projected content keeps its declarer's attribute

basics

~20 s

At compile time Angular appends a component-specific attribute selector to every rule; at runtime it stamps _ngcontent on elements the template creates and _nghost on the host. Styles cannot leak out, but global styles still apply inside.

solid answer

~40 s

Under `ViewEncapsulation.Emulated` the compiler rewrites the component's CSS: `h2 { }` becomes `h2[_ngcontent-ng-c1234] { }` and `:host` becomes `[_nghost-ng-c1234]`. The renderer adds the `_ngcontent-…` attribute to every element the component's template creates and `_nghost-…` to its host, and inserts the rewritten stylesheet into the document. The suffix is derived from the component definition, so all instances of one component share it. The scope is exactly the elements this template creates: not the internals of child components, which carry their own attribute; not DOM added by `innerHTML` or third-party code. Projected content carries the **declaring** parent's attribute, so the parent styles it. Stylesheets pulled in by a template `<link>` or CSS `@import` are not rewritten at all, and global styles still match inside.

code

ts · 15 lines
ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-invoice-card',
  template: `
    <h2>Invoice</h2>
    <ng-content />
  `,
  styles: `
    :host { display: block; padding: 1rem; }
    h2 { color: teal; }        /* matches only this template's h2 */
    p { margin: 0; }           /* does NOT match a <p> the parent projects */
  `,
})
export class InvoiceCard {}

go deeper

for a junior

Recall that Emulated is the default and scopes styles by adding attributes, so styles do not leak out while global styles still apply.

for a middle

Explain the compile-time rewrite to _ngcontent and _nghost selectors, the runtime stamping, and why projected content and child internals fall on different sides of the scope.

for a senior

Debug scoping failures by comparing attribute suffixes, and design around the boundaries: innerHTML content, child internals, specificity against global rules.

for a principal

Decide how a design system exposes styling across Emulated boundaries, through inputs, host classes and documented theming hooks rather than selector reach-through.

## What "emulated" means The browser's Shadow DOM scopes CSS natively, but it changes events, slots and tooling. Angular's default, `ViewEncapsulation.Emulated`, **emulates** the style-scoping part with ordinary attributes: no shadow root, no browser feature required. It works in two halves, one in the compiler and one in the renderer. ## Compile time: the selector rewrite When Angular compiles a component whose encapsulation is `Emulated`, it runs the component's CSS through a shim that scopes every selector: ```css /* authored */ :host { display: block; } h2 { color: teal; } .card .title { font-weight: 600; } /* emitted (suffix shortened) */ [_nghost-ng-c1234] { display: block; } h2[_ngcontent-ng-c1234] { color: teal; } .card[_ngcontent-ng-c1234] .title[_ngcontent-ng-c1234] { font-weight: 600; } ``` - Each compound selector gets the **content attribute**, so both `.card` and `.title` must belong to this component. - `:host` becomes the **host attribute**; `:host-context()` is rewritten too. - The suffix combines the application ID (`ng` by default) with an ID hashed from the component's definition, so every instance of the same component class shares one suffix. - If the component has no styles at all, the compiler switches it to `None`, and no attributes are generated. ## Runtime: stamping the attributes The renderer for an emulated component adds `_ngcontent-ng-c1234` to each element that component's template creates, and `_nghost-ng-c1234` to the host element. The rewritten stylesheet is added to the document when the component first renders. Nothing else in the page carries those attributes, which is why the rules cannot leak out. ## Where the scope ends | Element | Matched by the component's rules? | Why | |---|---|---| | elements in its own template | yes | they carry its `_ngcontent` attribute | | its host element | only via `:host` | the host carries `_nghost`, plus the parent's `_ngcontent` when the parent is emulated | | inside a child component's template | no | those carry the child's own attribute | | content it projects into a child | yes | projected nodes were created by its template | | content projected into it by a parent | no | those carry the parent's attribute | | DOM from `innerHTML` or third-party code | no | Angular's renderer did not create them | Two more boundaries matter: - **Global styles reach in.** Emulation adds attributes to the component's selectors; it does nothing to other stylesheets, so a global `button` rule still styles the component's buttons. - **External references are not rewritten.** A `<link>` in the template or a CSS `@import` is treated as an external style and is not affected by emulated encapsulation. A `<style>` element written inside the template, by contrast, is scoped like the component's own styles. ## A specificity side effect The added attribute raises each rewritten selector's specificity by one attribute selector, so `h2[_ngcontent-…]` usually beats a global `h2`. Usually is the right word: the docs state that Angular does not fully guarantee component styles override outside styles, and a more specific global selector still wins. ## Common misreadings - **Per instance or per class?** One suffix per component class; two `<app-invoice-card>` elements share it, which is why one stylesheet serves them all. - **Does the host get the content attribute?** The host carries the component's `_nghost` attribute and, under an emulated parent, the **parent's** `_ngcontent` attribute, so the parent can style the box and the component styles it through `:host`. - **Do nested selectors cross into children?** No: `.card .title` requires both elements to carry this component's attribute, so a `.title` inside a child component never matches. ## Practical consequences 1. To style a child component, give it inputs, host classes or a theming contract; the parent's rules cannot reach its internals. 2. To style content a parent projects, style it in the **parent** that declared it. 3. Dynamically inserted HTML (a CMS fragment via `[innerHTML]`) is not scoped; style it with global or deliberately unscoped rules. 4. When a rule unexpectedly fails to apply, inspect the element in dev tools and compare its `_ngcontent` suffix with the one in the rewritten selector.

  • Why can't a parent component's emulated styles restyle elements inside a child component?
    The parent's rules are rewritten to require the parent's `_ngcontent` attribute, and the child's template elements carry the child's attribute instead. The child's host element sits in the parent's template, so the parent can style that box, but nothing inside it.
  • Are styles in a <style> element inside a component template scoped?
    Yes. Angular applies the component's encapsulation mode to a `<style>` element in its template, though it does not support bindings inside it. Stylesheets referenced with a template `<link>` or a CSS `@import` are treated as external and are not affected by emulated encapsulation.

saying these in an interview costs you the question

  • Emulated encapsulation also blocks global styles from reaching the component.
  • A parent's emulated styles automatically style its child components' internals.
  • Emulated mode works by attaching a shadow root to each host element.
  • Elements inserted with innerHTML inherit the component's scoping attribute.
  • Projected content is styled by the component it is projected into.