Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Steve 'Cutter' Blades
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Steve 'Cutter' Blades ( @cutterbl )

Applying CSS Styles To Routable Child Views Using Simulated Encapsulation In Angular 4.4.4

By on

The other day, I mentioned that routable view components, in Angular 4.4.4, don't need to have a defined "selector" property in their @Component() meta-data; but, I did say that including a selector could be helpful for debugging and / or styling the injected view. That said, apply CSS styles to an injected view isn't exactly straightforward when using simulated encapsulation. Since an injected view doesn't receive the simulated encapsulation "content" attribute like its surrounding elements do, we have to get a little clever if we want to safely target the injected view with CSS selectors.

Run this demo in my JavaScript Demos project on GitHub.

When using simulated encapsulation (the default mode) in an Angular component, the component element is given an "nghost" attribute. Then, all of the descendant elements, contained within the host element, are given matching "ngcontent" attributes. Therefore, if you view the rendered source of a component, you should see something like this:

<my-element _nghost-c0>
	<p _ngcontent-c0>
		Notice that I have an ngcontent attribute. And, that both my ngcontent
		attribute and my parent's nghost attribute both end with "-c0". That's
		the local-scoping of the CSS right there!
	</p>
</my-element>

Notice that both the "nghost" and the descendant "ngcontent" attributes all end with "-c0". This suffix will change with every rendered component, keeping the component's styles contained within the current component context.

Now, when it comes to routable child views that are injected into the current view based on the browser URL state, Angular does not add the "ngcontent" to the injected element even though it is contained within a host view:

Injected views dont have ngcontent attributes in Angular 4.4.4.

As you can see, the "my-app" element has an "_nghost-c0" attribute; and, all of its local descendants have the corresponding "_ngcontent-c0" attribute. But, the injected view only has its own "host" attribute - no "ngcontent" attribute. As such, if we want to target the injected view component from within our parent component's CSS, we have to lean on the "::ng-deep" selector and the child combinator (>) to override the view styles:

:host {
	display: block ;
	font-size: 16px ;
}

a {
	color: red ;
	cursor: pointer ;
	text-decoration: underline ;
}

/*
	When Angular injects the routable ng-component into the current component view,
	it DOES NOT ADD the typical "ngcontent-XYZ" HTML attribute that's used to prevent
	styles from bleeding outside of the current component. This means that any attempt
	to use a "naked" View selector in this component CSS will fail to affect an injected
	component. As such, we have to use the DEEP SELECTOR with the CHILD COMBINATOR (>)
	in order to target the injected component.
	--
	NOTE: We're using the child combinator to ensure we don't accidentally bleed our
	::ng-deep styles beyond the scope of the current component.
*/
:host ::ng-deep > layout-a {
	border-color: gold ;
}

:host ::ng-deep > layout-b {
	border-color: aqua ;
}

The ::ng-deep selector prevents the "[_ngcontent-c0]" selector from being added to the remainder of the current selector path. And, the (>) selector prevents the given CSS from being applied to anything internal to the injected view. Together, these two selectors allow us to override the border-color of the injected, routable view component:

Using the ng-deep selector, we can target injected view components in Angular 4.4.4.

As you can see, this leaves us with a CSS selector of:

[_nghost-c0] > layout-b

... which has a higher CSS specificity than the injected view component:

[_nghost-c2]

This allows the "my-app" component to override the border-color of the injected view component without messing up any of the internal styles of the injected view.

The simulated encapsulation functionality for component styling in Angular 4 is pretty awesome. I feel like it maintains the easy, informal nature of CSS without having to deal with the complexity of the "CSS in JavaScript" movement. That said, there are some facets of simulated encapsulation that are fuzzy. Like, how to we deal with the deprecation of "deep selector" in the CSS specification? At least with the ::ng-deep selector, we can still do things like apply styles to injected, routable views in our Angular 4.4.4 components.

Want to use code from this post? Check out the license.

Reader Comments

15,688 Comments

@Jason,

Hmmmm, that's interesting. The last time I read about it, I thought that Angular was going to provide ::ng-deep as the mid-term solution between the deprecation of the /deep/ selector and whatever the future specification will hold. If they are planning to deprecate ::ng-deep, then I am not sure what people can do. This has wide reaching implications. Without some ability to do deep selection, you basically can't have any global CSS that may override something more locally (such as a border color or a background color).

Someone needs to answer this question more satisfactorily! For now, I'll continue to use the ::ng-deep selector, however, since it works currently.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel