Categories:
layout development Angular 2 code & tech
icon-search
Local CSS

Local CSS in Angular 2

Maciek Fulara 28.10.2016

Because of the CSS agressively global nature every CSS rule you write can potentially target unintended elements or overwrite other rules defined before it. The problems created by the lack of scoping support in the language become more painful as the project and the team size grows. With a large code base with thousands of CSS rules you can easily redeclare a class name already defined by someone else without even realizing it. Or you can create a selector that will accidentally be more specific then a selector  currently used to style some element in a remote corner of the application  and, voila, you have just restyled a component you didn’t even knew existed. Add to that external CSS libraries and you’re in real trouble. Now, in order to avoid name clashes, you have to know every name used in the library’s CSS – in other words in order to use a library you need to know all about its internals. How uncool is that?

Attempts at adding scope to CSS

Over the years people have come up with multiple approaches to addressing scoping problems in CSS. Most of them are based on naming conventions / patterns of structuring CSS code. BEM, OOCSS, SMACSS fall into this category. They work reasonably well as long as everyone on the team understands their rules and is willing to strictly stick to them. Another approach is to make CSS styles available directly inside javascript code. It takes advantage of the pluggable architecture of module loaders like Webpack or JSPM – there are adapters that allow you to import CSS directly into your javascript. This way the styles are local to the module into which they have been imported. This approach is popular mostly in the React world.

There have been also attempts at resolving this at the browser level. The first one was scoped <style> tags. The idea was that when you put a <style> tag inside some other tag the styles defined in this <style> tag would only apply to the subtree of the enclosing tag (they would not leak to the outside document). As of writing this article scoped style tags are implemented only in Firefo. What’s more they have been dropped from the current specification and removed from Chrome. They re not likely to come back. The second is WebComponents. In fact, WebComponents are much more general then just scoping CSS, they are about encapsulating everything that constitutes a component: markup, styles and behaviour. They look very promising but browser support is still limited – for now mostly in Chrome. There are polyfills for WebComponents available and a framework built on top of them – polymer.

CSS encapsulation in Angular 2

Angular 2 applications are built from components. Each component encapsulates all its markup and style (strictly speaking style encapsulation is not required – you can still use global css if that’s what you prefer). Having nicely encapsulated components that do not affect, nor are affected by the environment where they are placed makes composing large applications much easier. All you need to know about a component when using it is its public API. No knowlegde of its internals is required, because you are guaranteed they will not leak into the outside world.

Local CSS in Angular 2

When defining local styles in angular 2 components you have 3 options.
  1. Component inline styles. Angular 2 Component decorator has ‘styles’ property where you can define your styles just as you would in a css file. This option feels akward – ‘styles’ is just an array of strings so you will not get any IDE support and you cannot preproces your css
    @Component({
      templateUrl: 'component-template.html',
      styles: [`
        .error {
          color: red;
          font-weight: bold;
        }
      `],
    })
    
  2. Template inline styles. This time the styles are placed in the <style> tag inside the template or the template string.
    @Component({
      template: `
        <style>
          .error {
            color: red;
            font-weight: bold;
          }    	
        </style>
        <span class="error">{{errorMsg}}</span>
        `
    })
    
    or
    @Component({
      templateUrl: 'component-template.html'
    })
    
    component-template.html:
    <style>
      .error {
        color: red;
        font-weight: bold;
      } 
    </style>
    <span class="error">{{errorMsg}}</span>
    
    
  3. External css file. This is the most powerful option as it allows for any CSS preprocessing that you want. You can use SASS or LESS to create your CSS for the component – it’s just a regular CSS file.
    @Component({
      templateUrl: 'component-template.html',
      styleUrls: [path/to/external/css.css, path/to/another/external/css.css]
    })
    
You can mix and match all of the above strategies – in a single component you can have inline styles, template inline styles and external styles if you choose. At Sparkbit we always use external styles. To keep things organized we put component’s styles (to be precise scss sources, as we use SASS) in the same directory as component’s html and typescript.

CSS encapsulation in Angular 2

Having css file per component and having it close to other parts of the component makes the code base easier to read but without a mechanism for encapsulating style this wouldn’t be that useful. Luckily Angular 2 comes with built in component encapsulation with several strategies. Encapsulation strategy is determined by the ‘encapsulation’ field’s value on Component decorator
@Component({
  encapsulation: ViewEncapsulation.None | ViewEncapsulation.Emulated, ViewEncapsulation.Native
})
As you can see there are 3 encapsulation strategies so let’s quickly cover each of them using the following simple component definition:
@Component({
  template: "<span class='error'>{{msg}}</span>",
  selector: "error-message",
  styles: [".error {color: red}"] 
})
class ErrorMessageComponent {
  msg = "A message";
}

ViewEncapsulation.None

As the name suggests there’s no encapsulation here. All component local styles you write, be it inline, template inline or external will simply be appended to the <style> tag added to the head of the document when the component is loaded. Hence they will all become global.
This is how the generated markup will look like:
<head>
  <style>
    .error {
      color: red;
    }
  </style>
<head>
...
<error-message>
  <span class="error">A message</span>
</error-message>

ViewEncapsulation.Emulated

This is the default in Angular 2. When you use this strategy angular will add a randomly generated attribute to all your component elements.
The same attribute is added to the selectors in your CSS rules. This way the selectors match now only elements inside the component.
Generated markup:
<head>
  <style>
    .error[_ngcontent-xav-1] {
      color: red;
    }
  </style>
</head>
...
<error-message _nghost-xav-1>
  <span _ngcontent-xav-1 class="error">A message</span>
</error-message>

You may have noticed there’s also a special attribute added to the component host element. This is so to support WebComponents :host pseudo selector.

ViewEncapsulation.Native

This strategy can be used only with browsers that support WebComponents. When you use it angular will turn your component in a real WebComponent. All content of the component is placed inside the Shadow DOM, styles are placed in a <style> tag inside the Shadow DOM. Note that Shadow DOM is a whole separate DOM tree and no styles from the outside can affect how it’s rendered.
<error-message>
  #shadow-root
    <style>
      .error {
        color: red;
      }
    </style>
    <span>A message</span>
</error-message>

Global / shared CSS in Angular 2

There are cases where you want to share some styles among some (or maybe all) of your components. Your options vary depending on the view encapsulation strategy you choose.

With ViewEncapsulation.None everything is global.

With both ViewEncapsulation.Emulated and ViewEncapsulation.Native you can repeat the common bits of style in every component that needs them or split them into a separate file and link that file in every component that needs it. Repeating may be a viable option where you have just small bits of style and you are using SASS/LESS. In that case the common chunks can go into their own file that is @imported during CSS build into each component’s CSS. Having common CSS in split into separate files is possible because ‘styleUrls’ property is an array – you can link multiple css files in a component.

With ViewEncapsulation.Emulated all global CSS rules that you define do affect your component.

Let’s say we have the following component:

@Component({
  template: "<span class='error'>{{err}}</span><span class='warning'>{{warn}}</span>",
  selector: "message",
  styles: [".error {color: red}"] 
})
class AlertComponent {
  err = "Error";
  warn = "Warning";
}
And this CSS is added to the page:
.error {
  color: blue;
}
.warning {
  color: green;
}
“Warning” will be displayed in green (because it has “warning” class and the global CSS has a rule for this class) and “Error” in red (even though both global and component’s local CSS have a rule matching “error” class, the selector created by angular for component’s local style is more specific).

This will not work with ViewEncapsulation.Native – with WebComponents the Shadow DOM boundary is impenetrable to CSS selectors. So in the above example “Error” would be in red (local CSS rule) and “Warning” in black (default – no local rule for “warning” class).

On a final note we have to remember that CSS encapsulation applies to selectors not style properties. That means that CSS selectors do not match across component boundaries. But style properties are inherited across component boundaries. For example, if we had a WebComponent (or ng2 component with any encapsulation strategy we choose) that does not define a color property and placed it inside a component that has color set to red it would display all its text in red.

So in the following example ‘This is an example’ will be displayed in red:

@Component({
  template: "<span>This is an example</span>",
  selector: "example",
  encapsulation: ViewEncapsulation.Native
})
class ExampleComponent {}
<div style="color:red">
  <example></example>
</div>

Summary

Lack of namespacing in CSS makes it difficult to develop and maintain large code bases. Angular 2’s view encapsulation offers a mechanism to isolate CSS rules inside the component so that they don’t affect the rest of the application.

comments: 0