Shadow DOM & View Encapsulation In Angular

Introduction

We know how different styles are applied in Angular. Component styles are placed in the head section but usually, it got added up to the template when we use native shadow dom. We will see how Angular uses the concept of Shadow DOM. 

What is shadow DOM?

It is basically a specification that enables DOM tree and style encapsulation. It also allows us to apply scoped styles to elements without bleeding out to the external world. Shadow DOM allows us to hide the DOM logic behind other elements. Angular uses Shadow DOM for the component template and style encapsulation.
Whenever a component is created, Angular put the component template in a shadowRoot. shadowRoot of a component is the shadow DOM of that component. The main observations regarding the shadow DOM are as follows:
  • All the browsers don't support the concept of shadow DOM. That's why Angular doesn't use the native shadow DOM by default.
  • By default, Angular will not create the shadowRoot. But we can implement it in Angular.
  • We can make web components using the concept of shadow DOM. We can expose components which will have their hidden HTML logic and style encapsulation. Suppose we have some component <input type="color"> element. Using this code, the color picker will render in the browser using this single tag. We can achieve this using Shadow DOM.

ViewEncapsulation in Angular

Angular uses the property encapsulation to create a native shadow dom for our component. In component metadata, we have a property called encapsulation. The value of this is ViewEncapsulation enumeration. Angular comes with view encapsulation option to enable or emulate shadow dom. ViewEncapsulation is an enum defined in angular/core library. This enum has three options:
  • ViewEncapsulation.None - This option will have no Shadow DOM at all.
  • ViewEncapsulation.Emulated - In this, shadow DOM will not be there but style encapsulation is present. This is the default selected option in Angular.
  • ViewEncapsulation.Native - Native Shadow DOM with all its features.
This can be implemented in our component as follows:

import { Component, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-home-page',
  templateUrl: './home-page.component.html',
  styleUrls: ['./home-page.component.scss'],
  encapsulation: ViewEncapsulation.None or 
  encapsulation: ViewEncapsulation.Emulated or
   encapsulation: ViewEncapsulation.Native 
})
export class HomePageComponent implements OnInit {
}

Now we will see how these options work one by one. How these options affect the applied styles to our components.

ViewEncapsulation.None

In this option, Angular will not use shadow DOM concept at all. All the styles applied are written in the head section of the document. All the styles will be applied to the entire component. Even one component can rewrite styles for another component. The styles will leak out to the external world. No shadowRoot is created for the component. Style Encapsulation is not there in this option. 

ViewEncapsulation.Emulated

This option is used by default by Angular. It emulates no Shadow DOM with style encapsulation. This option is useful when we use some other third party components in our application. These components have their own styles which can affect our application. But this option takes care of this. It uses style encapsulation to scope styles. Let us see how Angular works in this option:
Suppose we have a component called master.component.html as follows:

import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-master-page',
template: '<div class="glyphicon glyphicon-star"></div>',
styles: [
     .glyphicon{
     color: red;
       }
],
encapsulation: ViewEncapsulation.Emulated
})
export class MasterPageComponent implements OnInit {

}

Now we will see that the styles are attached to the head section as there is no shadow DOM. 

<head>
  <style>.glyphicon[_ngcontent-1] {
  color: red;
  }</style>
</head>
Angular has added some additional attributes with our selectors such as_nghost-c0, _ngcontent-1, _ngcontent-0, etc. Actually, these are scoped styles without shadow DOM. Angular will apply style encapsulation using these attributes. Angular will make sure that styles are applied to the unique elements using these attributes. These selectors are extended using these additional attributes. So the reason these selectors will not collide with each other. The styles will be applied only to the element that has an additional attribute present. Angular rewrite the styles of our components. Even the template looks as follows:

  <app-root _nghost-c0>
    <div class="glyphicon glyphicon-star"  _ngcontent-1>
    </div>
  </app-root>

The color red style will be applied only to the element which has the same attribute _ngcontent-1.  

ViewEncapsulation.Native

In this option, Angular renders a native shadow DOM with all its features. 

import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-master-page',
template: '<div class="glyphicon glyphicon-star"></div>',
styles: [
     .glyphicon{
     color: red;
       }
],
encapsulation: ViewEncapsulation.Native
})
export class MasterPageComponent implements OnInit {

}

The shadow root will have all the styles placed in the shadow root. Only these styles will be applied to the elements. It emulates shadow DOM. The selector class will not have additional attributes in this case. In this case, no other styles such as global or bootstrap styles will get applied to the element. We have to import the additional styles to our component to have them applied in case of Native Shadow DOM. Practically we will not implement things like this as this will affect our application performance.

<component name _ng-content-c0>
  #shadow-root
  | <style>
  |   .glyphicon {
  |     color: red;
  |   }
  | </style>
  | <div class="glyphicon glyphicon-star"></div>
</component name>

Comments