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!