Emulated Encapsulation Host And Content Attributes Are Calculated Once Per Component Type In Angular 6.1.10
Yesterday, while experimenting with recursive components in Angular 6.1.10, I was briefly confused by the fact that recursive nesting of a single component required a little bit of CSS chicanery in order to prevent CSS styling from bleeding down into descendant nodes. Recursion tends to mess your head up; that's a right of passage. After I was done with my experiment, however, and I had some time to think more deeply, the CSS behavior made sense. Emulated encapsulation host (_nghost-%COMP%) and content (_ngcontent-%COMP%) attributes are calculated once per component type, not once per component rendering. As such, it makes sense that nested, recursive components would have the same host and content attributes.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
To recap the issue that I was seeing yesterday, I had a component, my-tree-node, that was rendering itself recursively. And, for each instance of the component in the DOM, I was seeing the same nghost and ngcontent attributes. It looked something like this:
<my-tree-node _nghost-c2>
<my-tree-node _nghost-c2 _ngcontent-c2>
<my-tree-node _nghost-c2 _ngcontent-c2>
<my-tree-node _nghost-c2 _ngcontent-c2>
<!-- ... -->
</my-tree-node>
</my-tree-node>
</my-tree-node>
</my-tree-node>
As you can see, each instance of my-tree-node was assigned the same nghost and ngcontent attributes. And, because the components were nested, changing a style to something like:
.selected[ _nghost-c2 ] label[ _ngcontent-c2 ] {
color: red ;
}
... would end up affecting the current my-tree-node label as well as all of the labels in the nested my-tree-node components in the current Document Object Model (DOM) branch. Since they all have the same emulated encapsulation attributes, there was nothing at the "CSS selector" level that would prevent the CSS properties from "leaking" down into another instance of the same component.
At first, this felt like a bug since the goal of the emulated encapsulation is there to prevent CSS styling from leaking out of a component. But, the encapsulation is only an "emulation". As such, there are edge-cases, like recursion, where special steps need to be taken. In my case, I fixed my leaking-style issue by including a "direct descendant" selector (>) for my label.
That said, I was confused because I didn't understand the nature of the emulated style encapsulation. It's not based on "instances", it's based on "types" (component types). As such, the nghost and ngcontent attributes are only calculated once per application life-cycle; and, only one Style element per component-type is injected into the document Head, regardless of how many instances of said component are being rendered in the active view.
To demonstrate this (to myself), I created three components: ThingAComponent, ThingBComponent, and ThingCComponent. Then, I created an AppComponent that renders each of these components twice. And, to make it more fun, I decided to have ThingBComponent also render the ThingCComponent internally. Ultimately, what we get is a view that has 2 instances of ThingAComponent and ThingBComponent and 4 instances of ThingCComponent. And still, we only see one Style-per-type injected into the Head:
As you can see, despite the fact that we have 4 instances of the ThingCComponent at various levels in the component tree structure, we still only get one relevant Style tag injected into the document Head. This is because the Style tags and the nghost and ngcontent attributes are based on the component Types, not the component instances.
In the vast majority of cases, the emulated encapsulation in Angular 6.1.10 "just works". This is why I've never needed to have a solid mental model for how the magic happens. Once I ran into the edge-case of recursively rendered components, however, it was time to step back and take a look at the fundamentals. And, now that I understand that the simulated encapsulation is based on component Types - not instances - the runtime styling makes a lot more sense.
Want to use code from this post? Check out the license.
Reader Comments