4. Template Syntax

Extend HTML vocabulary of our templates with components and directives that appear as new elements and attributes.

Interpolation, double curly braces:

<h2>My favorite technology is: {{ myTechnology.name }}</h2>
<h1>{{ title }}</h1>
<img src="{{ imageUrl }}" style="width:60rem; border-radius:50%">
<p>The sum of 2 + 2 + 2 is {{ 2 + 2 + 2 }}</p>
<p>The sum of 2 + 2 + 2 is not {{ 2 + 2 + getValue() }}</p>

Template Expression produces a value. Expression context is typically component instance.

title in double curly braces and isUnchanged in double quotes refer to properties of AppComponent:

{{ title }}
<span [hidden]="isUnchanged">changed</span>

Expression can also refer to properties of templates context such as template input variable let technology, or template reference variable #technologyInput:

<li *ngFor="let technology of technologies">
             {{ technology.name }}
</li>
<input #technologyInput> {{ technologyInput.value }} //wrap me in a (keyup)="0"

Template Statement has a side effect, appears to the right of the = symbol:

<button (click)="delete(technology)">x</button>

Data Binding:

One way from component to view. Interpolation, property, attribute, class, style:

{{ expression }}
[target]="expression"
bind-target="expression

One way from view to component. Event:

(target)="statement
on-target="statement"

Two way:

[(target)]="expression"
bindon-target="expression"

Property Binding:

<img [src]="imageUrl">
<button [disabled]="isDisabled">Button is disabled</button>
<div [ngClass]="red">[ngClass] binding to red, color of passion and love, property</div>
<app-technology-detail [technology]="technology"></app-technology-detail>

One-time string initialization:

<app-technology-detail prefix="You are my" [technology]="technology"></app-technology-detail>

Property Binding and Interpolation:

        <p><img [src]="imageUrl" style="width:30rem; border-radius:50%"></p>
        <p><img src={{imageUrl}} style="width:30rem; border-radius:50%"></p>
        <p><span>{{ title }}</span></p>
        <p><span [innerHTML]="title"></span></p>

Content Security:

Angular sanitizes HTML. script tags are not allowed to leak into the browser:

  evilScript = 'Template <script>alert("oneiro who deceive men with false visions, arrive through a gate of ivory")</script>Syntax';

        <p><span>{{ evilScript }}</span></p>
        <p><span [innerHTML]="evilScript"></span></p>

Attribute Binding:

    <table>
          <tr>
              <td [attr.colspan]="1 + 1">1-2</td>
          </tr>
          <tr>
              <td>3</td>
              <td>4</td>
          </tr>
        </table>

<button [attr.aria-label]="actionName">{{ actionName }} with Aria</button>

Class Binding:

<div class="red" [class]="redy">The color of passion, desire and love.</div> // <-- all or nothing

<div [class.special]="isSpecial">This class binding is special.</div>

<div class="redy" [class.special]="!isSpecial">This class binding is not so special.</div> // <-- binding class.special trumps class attribute

Style Binding:

<button [style.color]="isSpecial ? 'red' : 'green'">I love you</button>
<button [style.background-color]="canSaveMyPrincess ? 'yellow' : 'cyan'">I Save Her</button>
<button [style.font-size.em]="isSpecial ? 5 : 1">Big</button>
<button [style.fontSize.%]="!isSpecial ? 100: 50">Small</button>

Event Binding:

Template:

<button class="btn btn-danger" (click)="onSave()">Save</button>
<button class="btn btn-success" on-click="onSave()">On Save</button>
<div class="btn btn-warning" (myClick)="clickMessage=$event" clickable>Click with myClick</div>
{{ clickMessage }}

Component:

  clickMesssage = '';
  onSave() {
    console.log('save clicked');
  }

Directive:

import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';

@Directive({selector: '[myClick]'})
export class ClickDirective {
  @Output('myClick') clicks = new EventEmitter<string>();

  toggle = false;

  constructor(el: ElementRef) {
    el.nativeElement.addEventListener('click', (event: Event) => {
                  this.toggle = !this.toggle;
                  this.clicks.emit(this.toggle ? 'Click!' : '');
    });
  }
}

$event and event handling statements:

<input [value]="myTechnology" (input)="myTechnology=$event.target.value">

Custom Events with EventEmitter:

TechnologyDetailComponent:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-technology-detail',
  template: `
      <span>
      {{ technology }}
      </span>
      <button class="btn btn-danger" (click)="delete(technology)">Delete</button>
  `
})
export class TechnologyDetailComponent {
  @Input() technology;
  @Output() deleteRequest = new EventEmitter<string>();

  delete(technology: string) {
    this.deleteRequest.emit(technology);
  }

}

AppComponent:

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

@Component({
  selector: 'app-root',
  template: `
      <div class="row">
      <h1>{{ title }}</h1>
        <div class="col-md-6">
          <ul>
              <li *ngFor="let technology of technologies">
              <app-technology-detail (deleteRequest)="delete($event)" [technology]="technology">
              </app-technology-detail>
              </li>
          </ul>
        </div>
        <div class="col-md-6">
            <app-princess></app-princess>
        </div>
      </div>
  `
})
export class AppComponent {
  title = 'Angular 4 Love Affair';
  technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
  delete(value) {
    const index = this.technologies.indexOf(value);
    this.technologies.splice(index, 1);
  }

}

Two-way Binding:

SizerComponent:

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-sizer',
  template: `
      <div>
              <button class="btn btn-success" (click)="decrement()" title="smaller">--</button>
              <button class="btn btn-danger" (click)="increment()" title="bigger">++</button>
              <label [style.font-size.px]="size">FontSize: {{ size }}px</label>
      </div>
  `
})
export class SizerComponent {
        @Input() size: number | string;
        @Output() sizeChange = new EventEmitter<number>();

        decrement() { this.resize(-1); }
        increment() { this.resize(+1); }

        resize(delta: number) {
          this.size = Math.min(40, Math.max(8, +this.size + delta));
          this.sizeChange.emit(this.size);
        }
}

AppComponent:

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

@Component({
  selector: 'app-root',
  template: `
      <div class="row">
      <h1>{{ title }}</h1>
        <div class="col-md-6">
            <app-sizer [(size)]="fontSizePx"></app-sizer>
            <div [style.font-size.px]="fontSizePx">Resizable Text</div>
        </div>
        <div class="col-md-6">
            <app-princess></app-princess>
        </div>
      </div>
  `
})
export class AppComponent {
  title = 'Angular 4 Love Affair';
  technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
  fontSizePx = 20;
}

Desugar:

    <app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>

Attribute Directives:

  • NgClass: add and remove a set of CSS classes.
  • NgStyle: add and remove a set of CSS styles.
  • NgModel: two-way data binding to an HTML from element.

NgClass:

<div [class.special]="isSpecial">My Chromebook is special</div>
<div [ngClass]="{special: 'isSpecial'}">My Chromebook is special</div>
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
      <div class="row">
      <h1>{{ title }}</h1>
        <div class="col-md-6">
            <div [ngClass]="currentClasses">This div is initially saveable, unchanged and special.</div>
        </div>
        <div class="col-md-6">
            <app-princess></app-princess>
        </div>
      </div>
  `,
  styles: [`.saveable { color: red; } .modified { border-style: solid; } .special { font-weight: bold; font-size: x-large; }`]
})
export class AppComponent {
  title = 'Angular 4 Love Affair';
  technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
  private canSave = true;
  private isUnchanged = false;
  private isSpecial = true;
  currentClasses: {};
  constructor() {
    this.setCurrentClasses();
  }
  setCurrentClasses() {
    this.currentClasses = {
      'saveable': this.canSave,
      'modified': !this.isUnchanged,
      'special': this.isSpecial
    };
  }

}

NgStyle:

    <div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
                    This div is x-large.
              </div>
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
      <div class="row">
      <h1>{{ title }}</h1>
        <div class="col-md-6">
              <div [ngStyle]="setCurrentStyles()">
                    This div is initially italic, normal weight, and extra large (24px).
              </div>
        </div>
        <div class="col-md-6">
            <app-princess></app-princess>
        </div>
      </div>
  `
})
export class AppComponent {
  title = 'Angular 4 Love Affair';
  technologies = ['AngularJS', 'Angular 4', 'Angular Material', 'Angular CLI'];
  private canSave = true;
  private isUnchanged = true;
  isSpecial = true;
  setCurrentStyles() {
    return {
      'font-style': this.canSave ? 'italic' : 'normal',
      'font-weight': !this.isUnchanged ? 'bold' : 'normal',
      'font-size': this.isSpecial ? '24px' : '12px'
    };
  }

}

NgModel:

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

@Component({
  selector: 'app-root',
  template: `
      <div class="row">
      <h1>{{ title }}</h1>
        <div class="col-md-6">
              <input [(ngModel)]="myTechnology"> {{ myTechnology }}
        </div>
        <div class="col-md-6">
            <app-princess></app-princess>
        </div>
      </div>
  `
})
export class AppComponent {
  title = 'Angular 4 Love Affair';
  technologies = ['Angular 4', 'Angular 5 RC1', 'Angular Material', 'Angular CLI'];
  myTechnology = this.technologies[1];

}

Same result with binding to input element's value property and input event:

<input [value]="myTechnology" (input)="myTechnology=$event.target.value"> {{ myTechnology }}

ngModel desugared:

<input [ngModel]="myTechnology" (ngModelChange)="myTechnology=$event"> {{ myTechnology }}

ngModel, standard form:

<input [(ngModel)]="myTechnology"> {{ myTechnology }}

Force ngModel value to uppercase:

<input [ngModel]="myTechnology" (ngModelChange)="setUpperCaseName($event)"> {{ myTechnology }}

<input bindon-ngModel="myTechnology"> {{ myTechnology }}

  setUpperCaseName(input: string) {
    this.myTechnology = input.toUpperCase();
  }

Structural Directives:

  • NgIf: conditionally add or remove an element from the DOM.
  • NgFor: repeat a template for each item in a list.
  • NgSwitch: set of directivesthat switch among alternative views.

NgIf:

  Template:
  <app-technology-detail *ngIf="technology.isActive" [technology]="technology">
                      </app-technology-detail>
               <div [class.hidden]="!isSpecial">Show with class</div>
              <div [class.hidden]="isSpecial">Hide with class</div>
              <app-technology-detail [class.hidden]="isSpecial"></app-technology-detail>
              <div [style.display]="isSpecial ? 'block' : 'none'">Show/Write Angular code with style</div>
              <div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>

Component:
isSpecial = true;
<div *ngIf="myTechnology">Hello {{ myTechnology.name }}</div>
<div *ngIf="nullTechnology">Hello {{ nullTechnology.name }}</div>

NgFor (technology after let is a template input variable):

<div *ngFor="let technology of technologies">{{ technology.name }}</div>

<app-technology-detail *ngFor="let technology of technologies" [technology]="technology">
</app-technology-detail>

*ngFor with index:

          <div *ngFor="let technology of technologies; let i = index;">
              {{ i + 1 }} - {{ technology.name }}
          </div>

*ngFor with trackBy:

  Template:

  <div *ngFor="let technology of technologies; trackBy: trackByTechnologies;">
                      {{ technology.id }}  {{ technology.name }}
  </div>
  Component:

    trackByTechnologies(index: number, technology: Technology) {
    return technology.id;
  }

NgSwitch directive:

Multiple emotional components:

import { Component, Input } from '@angular/core';
import { Technology } from './app.component';

@Component({
  selector: 'app-awesome-technology',
  template: `Wow. You like {{ technology.name }}. What an awesome technology ... just like you.`
})
export class AwesomeTechnologyComponent {
  @Input() technology: Technology;
}

@Component({
  selector: 'app-happy-technology',
  template: `You are a happy technology: {{ technology.name }}. May the sun always shine over you.`
})
export class HappyTechnologyComponent {
  @Input() technology: Technology;
}

@Component({
  selector: 'app-great-technology',
  template: `You like {{ technology.name }}? Such a great technology. Are you great too?`
})
export class GreatTechnologyComponent {
  @Input() technology: Technology;
}

@Component({
  selector: 'app-unknown-future-killer-technology',
  template: ` {{ message }}`
})
export class UnknownFutureKillerTechnologyComponent {
  @Input() technology: Technology;

  get message() {
    return this.technology && this.technology.name ?
     `${this.technology.name} is strange and mysterious.` : 'Code with passion!';
  }
}

export const technologySwitchComponents =
[ AwesomeTechnologyComponent, HappyTechnologyComponent,
  GreatTechnologyComponent, UnknownFutureKillerTechnologyComponent ];

AppComponent:

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

export class Technology {
  constructor(public id: number, public name: string, public emotion: string) {}
}

@Component({
  selector: 'app-root',
  template: `
      <div class="row">
      <h1>{{ title }}</h1>
        <div class="col-md-6">
            <p>Pick your favorite technology</p>
              <div>
                  <label *ngFor="let tech of technologies">
                        <input type="radio" name="technologies" [(ngModel)]="currentTechnology"
                        [value]="tech"> {{ tech.name }} &nbsp;
                  </label>
              </div>
              <div [ngSwitch]="currentTechnology?.emotion">
                    <app-awesome-technology *ngSwitchCase="'awesome'" [technology]="currentTechnology">
                    </app-awesome-technology>
                    <app-happy-technology *ngSwitchCase="'happy'" [technology]="currentTechnology">
                    </app-happy-technology>
                    <app-great-technology *ngSwitchCase="'great'" [technology]="currentTechnology">
                    </app-great-technology>
                    <app-unknown-future-killer-technology *ngSwitchDefault [technology]="currentTechnology">
                    </app-unknown-future-killer-technology>
              </div>
        </div>
        <div class="col-md-6">
            <app-princess></app-princess>
        </div>
      </div>
  `
})
export class AppComponent {
  title = 'Angular 4 Love Affair';
  currentTechnology: Technology;
  technologies = [
    new Technology(1, 'Angular 4', 'awesome'),
    new Technology(2, 'Angular 5', 'happy'),
    new Technology(3, 'Angular Material', 'great'),
    new Technology(4, 'Angular CLI', 'unknown')
  ];
  myTechnology = this.technologies[0];

}

Switch directives work with native elements and web components too. Like this:

<div *ngSwitchCase="'happy'">You are a happy technology: {{ currentTechnology.name }}. May the sun always shine over you.</div>

Template reference variable (#var):

          <input #phone placeholder="phone number">

          <button (click)="callPhone(phone.value)">Call</button>

How reference variable gets its value:

        Template:

            <form (ngSubmit)="onSubmit(technologyForm)" #technologyForm="ngForm">
              <div class="form-group">
                <label for="name">Name
                  <input class="form-control" name="name" required [(ngModel)]="myTechnology.name">
                </label>
              </div>
              <button type="submit" [disabled]="!technologyForm.form.valid">Submit</button>
          </form>
          <div [hidden]="!technologyForm.form.valid">
            {{ submitMessage }} 
          </div>

        Component:
          submitMessage: any;
          onSubmit(technologyForm): void {
            this.submitMessage = technologyForm.value.name;
            }

Template reference variable, ref- prefix as alternative to #:

              <input ref-email placeholder="email">
              <button (click)="sendEmail(email.value)">Email</button>

Input and Output properties:

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Technology } from './app.component';

@Component({
  selector: 'app-technology-detail',
  template: `
      <span>{{ technology.name }}</span>
      <button (click)="delete()">Delete</button><br>
  `
})
export class TechnologyDetailComponent {
  @Input() technology;
  @Output() deleteRequest = new EventEmitter<Technology>();

  delete() {
    this.deleteRequest.emit(this.technology);
  }

}

Identify members in inputs and outputs arrays of directive metadata:

import { Component, EventEmitter } from '@angular/core';
import { Technology } from './app.component';

@Component({
  inputs: ['technology'],
  outputs: ['deleteRequest'],
  selector: 'app-technology-detail',
  template: `
      <span>{{ technology.name }}</span>
      <button (click)="delete()">Delete</button><br>
  `
})
export class TechnologyDetailComponent {
  technology;
  deleteRequest = new EventEmitter<Technology>();

  delete() {
    this.deleteRequest.emit(this.technology);
  }

}

AppComponent:

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

export class Technology {
  constructor(public id: number, public name: string, public emotion: string) {}
}

@Component({
  selector: 'app-root',
  template: `
      <div class="row">
      <h1>{{ title }}</h1>
        <div class="col-md-6">
          <app-technology-detail *ngFor="let technology of technologies" [technology]="technology"
          (deleteRequest)="delete($event)">
          </app-technology-detail>
        </div>
        <div class="col-md-6">
            <app-princess></app-princess>
        </div>
      </div>
  `
})
export class AppComponent {
  title = 'Angular 4 Love Affair';
  technologies = [
    new Technology(1, 'Angular 4', 'awesome'),
    new Technology(2, 'Angular 5', 'happy'),
    new Technology(3, 'Angular Material', 'great'),
    new Technology(4, 'Angular CLI', 'unknown')
  ];

  delete(technology) {
    const idx = this.technologies.indexOf(technology);
    this.technologies.splice(idx, 1);
  }

}

Aliasing input/output properties:

        <div (myClick)="clickMessage=$event" clickable>Click Me with myClick</div>
          {{ clickMessage }}
        </div>

@Output alias:

import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';

@Directive({selector: '[myClick]'})
export class ClickDirective {
  @Output('myClick') clicks = new EventEmitter<string>();
  toggle = false;
  constructor(el: ElementRef) {
    el.nativeElement.addEventListener('click', () => {
                  this.toggle = !this.toggle;
                  this.clicks.emit(this.toggle ? 'Click!' : '');
    });
  }

}

Alias property names, like this:

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

@Directive({
  selector: '[myClick]',
  outputs: ['clicks:myClick'] // <-- propertyName:alias
})
export class ClickDirective {
  clicks = new EventEmitter<string>();
  toggle = false;
  constructor(el: ElementRef) {
    el.nativeElement.addEventListener('click', () => {
                  this.toggle = !this.toggle;
                  this.clicks.emit(this.toggle ? 'Click!' : '');
    });
  }

}

Pipe ( | ) operator:

<div>Title through uppercase pipe: {{ title | uppercase }}</div>

Multiple Pipes:

<div>Title through pipe chain: {{ title | uppercase | lowercase }}</div>

Parameters to a Pipe(Elvis Operator or Safe Traversal Operator is in the mix):

  <div>Birthdate: {{ myTechnology?.birthdate | date:'longDate' }}</div>

JSON Pipe(useful for debugging):

<div>My JSON: {{ myTechnology | json }}</div>

Safe Navigation Operator(guard against null and undefined values):

<div>My technology's name is {{ myTechnology?.name }}</div>

Non Null Assertion Operator ( ! ):

<div *ngIf="myTechnology">My rocket is {{ myTechnology!.rocket }}</div>

results matching ""

    No results matching ""