Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the jQuery Conference 2011 (Cambridge, MA) with: Doug Neiner
Ben Nadel at the jQuery Conference 2011 (Cambridge, MA) with: Doug Neiner@dougneiner )

Moving Core Application CSS Styles Into The Root Component In Angular 2.0.0

By Ben Nadel on

By default, pretty much all of my JavaScript demos have a "demo.css" file that I link-to within the "index.htm" page. But, I'm starting to wonder if this makes sense for my Angular 2 demos. In Angular 2, we can easily use emulated style-encapsulation within our component directives; which makes me reconsider how much CSS is actually "owned" by the index page itself? And, should most of that CSS be moved into the component definition?


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

In my Angular 2 demos, the index page has basically nothing on it. Usually just an H1 tag and the "my-app" root Angular 2 component. This is the only visibility that the index page has into what is being rendered on the page. The rest of the content is completely encapsulated inside the Angular 2 ecosystem. So, perhaps it makes sense that the only thing the index page knows how to render is the Body tag and the H1 tag?

Here's what this might look like:

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Moving Core Application CSS Styles Into The Root Component In Angular 2.0.0
  • </title>
  •  
  • <style type="text/css">
  • /*
  • Technically, the only non-app styles in this demo would be the BODY and the
  • H1 tags (and any external drivers for the MY-APP element, for example if it
  • needed to be positioned). The rest of the content and its relevant styles
  • belong to the app itself.
  • */
  • body { /* ... */ }
  • h1 { /* ... */ }
  • my-app { /* ... */ }
  • </style>
  •  
  • <!--
  • While the use of fonts may "belong" to the app, common font usage poses an
  • interesting problem. When using remote font providers, like Google Fonts, the
  • font file is loaded from a different domain and it's loaded based on the current
  • user-agent. As such, the content of the remote CSS file cannot be known ahead of
  • time. And, what's more, Angular will skip any remote file URL in the "styleUrls"
  • component meta-data. All to say, fonts have to be loaded in the root page by the
  • current browser consuming the application.
  • -->
  • <link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Open+Sans:400,700"></link>
  •  
  • <!-- Load libraries (including polyfill(s) for older browsers). -->
  • <script type="text/javascript" src="../../vendor/angular2/2.0.0/node_modules/core-js/client/shim.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angular2/2.0.0/node_modules/zone.js/dist/zone.js"></script>
  • <script type="text/javascript" src="../../vendor/angular2/2.0.0/node_modules/reflect-metadata/Reflect.js"></script>
  • <script type="text/javascript" src="../../vendor/angular2/2.0.0/node_modules/systemjs/dist/system.src.js"></script>
  •  
  • <!-- Load the Web Animations API polyfill for most browsers (basically any browser other than Chrome and Firefox). -->
  • <!-- <script type="text/javascript" src="../../vendor/angular2/2.0.0/node_modules/web-animations-js/web-animations.min.js"></script> -->
  •  
  • <!-- Configure SystemJS loader. -->
  • <script type="text/javascript" src="./system.config.js"></script>
  • </head>
  • <body>
  •  
  • <h1>
  • Moving Core Application CSS Styles Into The Root Component In Angular 2.0.0
  • </h1>
  •  
  • <my-app>
  • Loading....
  • </my-app>
  •  
  • </body>
  • </html>

To illustrate what the index page is styling, I'm using an inline Style block as opposed to a linked stylesheet. And, as you can see, it just has (mock) definitions for the body, h1, and my-app elements. Because, this is all the index page knows about - this is the entirety of its insight into the working of the page.

Now, I'm also linking to a remote Font file from Google Fonts. It turns out, remote Fonts pose an interesting problem. Most CSS styling can be statically understood at compile time, when Angular is stitching together all the things. But, remote font files are different; not only is the content of the font file driven by the User-Agent of the requesting browser (I believe); but, its remote nature means that it can't be known at compile time. As such, I believe that remote font files still need to be owned by the index page. That said, I may be thinking about this incorrectly - if so, please let me know!

As we move CSS out of the index page and into the domain of the Angular 2 components, we have to think about "base styles." Meaning, the general look and feel of the application itself, as opposed to the per-component specific stylings. Things like font-family, font-color, and margins for generic content.

For context, let's look at the root component:

  • // Declare ambient module definition so TypeScript doesn't complain.
  • // --
  • // TODO: Figure out how to move this to typing files.
  • declare var module: { id: string };
  •  
  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • @Component({
  • moduleId: module.id,
  • selector: "my-app",
  • styleUrls: [ "./app.component.css" ],
  • template:
  • `
  • <h2>
  • Welcome to Thunderdome!
  • </h2>
  •  
  • <p>
  • <a (click)="toggleChild()">Toggle child component</a>.
  • </p>
  •  
  • <my-child *ngIf="isShowingChild"></my-child>
  • `
  • })
  • export class AppComponent {
  •  
  • public isShowingChild: booldean;
  •  
  •  
  • // I initialize the component.
  • constructor() {
  •  
  • this.isShowingChild = false;
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I show or hide the child component based on the current state.
  • public toggleChild() : void {
  •  
  • this.isShowingChild = ! this.isShowingChild;
  •  
  • }
  •  
  • }

Here, you can see that the root component has a "P" tag in its template. It's possible that this particular P tag should have very specific styling; but, it's much more likely that this P tag should render like every other P tag in the application. And, if the index page won't tell us what that's supposed to look like (because it doesn't know), the root component of our application must.

Of course, the [default] emulated encapsulation scopes all style rules to the current component. Fortunately, Angular gives us a work-around with the "deep" operator:

>>>

This "deep" operator allows us to define CSS selectors that purposefully bleed into the descendants of the current component. When we include this in our CSS selectors, no scoping will be applied to selectors that come after the deep operator. To see this in action, let's look at the CSS file associated with the root component. You'll notice that this file contains component-specific styles as well as "base" styling to be inherited by the rest of the application:

NOTE: In retrospect, I should have probably broken these two concerns up into two different CSS files and loaded them individually in the styleUrls component meta-data.

  • :host {
  • border: 1px dashed #AAAAAA ;
  • border-radius: 5px 5px 5px 5px ;
  • color: #333333 ;
  • display: block ;
  • font-family: "Open Sans", sans-serif ;
  • padding: 20px 20px 20px 20px ;
  • }
  •  
  • /*
  • NOTE: Angular will prepend the component unique identifier to the :first-/:last-
  • child pseudo-selectors. So, they end up like "[ _ngcontent-id_3z ]:first-child".
  • */
  • :host > :first-child {
  • margin-top: 0px ;
  • }
  •  
  • :host > :last-child {
  • margin-bottom: 0px ;
  • }
  •  
  • /*
  • When CSS is rendered for the Angular components, it adds a unique identifier to each
  • CSS selector. This applies to compound CSS selectors as well. So, for example, if we
  • had:
  •  
  • :host h2 {}
  •  
  • ... Angular would actually render it as something like:
  •  
  • :host[ _nghost-id_3z ] h2[ _ngcontent-id_3z ] {}
  •  
  • ... which means our CSS won't bleed down into the nested components. Of course, in
  • a root app component, this might be exactly what we want for a reset or a base CSS
  • definition. To get around this, Angular provides a way to turn off the unique
  • identifier on compound selectors: "/deep/" or, as an alias, ">>>".
  • */
  •  
  • :host /deep/ h2 { /* example using /deep/. */
  • color: #555555 ;
  • }
  •  
  • :host >>> h3 { /* example using >>>. */
  • color: #777777 ;
  • }
  •  
  • :host >>> p {
  • font-size: 18px ;
  • }
  •  
  • :host >>> a {
  • color: red ;
  • cursor: pointer ;
  • text-decoration: underline ;
  • user-select: none ;
  • -moz-user-select: none ;
  • -webkit-user-select: none ;
  • }

Here, you can see that we are defining "deep" styles for the h2, h3, p, and a elements. These are the "base" styles for the application; and, they are provided by the root component because the root component is the only component that can truly claim to have a holistic understanding of how the application should look and function.

To test this cascading of styles, I included a child component that uses some of those base tags:

  • // Declare ambient module definition so TypeScript doesn't complain.
  • // --
  • // TODO: Figure out how to move this to typing files.
  • declare var module: { id: string };
  •  
  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • @Component({
  • moduleId: module.id,
  • selector: "my-child",
  • styleUrls: [ "./my-child.component.css" ],
  • template:
  • `
  • <h3>
  • What a Wonderful Day!
  • </h3>
  •  
  • <p>
  • Good morning my friend.
  • </p>
  • `
  • })
  • export class MyChildComponent {
  •  
  • // I initialize the component.
  • constructor() {
  •  
  • // ...
  •  
  • }
  •  
  • }

And, in case you're curious, the my-child component has its own CSS file, of course:

  • :host {
  • border: 1px dashed #AAAAAA ;
  • border-radius: 5px 5px 5px 5px ;
  • display: block ;
  • padding: 20px 20px 20px 20px ;
  • }
  •  
  • /*
  • NOTE: Angular will prepend the component unique identifier to the :first-/:last-
  • child pseudo-selectors. So, they end up like "[ _ngcontent-id_4a ]:first-child".
  • */
  • :host > :first-child {
  • margin-top: 0px ;
  • }
  •  
  • :host > :last-child {
  • margin-bottom: 0px ;
  • }

Now, when we render this page, we can see that the "deep" styles provided by the root component cascaded down into the child component:


 
 
 

 
 Moving core CSS out of the index file and into the root Angular 2 component. 
 
 
 

As you can see, each component has its own specific styling; however, the root component successfully provided "base" styling that created a cohesive look and feel for the application.

Now, you might take objection to the idea of "cascading" styles. It seems that in recent years, the "C" in "CSS" (Cascading Stylesheets) has come under much criticism. Personally, I'm not good enough with CSS to have much of an opinion on the matter. That said, I still think it's worthwhile to consider the ownership of the CSS; no matter how you architect your styles, it would seem that the index page should know next to nothing about it. And, it's nice that Angular 2 makes this fairly easy to do.

Anyway, just something I've been noodling on lately. I'm trying to take my older thinking about CSS and apply it to a modern, componentized mental model. Still very open to suggestions on this topic, to be sure.




Reader Comments

Interesting idea and was thinking about this. But also the performance and download size benefits of this concept need to be test for huge active user website and client device compatibility.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.