Many Angular developers and layout designers who write CSS/SCSS code in Angular applications have encountered a situation where they need to apply styles to a component nested in the current one and, without fully understanding how it works, turned off style encapsulation or added ng-deep, while not taking into account some nuances, which later leads to problems. In this article, I will try to present all the details as simply and concisely as possible.
When a component has style encapsulation enabled (it is enabled by default and should be left enabled in most cases), the styles contained in the component's style file\files will only apply to the components' elements. This is very convenient, you don't have to keep track of the uniqueness of the selectors, you don't have to use BEM or come up with long class names and keep them unique, although you can still do that if you want. During the creation of each component, Angular itself will add a unique attribute to all elements inside the component, for example, _ngcontent-ool-c142
and replace your .my-class
selector with .my-class[_ngcontent-ool-c142]
(this is in the case of ViewEncapsulation.Emulated
, which is enabled by default, if you specify ViewEncapsulation.ShadowDom
the behavior is different but the result is the same).
Now let's imagine that we have a component named ComponentA
<div class="checkbox-container">
<mat-checkbox>Check me</mat-checkbox>
</div>
that has a mat-checkbox from Angular material nested inside it (this can be your own component, not necessarily components from libraries).
Inside mat-checkbox there is a label that we want to add a border to.
<mat-checkbox>
<label>
...
If we write in the component's style file,
mat-checkbox label {
border: 1px solid #aabbcc;
}
then after applying ViewEncapsulation.Emulated
the selector will be something like this
mat-checkbox[_ngcontent-uiq-c101] label[_ngcontent-uiq-c101] {
border: 1px solid #aabbcc;
}
i.e. the border will be applied to the label with the _ngcontent-uiq-c101
attribute, but all child elements inside the mat-checkbox
will have a different attribute since the label is inside another component and it will either have an attribute with a different ID (id of mat-checkbox
component), or it will not exist at all if the component, in turn, has encapsulation disabled (in our case, there will be no attribute at all, because mat-checkbox, like other components from Angular Material has ViewEncapsulation.None
).
Thus, styles restricted by the ComponentA
component attribute only apply to elements directly inside that component. If the component contains another component, then these styles no longer apply to its elements.
If you're wondering exactly how Angular's Emulated encapsulation works, you can find a lot of detailed articles on the subject, but here I'll give a very brief description so as not to bloat the article. If the component has encapsulation, then the _nghost-ID
attribute will be added to the component itself, and the _ngcontent-ID
attribute will be added to each nested element, and [_ngcontent-ID]
will be added to all styles in this component. This way all styles will be applied ONLY to the elements inside that component.
What if we need to apply styles to the elements inside the nested component (in our example, to the label inside the mat-checkbox)
In order to apply styles, we have three options:
- disable style encapsulation in ComponentA
- use ng-deep
- place css code in global styles (those in styles.(s)css or in other files specified in the styles section in angular.json)
Let's take a closer look at them
ViewEncapsulation.None
In this case, all styles inside the component will become "global", and this will happen only after the component is created, i.e. after the user has visited the section of the application where this component is used, which makes it very difficult to identify this problem. Let's turn off style encapsulation on our component.
@Component({
selector: 'app-component-a',
templateUrl: './component-a.component.html',
styleUrls: ['./component-a.component.scss'],
encapsulation: ViewEncapsulation.None
})
remember that in the style file we have this
mat-checkbox label {
border: 1px solid #aabbcc;
}
until the user has opened the page where component A is used, all other mat-checkboxes in the application look borderless, but after component A has rendered, the css code above will be dynamically added to the section in the DOM tree and after then all mat-checkboxes will use these styles. <br> In order to prevent this apparently undesirable effect, we can limit the scope of styles by applying a more specific selector. For example, let's add the "checkbox-container" class to the mat-checkbox's parent element, </p> <div class="highlight"><pre class="highlight html"><code> <span class="nt"><div</span> <span class="na">class=</span><span class="s">"checkbox-container"</span><span class="nt">></span> <span class="nt"><mat-checkbox></span>Check me<span class="nt"></mat-checkbox></span> <span class="nt"></div></span> </code></pre></div> <p>and fix the selector to this</p> <div class="highlight"><pre class="highlight css"><code> <span class="nc">.checkbox-container</span> <span class="nt">mat-checkbox</span> <span class="nt">label</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p>now only checkboxes located inside an element with the checkbox-container class will get the border. But instead of adding a class with a unique name and making sure they're not repeatable, it's much easier to use a component selector, because it will be unique</p> <div class="highlight"><pre class="highlight css"><code> <span class="nt">app-component-a</span> <span class="nt">mat-checkbox</span> <span class="nt">label</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p><strong>Conclusion: if you turn off encapsulation, don't forget to add the component selector to ALL styles inside the component, in case of SCSS\SASS, just wrap all code in:</strong></p> <div class="highlight"><pre class="highlight css"><code> <span class="nt">app-component-a</span> <span class="p">{</span> <span class="err">...</span> <span class="p">}</span> </code></pre></div><h2> <a name="pseudoclass-ngdeep" href="#pseudoclass-ngdeep" class="anchor"> </a> Pseudo-class ng-deep </h2> <p>Now let's turn encapsulation back on by removing encapsulation: <code>ViewEncapsulation.None</code> from the <code>@Component</code> decorator. And add the <code>::ng-deep</code> selector to the css</p> <div class="highlight"><pre class="highlight css"><code> <span class="nd">::ng-deep</span> <span class="nt">mat-checkbox</span> <span class="nt">label</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p><code>ng-deep</code> will force the framework to generate styles without adding attributes to them, as a result, this code will be added to the DOM:</p> <div class="highlight"><pre class="highlight css"><code> <span class="nt">mat-checkbox</span> <span class="nt">label</span><span class="p">{</span><span class="nl">border</span><span class="p">:</span><span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">}</span> </code></pre></div> <p>which will affect all mat-checkbox applications, just like if we added it to global styles or turned off encapsulation as we did earlier. To avoid this behavior, we can again restrict the scope to the component selector</p> <div class="highlight"><pre class="highlight css"><code> <span class="nd">::ng-deep</span> <span class="nt">app-component-a</span> <span class="nt">mat-checkbox</span> <span class="nt">label</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p>or do it even simpler and use the <code>:host</code> pseudo-class</p> <div class="highlight"><pre class="highlight css"><code> <span class="nd">:host</span> <span class="nd">::ng-deep</span> <span class="nt">mat-checkbox</span> <span class="nt">label</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <p>which is much more convenient and reliable (imagine if you renamed the component selector and forgot to change it in the css code). <br> How does it work? Very simple - Angular will generate in this case the following styles</p> <div class="highlight"><pre class="highlight css"><code> <span class="o">[</span><span class="nt">_nghost-qud-c101</span><span class="o">]</span> <span class="nt">mat-checkbox</span> <span class="nt">label</span><span class="p">{</span><span class="nl">border</span><span class="p">:</span><span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">}</span> </code></pre></div> <p>where <code>_nghost-qud-c101</code> is the attribute added to our <code>ComponentA</code>, i.e. the border will apply to all labels inside any mat-checkbox lying inside an element with the <code>_nghost-qud-c101</code> attribute, which ONLY <code>ComponentA</code> has.</p> <div class="highlight"><pre class="highlight html"><code> <span class="nt"><app-component-a</span> <span class="na">_ngcontent-qud-c102</span> <span class="na">_nghost-qud-c101</span><span class="nt">></span> </code></pre></div> <p><strong>Conclusion: if using ::ng-deep ALWAYS add :host or create a mixin and use it everywhere</strong></p> <div class="highlight"><pre class="highlight css"><code> <span class="k">@mixin</span> <span class="n">ng-deep</span> <span class="p">{</span> <span class="nd">:host</span> <span class="nd">::ng-deep</span> <span class="p">{</span> <span class="err">@content;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">@include</span> <span class="n">ng-deep</span> <span class="p">{</span> <span class="nt">mat-checkbox</span> <span class="nt">label</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#aabbcc</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div> <p>Many developers are confused by the fact that <code>ng-deep</code> has been marked as deprecated for a long time. The Angular team had plans to deprecate this pseudo-class, but that decision was later shelved indefinitely, at least until new alternatives come along. If we compare <code>ng-deep</code> and <code>ViewEncapsulation.None</code>, then in the first case, we at least turn off encapsulation not for all component styles, but only for those that we need. Even if you have a component where all the styles are for child components, ng-deep seems to be more advantageous because you can later add styles for the component's own elements, in which case you just write them above/below the code nested in <code>:host ::ng-deep {}</code> and they will work as usual, but with encapsulation disabled, you no longer have this option.</p> <p>Finally, I want to add a few words about how to βstyleβ components from libraries. If you need to change the default view for, say, all mat-selects in your application, it's often best to do so in global styles. Sometimes, some developers prefer to put these styles in a separate SCSS file and import it wherever needed, but in this case, when building the project, these styles are duplicated in each chank (compiled js file for a lazy or shared module / group of modules) , where at least one of the components included in this chank uses this style file, which can significantly increase the total size of the bundle. Therefore, this practice should be avoided.</p>
Top comments (1)
Love the tip about using mixins for host! I repeat that often without considering this option