Advanced styling v4+💅

slides.com/gerardsans | @gerardsans

Google Developer Expert

Master of Ceremonies

Blogger

International Speaker

Angular Trainer

Community Leader

800

750

✨new in Angular✨

Angular Version

// index.html
<my-app ng-version="4.0.0-rc.4">
  <div>
    <h2>Hello Angular 4! 👋</h2>
  </div>
</my-app>

Semantic Versioning

X . Y . Z

   MAJOR       MINOR        PATCH  

Semantic Versioning

  • v4 March 2017
  • v5 Sept/Oct 2017
  • v6 March 2018
  • v7 Sept/Oct 2018

Components inheritance

Usage

<div>
  <person name="John"></person>
  <employee name="Tom" id="45231"></employee>
</div>

Implementation

@Component({
  selector: 'person',
  template: `<h4>Person: {{name}}</h4>`
})
export class Person {
  @Input() name: string;
}

@Component({
  selector: 'employee',
  template: `<h4>Employee: {{name}}, id: {{id}}</h4>`
})
export class Employee extends Person {
  @Input() id: string;
}

Introduction

Cascading Style Sheets

  • Styling HTML Elements
  • CSS rules
  • Specificity and order
  • Box model

HTML Page life-cycle

source: blog

Basic Example

<html>
<head>
 <link rel="stylesheet" href="style.css" />
</head>
<body>
  <h1>Hello World!</h1>
  <p>This is my first CSS example</p>
</body>
</html>

CSS Rules

//style.css
h1 {
  color: blue;
  background-color: yellow;
  border: 1px solid black;
}

p {
  color: red;
}

Specificity and Order

  • Element style

  • Element id

  • class/attribute selectors

  • element selectors

  • last CSS rule wins

Box Model

source: blog

Angular

Components Tree

source: blog

Song Track Component

@Component({
  selector: 'song-track',   // <song-track></song-track>
  template: ` 
    <div class="container">
      <track-image image="http://..."></track-image>
      <div class="track-information">
        <track-title>{{track}}</track-title>
        <track-artist>{{artist}}</track-artist>
      </div>
    </div>`
})
export class SongTrack { }

CSS Styles Encapsulation

  • Shadow DOM (emulated)
  • Shadow DOM (native)
  • Disabled

Shadow DOM (emulated)

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

<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>

Shadow DOM (native)

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

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

Disabled

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

<head>
  <style>.container { ... }</style>
</head>
<body>
  <my-app>
    <song-track>    
      <div class="container"></div>
    </song-track>
  </my-app>
</body>

Browser Support

safari

safari

Shadow vs light DOM

Shadow DOM

  • Elements that the component creates and manages
  • @Component
    • template, templateUrls

Shadow DOM

@Component({
  selector: 'song-track',
  encapsulation: ViewEncapsulation.Native,
  template: ` 
    <div class="container">
      <track-image image="image"></track-image>
      <div class="track-information">
        <track-title>{{track}}</track-title>
        <track-artist>{{artist}}</track-artist>
      </div>
    </div>`
})
export class SongTrack { }

light DOM

  • DOM elements created by the container. Not known at design time.
  • Projected DOM elements
    • <ng-content>

Light DOM

@Component({
  selector: 'song-track',
  encapsulation: ViewEncapsulation.Native,
  template: ` 
    <div class="container">
      <ng-content select="track-image"></ng-content>
      <div class="track-information">
        <ng-content select="track-title"></ng-content>
        <ng-content select="track-artist"></ng-content>
      </div>
    </div>`
})
export class SongTrack { }

Shadow vs Light DOM

<!-- Component (Shadow DOM) -->
<song-track 
  track="No Lie" 
  artist="Sean Paul, Dua Lipa">
</song-track>

<!-- Component (Light DOM) -->
<song-track2>
  <track-image image="..."></track-image>
  <track-title>No Lie</track-title>
  <track-artist>Sean Paul, Dua Lipa</track-artist>
</song-track2>

Shadow DOM selectors

Container Styling (host)

@Component({
 styles: [`
  :host { color: black; }          
  :host(.selected) { color: red; } 
 `]
})
export class SongTrack { }


<song-track></song-track>
<song-track class="selected"></song-track>

Container Styling (context)

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


<div class="theme">
 <song-track></song-track>
</div>

<div id="player1">
 <song-track></song-track>
</div>

Overriding Styles (deep)

@Component({
 styles: [`
  :host  /deep/ .h3 { color: red; }
  :host   >>>   .h4 { color: purple; }
 `],
 template: `
    <div class="container">
      <track-image image="http://..."></track-image>
      <div class="track-information">
        <track-title>{{track}}</track-title>     //<h3><ng-content></h3>
        <track-artist>{{artist}}</track-artist>  //<h4><ng-content></h4>
      </div>
    </div>`
})
export class SongTrack { }

Component Styles

Inline Styles

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

Template Inline Styles

@Component({
 template: `
   <style>
   .container { color: deepskyblue; }
   </style>   
   <div class="container">...</div>
 `
})
export class SongTrack { }

External Styles

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

//shared.css
.container { ... }

Using Directives

ngClass (single)

<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>

<song-track class="selected disabled"></song-track>
<song-track class="selected"></song-track>

ngClass (multiple)

<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}">

<song-track class="selected disabled"></song-track>

ngStyle (single)

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

<song-track style="color: white; font-size: 12px;">
<song-track style="font-size: 12px;">

ngStyle (multiple)

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

<song-track style="color: white; font-size: 12px;">

Using host metadata

host metadata (basics)

@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) : ...
 } 
})

@Component.host (styling)

@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 { }

@HostBinding (styling)

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

  //<host style="color: red;"></host>     
  @HostBinding('style.color') color = 'red';
}

Using APIs

ElementRef (only browser)

import { ElementRef } from '@angular/core';

@Component(...)
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;"); 
  }
}

Renderer (all platforms)

import { ElementRef, Renderer } from '@angular/core';

@Component(...)
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);
  }
}

Playground

Song Track Example

  • Encapsulation Modes

  • Inline, Template inline, External Styles

  • ngClass, ngStyle

  • Shadow DOM Selectors

  • Host bindings/listeners

Blog Post

Angular 4

New in Angular 4

  • Improved compiler (AOT)
  • uses TypeScript 2.2
  • new bundles
    • @angular/platform-server
    • @angular/animations

New in Angular 4

  • Add/update meta tag
  • New EmailValidator
  • Deprecated
    • template, OpaqueToken
    • ng-template, InjectionToken 

ngIf changes

// Angular 2
<div *ngIf="flightInfo">{{flightInfo.name}}</div>
<div *ngIf="!flightInfo">Loading...</div>

// Angular 4
<div *ngIf="flightInfo; else noInfo">{{flight.name}}</div>
<ng-template #noInfo>
  <div>Loading...</div>
</ng-template>

async pipe changes

// Angular 2
<div *ngIf="flightInfo$ | async">
  {{(flightInfo$ | async)?.name}}
</div>

// Angular 4
<div *ngIf="flightInfo$ | async; let flight">
  {{flight.name}}
</div>

<div *ngIf="flightInfo$ | async as flight">
  {{flight.name}}
</div>

Examples

Danke Viel!

Angular Advanced Styling (v4+)

By Gerard Sans

Angular Advanced Styling (v4+)

In this talk we are going to cover all the different ways we can style our Angular Components. From introductory to advanced. We will learn how to style using Shadow DOM selectors, Light Dom, @HostBinding, ElementRef and more.

  • 3,153