Angular — Advanced Styling Guide (v4+)

Gerard Sans
Google Developer Experts
8 min readFeb 20, 2017

Learn how to style like a pro using Shadow DOM selectors, Light DOM, @HostBinding, ElementRef, Renderer, Sanitizer and much more

Geometric Shapes by Sasj

In this guide we want to cover the different options available when styling Angular Components and Directives. We will cover:

You can explore the final code by using this Plunker.

Find the latest Angular content following my feed at @gerardsans.

Introduction

Styling Angular Applications has never been more flexible. Angular Component Architecture offers a new styling model that isolates Component styles by using Shadow DOM (emulated or native) a technology from Web Components specification. Styles are scoped for each Component so they can’t affect other areas of the UI.

For this post we are going to use a Component to render song tracks showing some of the different styling options. This component will render the cover, title and artist for a song.

@Component({
selector: 'song-track', // <song-track></song-track>
})
export class SongTrack { }

See the final result below.

Angular Encapsulations Modes

Let’s quickly review all available encapsulation modes before further exploring the different styling approaches.

Emulated (default)

When using this mode Angular will identify each component using two unique attributes: _nghost-* and _ngcontent-*. Any component styles will be added to the head using these attributes to isolate the styles as in the example below.

<head>
<style>
.container[_ngcontent-ikt-1] { ... }
</style>
</head>
<body>
<my-app>
<song-track _nghost-ikt-1>
<div _ngcontent-ikt-1 class="container"></div>
</song-track>
</my-app>
</body>

Note the attributes added to the root and content of our component in bold. You can explicitly activate this mode by using the code below

@Component({
selector: 'song-track',
encapsulation: ViewEncapsulation.Emulated
})

Emulated encapsulation achieves the best support across browsers.

Native Encapsulation

This encapsulation will set Angular to use Native Shadow DOM for an specific Component. Depending on the browser this will be v1 of the specification (Chrome).

@Component({
selector: 'song-track',
encapsulation: ViewEncapsulation.Native
})

This will render the following.

<body>
<my-app>
<song-track>
▾ #shadow-root (open)
<style>.container { ... }</style>
<div class="container"></div>
</song-track>
</my-app>
</body>

Note how the styles are now encapsulated under #shadow-root. We will cover the specific styling options later on.

Native Encapsulation is not supported in some browsers. Check current support here.

Disabling Encapsulation

We can also disable encapsulation altogether for an specific Component.

@Component({
selector: 'song-track',
encapsulation: ViewEncapsulation.None
})

By using this mode Angular will add any defined styles to the head so styles can be shared across components using this encapsulation.

Native Shadow DOM browser support

Native Shadow DOM is still not supported broadly at this time. See below Emulated and Native browser support comparison side-by-side.

To see current support check canIuse.

Take browser support into account before activating Native Encapsulation.

Shadow DOM vs Light DOM

When styling our components it can help to differentiate between Shadow DOM and Light DOM.

  • Shadow DOM : any local DOM elements a component creates or manages. This also includes any child components.
<song-track title="No Lie" artist="Sean Paul..."></song-track>@Component({
selector: 'song-track',
template: `
<track-title>{{track}}</track-title>
<track-artist>{{artist}}</track-artist>`
})
export class SongTrack { }
  • Light DOM: any children DOM elements of a component. Also referred as projected content (ng-content).
<song-track>
<track-title>No Lie</track-title>
<track-artist>Sean Paul, Dua Lipa</track-artist>

</song-track>
@Component({
selector: 'song-track',
template: `<ng-content></ng-content>`
})
export class SongTrack { }

@Component styling metadata

In order to style our component we can use the Component metadata.

Angular will add the styles in the header following the same order used below.

Using Inline styles

This is when we add our styles in the same file as our Component. Added in first place in the header following array order.

@Component({
selector: 'song-track',
styles: [`.container { color: white; }`]
})
export class SongTrack { }

Using Template Inline Styles

We can also use this feature to embed our styles right in our template. Added in second place in the header.

@Component({
template: `
<style>
.container { color: deepskyblue; }
</style>

<div class="container">...</div>
`
})
export class SongTrack { }

Using an External File

When our components require more complex styling we can use an external file. Added in third place in the header following array order.

//song-track.component.css
.container { ... }
//song-track.component.ts
@Component({
styleUrls: ['./song-track.component.css'],
})
export class SongTrack { }

As part of the CSS specification we can also use @import to import styles from other style sheets. These must precede any style rules in the style sheet. See @import. Imports will be added in the header after the style sheet.

@import 'common.css';
.container { ... }

Using ngClass and ngStyle directives

We can use ngClass and ngStyle directives to dynamically style our component. Let’s see some common usages

<song-track ngClass="selected" class="disabled"></song-track>
<song-track [ngClass]="'selected'"></song-track>
<song-track [ngClass]="['selected']"></song-track>
<song-track [ngClass]="{'selected': true}"></song-track>

Note that ngClass can be combined with existent class attributes and without using any bindings. To target multiple classes we can use the extended syntax with some interesting variations

<song-track ngClass="selected disabled">             
<song-track [ngClass]="'selected disabled'">
<song-track [ngClass]="['selected', 'disabled']">
<song-track [ngClass]="{'selected': true, 'disabled': true}">
<song-track [ngClass]="{'selected disabled': true}">

For ngStyle we can do the same but as we need pairs of properties and values there are less options

<song-track [ngStyle]="{'color': 'white'}" style="margin: 5px;"><song-track [ngStyle]="{'font-size.px': '12'}">
<song-track [ngStyle]="{'font-size': '12px'}">
<song-track [ngStyle]="{'color': 'white', 'font-size': '12px'}">

Note the extended units syntax matching existing CSS measurement units. To apply multiple styles you can add more properties.

Using Shadow DOM selectors

When using Emulated or Native Encapsulation we have access to some interesting CSS selectors only available to Shadow DOM.

Styling our container (aka host)

In case we need to access our container or in conjunction with other selectors we can use the :host pseudo-class selector

:host { color: black; }          // <song-track>
:host(.selected) { color: red; } // <song-track class="selected">

The first example will match the song-track element and add the color to its styles. The second example will match song-track elements using the selected class.

Styles depending on ancestors

We can also add styles depending on a match on our ancestors going up to the document root.

:host-context(.theme) { color: red; }   
:host-context(#player1) { color: red; }

The example above will change the color only if the theme class was applied to any of the ancestors of our component. The second example will match an ancestor using id=”player1".

Styling host and descendants (crossing boundaries)

This option will override any encapsulation settings including host children. This selector will both work for Shadow and Light DOM.

We can override Shadow DOM boundaries using /deep/

:host  /deep/ .selected { color: red; }
:host >>> .selected { color: red; }

Note: within Angular-CLI use /deep/ instead of >>> .

Using @Component.host

By using this property we can bind with DOM properties, DOM attributes and events. See an overview of the different options below.

@Component({
host: {
'value': 'default', //'DOM-prop': 'value'
'[value]': "'default'", //'[DOM-prop]': 'expr'

'class': 'selected', //'DOM-attr': 'value'
'[class]': "'selected'", //'[DOM-attr]': 'expr'

'(change)': 'onChange($event)', // (event) : ...
'(window:resize)': 'onResize($event)', // (target:event) : ...
}
})

Let’s see some examples using class and style DOM attributes.

@Component({
host: {
//setting multiple values
'class': 'selected disabled',
'style': 'color: purple; margin: 5px;',

//setting single values (using binding)
'[class.selected]': 'true',
'[class.selected]': '!!selected', //add class if selected = true
'[style.color]': '"purple"' //expression must be a string
}
})
export class SongTrack { }

Note the usage of square brackets to create a binding. That’s why ‘true’ becomes the boolean true. For the CSS property color we need to pass a string.

Binding unsafe expressions

To avoid abuse some styling expressions might be flagged as unsafe by Angular.

@Component({
host: {
'[style]': '_hostStyle' //unsafe
}
})
export class SongTrack { }

If you face this particular issue, you can flag the expression as safe by using the bypassSecurityTrustStyle API on the Sanitizer. This will avoid any abuse or security breaches.

export class SongTrack {
constructor(private sanitizer: Sanitizer){
this._hostStyle = this.sanitizer
.bypassSecurityTrustStyle('color: black;');
}
}

Using @HostBinding

We can also use the @HostBinding decorator to set our styles. See some examples below.

export class SongTrack {   
//<host class="selected"></host>
@HostBinding('class.selected') selected = true;
//<host style="color: red;"></host>
@HostBinding('style.color') color = 'red';
}

@HostBinding decorators are translated into @Component.host metadata.

Using ElementRef and nativeElement APIs (Browser)

Sometimes we may want to access the underlying DOM element to manipulate its styles. In order to do that we need to inject ElementRef and access to the nativeElement property. This will give us access to the DOM APIs.

export class SongTrack {
constructor(private element: ElementRef){
let elem = this.element.nativeElement;
elem.style.color = "blue";
elem.style.cssText = "color: blue; ..."; // multiple styles
elem.setAttribute("style", "color: blue;");
}
}

Note that this option will work with the browser platform but not with desktop or mobile.

Using Renderer and setElementClass/setElementStyle APIs (Web, Server, WebWorker)

A safer alternative to ElementRef to set our styling is to use the Renderer together with setElementClass and setElementStyle. Their implementation will abstract the underlying platform being used overcoming the compatibility limitation from ElementRef.

export class SongTrack {
constructor(
private element: ElementRef,
private renderer: Renderer
){
let elem = this.element.nativeElement;
renderer.setElementStyle(elem, "color", "blue");
renderer.setElementClass(elem, "selected", true);
}
}

CSS Styles specificity and execution order

All Styling follows the following specificity and order rules.

  • The more specific a style rule the higher priority it gets
  • With same specificity the last style rule applied overrides any previous ones

This is the order of application of styles and their priority from bottom to top.

Component implementation
- Styles defined at @Component.styles (following array order)
- Template Inline Styles
- External styles @Component.styleUrls (following array order)
Container
- Inline style. Eg: <... style="">
- ngClass and ngStyle

So if we use ngStyle this will override any inline styles defined on the element and any of the previous.

Styles are applied statically and dynamically as part of the Angular rendering execution and Components life cycle.

Note that depending on the order of execution we may get a style overridden by another. For example, @Component.host is applied first and then @Hostbinding.

That’s all folks! Have any questions? Thanks for reading! Ping me at @gerardsans

Want more?

If you need more examples please feel free to contact me at gerard_dot_sans_at_gmail_dot_com!

Further Reading

--

--

Gerard Sans
Google Developer Experts

Helping Devs to succeed #AI #web3 / ex @AWSCloud / Just be AWSome / MC Speaker Trainer Community Leader @web3_london / @ReactEurope @ReactiveConf @ngcruise