Angular: style encapsulation 101

Sergey Gultyayev
5 min readJun 26, 2022

Despite the fact that one type of emulation is being used in 99% of all cases we need to know of all available options to make better choices during the development.

Fig. 1. Comparison of generated styles and their generated styles

Let’s start with the basics. What is style encapsulation? It’s a mechanism to prevent style leak from our components i.e. when we define styles for a ComponentA we don’t want any of its styles to be applied elsewhere in the application apart from its template.

Encapsulation type is specified in components’ metadata under encapsulation property. For example

@Component({
selector: 'app-card',
template: '<ng-content></ng-content>',
styles: [style],
standalone: true,
encapsulation: ViewEncapsulation.Emulated,
})
export class CardComponent {}

If we don’t specify one, Emulated is used by default.

Emulated

This is available in all browsers and the principle of work is pretty straightforward. Each component is given a unique ID. Then this component’s tag receives an attribute in format _nghost-uniqueID and tags defined in its template _ngcontent-uniqueID , then these attributes are add for all component’s style declarations.

This way, all styles you define are prefixed with the _ngcontent-uniqueID /_nghost-uniqueID selector which:

  1. Increases the selector’s specificity
  2. Applies styles to tags with such attributes and leaves all others intact

It’s worth mentioning that if you have a ComponentB used inside a ComponentA, ComponentA’s styles won’t get inside the ComponentB, because its template is not defined as part of the ComponentA. Therefore, it’s safe and sorts of intuitive working with the default encapsulation strategy.

On figure 1 we can see the generated attribute for the component and its corresponding styles attached in the <head> tag.

With Emulated strategy we have some extra selectors:

  • :host — allows us to target our host component’s selector (_nghost attribute) to style the component itself.
  • ::ng-deep — allows us to target children that are not defined in the template (other components, innerHTML).
    What it does is that when we use .container ::ng-deep p it creates styles with selector .container[_ngcontent] p (notice the absence of _ngcontent at p ). I.e. all selectors used after ::ng-deep don’t have any component related encapsulation for selectors.
    Therefore it should be used with care. If you use selector ::ng-deep p it will create a simple style declaration p which means that this style will become global and you will spend hours guessing where those styles come from.
  • :host-context allows targeting our component’s parents.
    Let’s say we have two themes for our app. We could set a class on our app-root component .dark /.bright and then define style for the component, this will give us a following couple of selectors
    app-root.dark[_nghost] p[_ngcontent],
    app-root.dark [_nghost] p[_ngcontent] this way the styles will be applied only of app-root has a class applied dark .

None

This is available in all browsers as well as it does not do anything with the styles. It’s pretty much the same as if you declared those styles globally with the exception that styles appear in the DOM only when the component was first mounted.

In this mode we cannot use such selectors as :host , ::ng-deep etc. as those are ShadowDOM and ShadowDOM-ish selectors which are available only in Emulated and ShadowDom modes.

For styling hosts in None you would need to apply a class to your host and use it as a selector or use a tag’s selector for styling.

On figure 1 it can be seen that :host style declaration is attached as is and, naturally, it’s not working on the element.

ShadowDom

Shadow is a concept for Web Components which creates a separate tree attached to a host component and makes its content unreachable for outer styles and JavaScript. It can be open and closed. Closed mean that elements cannot be reached by JavaScript by any means. Open, which Angular is using, means that we can get to the elements using special Shadow DOM API.

Since Shadow DOM is not something that has been with us from the day 0, not all browsers support it. Today it has a great 93% of the World Wide support. However, a one important note here — despite the high support level, not all features of Shadow DOM are supported equally well. For example, :host-context has only 71.65% of support with an absolute absence on Firefox and Safari, so generally only WebKit browsers support it.

When it’s used, a shadow root is being attached to the component and all its children are completely isolated from the styles defined elsewhere. Nothing gets in, nothing gets out.

With this peculiarity Angular has to copy all the components’ styles in the shadow root once again, because we are likely to use other components as well. That’s why on figure 1 we can observe same styles being populated in the shadow root.

When ShadowDom is used we also can work with Shadow DOM specific CSS selectors such as:

  • :host — selects the host component
  • :host-context(<selector>) — selects the host if it’s in the hierarchy under the specified <selector>
  • ::part(<part-name>) — selects an element with part="part-name" attribute specified, which is used for styling elements withing Shadow DOM from outside.

Which one to chose?

As always it all depends on your needs. For most of the projects using default Emulated encapsulation is good enough and there is no need to change it.

“None” and ShadowDom are good choices for library authors. “None” would be prevalent, because using only class selectors will make it easier for consumers to override styles. It does come with cost that your class names must be unique enough not to interfere with any of the consumers’ pre-existent classes.

ShadowDom will be a good choice for ensuring that your components’ styles won’t be affected by your consumer’s styles. It will be a good choice for Angular Elements (building Web Components using Angular) so that nothing affects your component.

--

--

Sergey Gultyayev

A front-end developer who uses Angular as a main framework and loves it