Friday, October 18, 2019

Angular css style and view encapsulation

For angular project, the style defined in component's css file is encapsulated for the current component, so the style does not apply to the global scope, or the indirect children components contained within the current component.

For example, appComponent has the below html and css definition

html:
<p>The app component title</p>
<p myattribute>the app component content</p>
<div class="container" style="background-color:yellow;">
  <div class="row">
    <div class="col-sm" >
      <app-cmp1></app-cmp1>
    </div>
    <div class="col-sm">
      <app-cmp2></app-cmp2>
    </div>
  </div>
</div>
css:
p {
    background-color: green;
    font-weight: bold;
}
p[myattribute] {
    background-color: pink;
}
Then when the app is running on browser, the defined css style will automatically be added an attribute selector as 

p[_ngcontent-gio-c0] {
    background-color: green;
    font-weight: bold;
}

p[myattribute][_ngcontent-gio-c0] {
    background-color: pink;
}

where _gncontent_gio-c0 is the random attribute added to the elements defined in appComponent.htm. The runtime element of appComponent looks like as below:
Note every elements defined in appcomponent.html has the new ng attribute of _ngContent-gio-c0, so the defined css style in appcomponent.css can be applied to them.
Similarly, the child element of app-cmp1 element has _ngcontent_gio_c1 defined on it. as a result, any indirect html elements defined in app-cmp1 will not inherit the css style defined in the parent components of appcomponent, as they have a different attribute generated by angular..

Css style files added into compoent's styleURls will always has this ng style attribute selector added for it when inserting the styles into index.html. However, styles files added in component.html will be handled differently depending on whether full url or relative url are used to in style link's href url. If relative url (without http(s) scheme) is used, then the style will have ng attribute selector.

<link rel="stylesheet" href="assets/mystyles.css">

If absolute url (with http(s) scheme) is used, then the style will not have ng generated attribute, so the style will be applied globally, and may affect other elements by accident.
<link rel="stylesheet" href="http://localhost:4200/assets/mystyles.css">
There are two additional ways to add external css style files to angular project:
1. set global css style by adding the css style url in the project's styles.css file, as mentioned in its comment of  "/* You can add global styles to this file, and also import other style files */

2. in the web component's css file, add the external css file with @import statement as shown below:
@import "https://maxcdn.bootstrapcdn.com/bootstrap/4.3.0/css/bootstrap.min.css";

Now the question come, how can we set the style to the html elements of a web component from the holding html page of the web component. For example, how to set style of p element of app-cmp1 from root appComponent.html and its css file. The easy way to do so is applying ::ng-deep to styles. ::ng-deep will remove the ng attribute selector when adding css style at runtime, so it makes the style global available. Note ng-deep is marked as deprecated, although no alternative available for now.
::ng-deep p {
    background-color: pink;
}
Other than ::ng-deep, another option is using component module configuration's encapsulation settings. 
ViewEncapsulation.ShadowDom 
ViewEncapsulation.None
which will allow you to set the global css style from your component's css code. ShodowDom is a better option without the need to generate the random angular attribute id, but it is not supported by all browser, particularly by IE. When using ViewEncapsulation.None, be sure to limit the scope of the css style to be applied, so it will not affect other elements by accident.

One solution of using ViewEncapsulation.None without polluting the angular project's global css style is, using the regular css style for general web component css styles. But creating a separate web component with empty html element, and special css styles that need to be applied globally, so that only a very limited css styles are exposed by this dummy web component.

Note:
1. external css url (from remote cdn) starting with http or https  cannot be directly added into component's ts file's styleUrls list. If external css style should only be applied to a particular web component, then the recommended way is importing the whole npm package (like bootstrap or material design) into the project, and then add the css file from local relative path into the component's style files. For example, the below code in a webcomponent's css file import another css file from a different package (from @ng-select). In this way, when loading the css style file, it will have the angular generated attribute, so those css style will not be applied globally.
@import "~@ng-select/ng-select/themes/default.theme.css";
@media screen and (max-width: 769px) {
    .container-size{
        margin: 1.2em;
    }
}
2. when external css file loaded from web component's html file's as css link reference, the css styles are added into DOM tree without angular random id. The reason of why the style definition can still be applied to web component's html elements is, those styles are applied globally to all elements, no matter the elements have the angular generated attribute or not.

No comments:

Post a Comment