In an Angular 22 app, part of a page loses its styling after a ViewEncapsulation.None component is destroyed; what explains it, and how do you fix it?
answer
- component styles are reference-counted
- last instance gone, style element gone
- a DI token controls it
- global rules belong in a global stylesheet
basics
~20 sAngular removes a component's styles from the DOM once no instance using them remains, and v22 extended removal to styles whose host is dropped. Rules a None component carried for other DOM vanish with it; move them to a global stylesheet.
solid answer
~40 sAngular adds a component's styles when it first renders and counts their uses; when the last instance is destroyed the `<style>` or `<link>` element is removed. That is controlled by the `REMOVE_STYLES_ON_COMPONENT_DESTROY` token from `@angular/platform-browser`, `true` by default since v17. Angular 22 went further and removes unused styles when their associated host is dropped, and its changelog warns that DOM relying on those styles, because it does not use `Emulated` encapsulation or lives outside Angular, can appear unstyled. A `ViewEncapsulation.None` component whose rules also styled a non-Angular widget or another page region is the classic victim. The durable fix is to move rules that must outlive the component into the application's global stylesheet. Providing `REMOVE_STYLES_ON_COMPONENT_DESTROY` as `false` restores the old keep-everything behaviour, but for every component in the app.
code
ts · 8 linesimport { ApplicationConfig } from '@angular/core';
import { REMOVE_STYLES_ON_COMPONENT_DESTROY } from '@angular/platform-browser';
// Last resort: keep destroyed components' styles in the DOM app-wide.
// Prefer moving shared rules into the global stylesheet instead.
export const appConfig: ApplicationConfig = {
providers: [{ provide: REMOVE_STYLES_ON_COMPONENT_DESTROY, useValue: false }],
};go deeper
Recall that component styles are added when the component renders and can be removed when it is destroyed, so they are not the same as global CSS.
Explain reference-counted style elements, the REMOVE_STYLES_ON_COMPONENT_DESTROY token and its default, and why Emulated components are unaffected by removal.
Diagnose styling that disappears on navigation, trace it to a None component whose rules leaked, and fix it by relocating rules rather than flipping the global token.
Weigh style removal against leaked-global dependencies during a major upgrade, and set rules so shared CSS lives in owned global stylesheets.
## The symptom A dashboard page includes `LegacyThemeComponent`, declared with `ViewEncapsulation.None`. Over time other code began to rely on its rules: a non-Angular chart widget and a banner in the app shell are styled by selectors that only exist in that component's stylesheet. After upgrading, users who navigate away from the dashboard see the banner and widget lose their styling. ## How Angular manages component styles Component styles are not bundled into one global CSS file. They are emitted with the component's JavaScript and added to the document by the renderer when the component is rendered: - The first time an instance renders, its styles are added as `<style>` elements (or `<link>` elements for external stylesheets). - Each further instance increases a **usage count** instead of adding a duplicate. - When an instance is destroyed, the count goes down; at zero, the style element is **removed**. The removal step is governed by the `REMOVE_STYLES_ON_COMPONENT_DESTROY` injection token, exported from `@angular/platform-browser`, whose default became `true` in v17. The v22 release added a fix titled "remove unused styles when associated host is dropped", and its breaking-change note reads, in substance: styles are removed when they appear to no longer be used by an associated host; other DOM may still be affected by those styles if it is not using Emulated encapsulation or is outside Angular, and may then appear unstyled. ## Why Emulated components are safe and None components are not | Encapsulation | Who can depend on the styles | Effect of removal | |---|---|---| | `Emulated` | only the component's own elements, via its attribute | nothing else is affected | | `None` | anything in the page matching the selectors | unrelated DOM loses styling | | `ShadowDom` | only the shadow tree | nothing outside is affected | With `Emulated`, the selectors carry the component's own attribute, so when its last instance is gone there is nothing left for them to match. With `None`, the rules are global, and anything that happened to match them silently depended on the component being alive. ## Diagnosing it 1. Inspect the element that lost its style and find the rule in dev tools while the page still works. 2. Check which `<style>` element carries that rule; component styles live in `<style>` elements the renderer added to the document, not in the global stylesheet. 3. Trace that stylesheet to a component, and check its `encapsulation`. 4. Confirm by keeping one instance of the component alive; if the styling returns, removal is the cause. ## Fixing it - **Move shared rules to the global stylesheet.** Anything that must apply regardless of which components are on screen belongs in the application's global styles, which Angular never removes. - **Scope the component again.** If the rules really belong to the component, switch it to `Emulated` and fix the selectors that depended on leaking. - **Style non-Angular DOM deliberately.** A third-party widget should be themed through its own API or a prefixed global rule, not through a component's leaked styles. - **Last resort: the token.** Providing `{ provide: REMOVE_STYLES_ON_COMPONENT_DESTROY, useValue: false }` keeps every destroyed component's styles in the DOM. It hides the dependency instead of fixing it, applies to the whole application, and lets the stylesheet count grow as users visit more pages. ## Common misreadings - **"None styles are global, so Angular leaves them alone."** They are global in reach but still managed as component styles, added and removed by the renderer. - **"Destroying one instance removes the styles."** Removal happens only when the usage count reaches zero, so other live instances keep them in place. - **"The global stylesheet is affected too."** The application's global styles are not component styles and are never removed by this mechanism. - **"It is a bug in the upgrade."** The removal is intended; the bug is a hidden dependency on a component's leaked rules. ## Why the behaviour is worth having Removing unused styles keeps the number of active style rules proportional to what is on screen, which matters in large apps with many lazily loaded routes. It also prevents styles from a page the user left from quietly affecting the next one. The cost falls only on code that let a component's styles act as global CSS, which is the practice worth removing anyway.
- Why doesn't the same removal break an Emulated component?Emulated selectors require the component's own `_ngcontent` or `_nghost` attribute, so they can only ever match that component's elements. When its last instance is destroyed, nothing on the page can match those rules, and removing them has no visible effect.
- What does setting REMOVE_STYLES_ON_COMPONENT_DESTROY to false cost?Every destroyed component's styles stay in the document for the rest of the session, across the whole app. Style count grows as users visit more routes, and styles from pages they left can keep affecting later pages. It hides leaked-style dependencies rather than removing them.
saying these in an interview costs you the question
- Component styles stay in the DOM forever once a component has rendered.
- Angular removes styles only for ViewEncapsulation.None components.
- Setting ViewEncapsulation.None moves a component's styles into the global stylesheet.
- Removing unused component styles was introduced to fix a memory leak in Emulated mode.
- Destroying one instance removes its styles even while other instances remain.