Upgrade to Pro — share decks privately, control downloads, hide ads and more …

QuickStart to Angular - platform

QuickStart to Angular - platform

QuickStart to Angular 2+ - platform

Jussi Pohjolainen

December 06, 2017
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. React vs Angular React • Pure React is just for

    UI • But you can add third-party libraries • JavaScript preference • JSX for templates • One-way databinding Angular 2+ • In addition for UI you will have lot of other libraries for AJAX, services, modules ... • TypeScript preference • Strong opinions how app should be structured • HTML for templates with add-ons from Angular • Two-way databinding • Learning curve can be steep
  2. AngularJS vs Angular • No scope, uses hierarchical components •

    Simpler expression syntax, [ ] for property binding, ( ) for event binding • Modularity – core functionality in modules, lighter and faster • Modern Browser only – reduce need for browser compability workarounds • TypeScript Recommendation
  3. TypeScript • TypeScript is a superset of JavaScript and ES6

    • Created by Microsoft • Enhances the language with type annotations • Strong recommendation for Angular • Browsers do not understand TypeScript, but it is possible to compile TypeScript to JavaScript • So in Angular2 you will end up with files ending with .ts rather than .js. In the end the .ts files are compiled to .js • You can implement also React, Express, Vue.js in TypeScript
  4. Install • To install use npm • npm install -g

    typescript • Then code with *.ts files • And compile • tsc *.ts • Which will in the end compile to *.js
  5. Example of types function repeat(mystring : string, amount : number)

    : string { let returnValue : string = "" for(let i=0; i<amount; i++) { returnValue += returnValue + mystring } return returnValue } Cannot pass not string Return value is string
  6. Running in Browser <!DOCTYPE html> <html> <head> <title>Title</title> <meta charset="UTF-8"

    /> <style media="screen"></style> <script src="dist/bundle.js"></script> </head> <body> <p id="changethis">Loading...</p> </body> </html>
  7. Watch - mode By running in watch mode (-w) it

    detects changes in ts file and compiles it
  8. Types • Now when trying to invoke • p.innerHTML =

    repeat(5, 5) • Where the function is declared • function repeat(mystring : string, amount : number) : string • You will get an error: • Argument of type '5' is not assignable to parameter of type 'string'
  9. Lab

  10. TypeScript types • boolean • number • string • array

    • tuple • enum • any • void • null • undefined • never
  11. Tuple: Define the types in array let tuple: [string, number,

    boolean] tuple[0] = 'hello' tuple[1] = 5 tuple[2] = true
  12. Enum enum Day {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}

    let today : Day = Day.Monday console.log(today) // 0
  13. Any: type can change let variable : any variable =

    1 variable = "hello" let array : any[] = [1, 'hello', true]
  14. void function doIt() : void { console.log('I am not returning

    anything') let variable : void variable = null variable = undefined } Not really useful, only null or undefined values are accepted
  15. let vs var vs const • var • scope either

    global or function • let • block scoping • const • block scoping, cannot change
  16. Interface function display(person : Person) : string { return `firstname

    = ${person.firstname}, lastname = ${person.lastname}` } interface Person { firstname : string lastname : string age? : number } let result = display({"firstname": "jack", "lastname": "bauer", age: 30}) Must have firstname and lastname. Age is optional optional property (?)
  17. readonly function display(person : Person) : string { person.firstname =

    "Tina" return `firstname = ${person.firstname}, lastname = ${person.lastname}` } interface Person { readonly firstname : string readonly lastname : string } Cannot change!
  18. Function in interface interface MyFunc { (msg: string) : void

    } function display(f : MyFunc) : void { f('hello') } function printToConsole(msg : string) : void { console.log(msg) } display(printToConsole)
  19. Class Types interface Flyable { fly() : void } class

    Bird implements Flyable { fly() { console.log('Bird is flying') } } class Airplane implements Flyable { fly() { console.log('Airplane is flying') } } function flySomething(something : Flyable) : void { something.fly() } flySomething(new Airplane()) flySomething(new Bird()) Must have the fly() method We can pass bird and airplane
  20. Class and Inheritance class Person { private name : string

    private age : number constructor(name : string, age : number) { if(name.length >= 2 && age >= 0 && age <= 150) { this.name = name this.age = age } } public sleep() { console.log(`${this.name} sleeps`) } } class Programmer extends Person { private salary : number constructor(name : string, age : number, salary : number) { super(name, age) if(salary >= 0) { this.salary = salary } } public codeApps() { console.log('code apps') } }
  21. Setters and Getters (ES5 Target required) class Person { private

    _name : string get name(): string { return this._name; } set name(newName : string) { if(newName.length >= 2) { this._name = newName } else { throw new TypeError('Name must be at least 2 chars') } } } // tsc -target ES5 -w src/index.ts --outFile dist/bundle.js let tina = new Person() tina.name = "T" Invoking the set method
  22. Static class Circle { public static PI : number =

    3.14 private _radius : number set radius(newRadius : number) { if(newRadius > 0) { this._radius = newRadius } else { throw new TypeError('radius must be > 0') } } get radius() : number { return this._radius } calculateArea() : number { return Circle.PI * c.radius * c.radius } } let c = new Circle() c.radius = 5 console.log(c.calculateArea()) console.log("area = " + Circle.PI * c.radius * c.radius) Common variable to all objects. Class variable
  23. Abstract Class abstract class Shape { protected _xPos : number

    protected _yPos : number abstract calculateArea() : number } class Circle extends Shape { private radius : number calculateArea() : number { return Math.PI * this.radius * this.radius } } class Rectangle extends Shape { private width : number private heigth : number calculateArea() : number { return this.width * this.heigth } }
  24. Generics class List<T> { private list: T[] = [] public

    add (element: T) { this.list.push(element) } public removeEveryOther() { let newList : T[] = [] for(let i=0; i<this.list.length; i = i + 2) { newList.push(this.list[i]) } this.list = newList } public printAll() { for(let item of this.list) { console.log(item) } } } let mylist = new List<Number>() mylist.add(1); mylist.add(2); mylist.add(3); mylist.add(4); mylist.removeEveryOther() mylist.printAll() Let's define the T - type at runtime
  25. Modules • Starting from ES2015, JavaScript has module support, altough

    Browser support is weak • TypeScript shares the module support from ES2015 • Modules are executed in their own scope, not in global scope • Variables, functions etc are visible to other modules using export • Module can use other module's "stuff" using import • Any file containing top-level either import or export is considered to be module
  26. Importing import { Person } from "./person"; let jackObject :

    Person = {name: "jack", age: 40}; console.log(jackObject);
  27. Import: Renaming import { Person as Henkilo } from "./person";

    let jackObject : Henkilo = {name: "jack", age: 40}; console.log(jackObject);
  28. Multiexport interface Person { name: string, age: number } interface

    Movie { director: Person, title: string } export { Person, Movie };
  29. Multi-import import { Person, Movie } from "./person"; let jackObject

    : Person = {name: "jack", age: 40}; let movieObject : Movie = {director: jackObject, title: "My Movie"}
  30. Default Export Option 1 interface Person { name: string, age:

    number } export default Person; Option 2 export default interface Person { name: string, age: number }
  31. Code Generation for Modules • Compiler will develop different code

    depending on module target specification • Options: CommonJS, require.js, UMD, SystemJS, ES6
  32. Lab

  33. Quickstart • Angular CLI - command line app • To

    install globally • npm install -g @angular/cli • Create skeleton project • ng new my-app • Run your app • ng serve –open • If having permission problems, see • https://docs.npmjs.com/getting -started/fixing-npm- permissions
  34. Concepts • Applications are written using HTML templates that may

    contain Angular markup • <h1>{{ title }}</h1> • These templates are managed by Application components • Business logic can be wrapped inside of services • All of these are sandboxed into modules • So module contains components (that contain templates) and services • The app is launched by bootstrapping a root module
  35. Component • Component controls a fragment of the screen called

    a view.. which is html • Core building block of Angular 2 apps • Reusable piece of UI that is depicted by a custom html tag • Views? • Navigation links • Content in the web page • Component's application logic is written inside of a class • Class interacts with the view through an API of properties and methods • Angular creates, updates and destroys components when user moves through the app • These classes may have lifecycle hooks / methods
  36. HTML Template and Component import { Component } from '@angular/core';

    @Component({ selector: 'custom-element', template: `<h1>Hello {{name}}</h1>` }) export class AppComponent { name = 'Angular'; } HTML template. Usuall in separate html file When angular finds <custom-element> </custom-element> use the template instea Component that manages the view
  37. HTML Template and Component – sepate files import { Component

    } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app'; } Now template and css in separate files
  38. Module Files app.component.ts the component app.component.html the view app.component.css the

    styling app.component.spec unit testing app.module.ts boxing the componen into module
  39. Modules • Angular apps are modular • Angular modules or

    NgModules • Every app has at least one module: the root module, named AppModule • In large scale app you will end up with several feature modules • Module is a class with @NgModule decorator • Decorators are functions that modify JavaScript Classes • You will use both standard EcmaScript module- and Angular module system
  40. NgModule({ }) • Takes single object that describes the module

    • declarations • The view classes that belong to module: component, directive or pipe view class • imports • Other modules that are needed by component templates declared in this module • bootstrap • The main application view; root component, that hosts all other app views. Only root module should set this.
  41. app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule

    } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule], providers: [], bootstrap: [AppComponent] }) export class AppModule { } By using JavaScript Module system we import modules. This is standard JS Declare all the components that are part of this module. Can be also pipes and directives AppComponent will host all other views. This is done only in the root module All components can now use BrowserModule. You can add here forms, routing, http...
  42. main.ts -> main.js import { enableProdMode } from '@angular/core'; import

    { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.log(err)); Sets AppModule to be the main module of our app
  43. <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>MyApp</title> <base href="/">

    <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root></app-root> </body> </html> Custom element which angular will change dynamically Not a single import for javascript? <script> tags are injected by runtime here
  44. Lab

  45. Several Components import { Component } from '@angular/core'; @Component({ selector:

    'my-app', template: `<h1>{{title}}</h1> <planet-list></planet-list>` }) export class AppComponent { title : string = "Planets"; } Custom element again! This needs to be defined in other component
  46. app/planet-list/planet-list-component.ts import { Component } from '@angular/core'; interface Planet {

    name: string; diameter: number; population: number; } @Component({ selector: 'planet-list', template: `<ul> <li *ngFor="let planet of planets"> {{planet.name}} </li> </ul>` }) export class PlanetListComponent { planets: Planet[] = [ {'name": 'Yavin IV', 'diameter': 10200, population: 1000}, {'name": 'Alderaan', 'diameter': 12500, population: 2000000000}]; } Create directo for each component Iterate all the planets
  47. app/planet-list/planet.ts export interface Planet { name: string; diameter: number; population:

    number; } Let's put the interface into separate file for better practice and reusage
  48. app/planet-list/planet-list-component.ts import { Component } from '@angular/core'; import { Planet

    } from './planet'; @Component({ selector: 'planet-list', template: `<ul> <li *ngFor="let planet of planets"> {{planet.name}} </li> </ul>` }) export class PlanetListComponent { planets: Planet[] = [ {"name": "Yavin IV", "diameter": 10200, population: 1000}, {"name": "Alderaan", "diameter": 12500, population: 2000000000}]; } Let's import the interface
  49. Final modification to the module import { NgModule } from

    '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { PlanetListComponent } from './planet-list/planet-list.component'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent, PlanetListComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } The components that belong to this module
  50. Angular CLI • You can create new components and stuff

    by using Angular CLI • ng g component my-new-component • Creates several files and modifies also your module!
  51. Lab

  52. import { Component } from '@angular/core'; import { Planet }

    from './planet'; @Component({ selector: 'planet-list', template: `<ul> <li *ngFor="let planet of planets"> {{planet.name}} </li> </ul> <p *ngIf="planets.length > 3">Too many planets</p>` }) export class PlanetListComponent { planets: Planet[] = [ {"name": "Yavin IV", "diameter": 10200, population: 1000}, {"name": "Alderaan", "diameter": 12500, population: 2000000000}, {"name": "Hoth", "diameter": 80, population: 20000}, {"name": "Dagobah", "diameter": 8900, population: 3000}]; } *ngFor for displaying array Template Syntax *ngIf for conditional statement
  53. Template Syntax • Template expression produces a value • {{

    1 + 1 }} • Many JavaScript expressions are allowed, but not all • assignments (=) • new • chaining expressions (;) • incrementing, decrementing (++, --) • Expression context is typically the component instance • {{ title }} refers to properties of the instance of the component • Should not have visible side effects, quick execution, simplicity ...
  54. Examples of Data Binding • One way • <img [src]="heroImageUrl">

    • <img bind-src="heroImageUrl"> • <img src="{{heroImageUrl}}"> • One way event • <button (click)="onSave()">Save</button> • <button on-click="onSave()">Save</button> • Two way binding in Forms • <input [(ngModel)]="name"> All the same
  55. Binding Data Between View and Component import { Component }

    from '@angular/core'; @Component({ selector: 'my-app', template: `<form> <input type="text" placeholder="name" name="nameOfTheTextField" [(ngModel)]="personName" /> </form> <p>name = {{personName}}</p>` }) export class AppComponent { personName = 'kalle'; }
  56. Importing new Module import { NgModule } from '@angular/core'; import

    { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
  57. Example import { Component } from '@angular/core'; @Component({ selector: 'my-app',

    template: `<form> <input type="number" placeholder="mass" name="massText" [(ngModel)]="mass" /> <input type="number" placeholder="height" name="heightText" [(ngModel)]="height" /> </form> <p>bmi = {{mass / (height * height)}}</p>` }) export class AppComponent { } View handles the logic of calculating bmi
  58. Example import { Component } from '@angular/core'; @Component({ selector: 'my-app',

    template: `<form> <input type="number" placeholder="mass" name="massText" [(ngModel)]="mass" /> <input type="number" placeholder="height" name="heightText" [(ngModel)]="height" /> </form> <p>bmi = {{calculateBmi()}}</p>` }) export class AppComponent { mass : number = 0; height : number = 0; calculateBmi() : number { return this.mass / (this.height*this.height); } } Component handles the logic of calculating bmi
  59. Event Example import { Component } from '@angular/core'; @Component({ selector:

    'my-app', template: `<form> <input type="number" placeholder="mass" name="massText" [(ngModel)]="mass" /> <input type="number" placeholder="height" name="heightText" [(ngModel)]="height" /> <button (click)="calculateBmi()">Calculate Bmi</button> </form> <p>bmi = {{bmi}}</p>`, }) export class AppComponent { mass : number = 0; height : number = 0; bmi : number = 0; calculateBmi() : void { this.bmi = this.mass / (this.height*this.height); } } When clicking the button, call the method Method updates the bmi
  60. Lab

  61. Basic Lifecycle hooks import { Component } from '@angular/core'; import

    { OnInit } from '@angular/core'; import { OnDestroy } from '@angular/core'; @Component({ selector: 'life-cycle', template: `<p>Testing lifecycle</p>` }) export class LifeCycleComponent implements OnInit, OnDestroy { ngOnInit() { console.log("init") } ngOnDestroy() { console.log("destroy") } } Interfaces are voluntary here, you do not need them
  62. Usage import { Component } from '@angular/core'; @Component({ selector: 'my-selector',

    template: `<button (click)="toggle()">Toggle</button> <life-cycle *ngIf="isVisible"></life-cycle>` }) export class AppComponent { isVisible : boolean = true toggle() { this.isVisible = !this.isVisible } }
  63. ngOnInit and Destroy • To perform complex initializations shortly after

    construction • To set up component after Angular sets input properties • So if for example your component fetches data from server do NOT do this in constructor • In Destroy you can do clean ups if necessary
  64. ngDoCheck() • Detect and act upon changes that Angular can't

    or won't detect of its own. • So for example if user input changes the view, the ngDoCheck() is called • When view is changed a lot of "sub-lifecycle" hooks are called • ngAfterContentChecked() • ngAfterViewChecked()
  65. Example App Component import { Component } from '@angular/core'; @Component({

    selector: 'my-selector', template: `<button (click)="toggle()">Toggle Component</button> <nameform *ngIf="isVisible"></nameform>` }) export class AppComponent { isVisible : boolean = true toggle() { this.isVisible = !this.isVisible } } Button will toggle the nameform component ON and OFF
  66. Name Component import { Component } from '@angular/core'; import {

    Input } from '@angular/core'; @Component({ selector: 'nameform', template: `<input type="text" placeholder="name" [(ngModel)]="name" /> {{name}}` }) export class NameComponent { constructor() { console.log(new Date() + " NameComponent: constructor") } ngOnInit() { console.log(new Date() + " NameComponent: ngOnInit") } ngOnChanges() { console.log(new Date() + " NameComponent: ngOnChanges") } ... The view is modified when use gives input
  67. Lab

  68. Master Component import { Component } from '@angular/core'; @Component({ selector:

    'my-selector', template: `<ul><point [xPos]="0" [yPos]="0"></point> <point [xPos]="5" [yPos]="20"></point></ul>` }) export class AppComponent { } Passing Data to component
  69. Child Component import { Component } from '@angular/core'; import {

    Input } from '@angular/core'; @Component({ selector: 'point', template: `<li>xPos = {{xPos}}, yPos = {{yPos}}</li>` }) export class PointComponent { @Input() xPos: number @Input() yPos: number } Injecting the values given
  70. Iterating import { Component } from '@angular/core'; @Component({ selector: 'my-selector',

    template: `<ul> <point *ngFor="let point of points" [xPos]="point.xPos" [yPos]="point.yPos"></point> </ul>` }) export class AppComponent { points : Object[] = [{"xPos": 0, "yPos": 0}, {"xPos": 2, "yPos": 2}] } Iterating!
  71. Passing Variables import { Component } from '@angular/core'; @Component({ selector:

    'my-selector', template: `<monster [name]="thename"></monster>` }) export class AppComponent { thename : string = "The Monster" } Passing Data component us variable
  72. Passing Variables import { Component } from '@angular/core'; @Component({ selector:

    'my-selector', template: `<monster [name]="'The Monster'"></monster>` }) export class AppComponent { } Passing Data to component without variable
  73. Parent: Parent listening to Children import { Component } from

    '@angular/core'; @Component({ selector: 'my-selector', template: `<ul> <point (onDeleteClicked)="clicked($event)" [xPos]="0" [yPos]="0"></point> </ul>` }) export class AppComponent { clicked(event: string) { console.log(event); // delete clicked } } Child triggers an event th the Parent catches. Th onDeleteClicked is someth that must be implemente child
  74. Child: Parent listening to Children import { Component } from

    '@angular/core'; import { Input } from '@angular/core'; import { Output } from '@angular/core'; import { EventEmitter } from '@angular/core'; @Component({ selector: 'point', template: `<li>xPos = {{xPos}}, yPos = {{yPos}} <button (click)="delete()">Delete</button></li>` }) export class PointComponent { @Input() xPos: number @Input() yPos: number @Output() onDeleteClicked = new EventEmitter<string>(); delete() { this.onDeleteClicked.emit('delete clicked') } } When delete button is pressed, send the event The parent listenes to this Triggering the event
  75. CSS

  76. Component Styles • Use standard CSS • You can use

    component styles • More modular approach to regular stylesheets import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: '<h1>Hello</h1>', styles: ['h1 { color: RGB(255,0,0) }'] }) export class AppComponent { title = 'app'; }
  77. Overview • Attribute directives: used in attributes in elements •

    <p doSomething>...</p> • Structural directives: change the structure of the view • ngFor, ngIf ...
  78. Generating directive ng g directive dosomething create src/app/dosomething.directive.spec.ts (244 bytes)

    create src/app/dosomething.directive.ts (151 bytes) update src/app/app.module.ts (484 bytes)
  79. Module import { DosomethingDirective } from './dosomething.directive'; @NgModule({ declarations: [

    AppComponent, ChildComponent, DosomethingDirective ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
  80. Directive import { Directive } from '@angular/core'; @Directive({ selector: '[appDosomething]'

    }) export class DosomethingDirective { constructor() { } } Defining selector (attribute name) Does not do anything yet.
  81. Usage import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: '<h1 appDosomething>Hello</h1>', }) export class AppComponent { title = 'app'; } Using the directive
  82. Implementing Logic import { Directive, ElementRef } from '@angular/core'; @Directive({

    selector: '[appDosomething]' }) export class DosomethingDirective { constructor(el: ElementRef) { el.nativeElement.style.backgroundColor = 'yellow'; } } Importing reference to the element that has the attribute Native DOM - element
  83. import { Directive, ElementRef, HostListener } from '@angular/core'; @Directive({ selector:

    '[appDosomething]' }) export class DosomethingDirective { private el: ElementRef; constructor(el: ElementRef) { this.el = el; } @HostListener('mouseenter') onMouseEnter() { this.highlight('yellow'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(null); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; } } @HostListener let's you subscribe to events of the host DOM element
  84. TypeScript Constructor Initializing attribute export class DosomethingDirective { private el:

    ElementRef; constructor(el: ElementRef) { this.el = el; } } Shorter syntax export class DosomethingDirective { constructor(private el: ElementRef) {} }
  85. Input import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: '<h1 appDosomething [color]="someColor">Hello</h1>' }) export class AppComponent { title = 'app'; someColor = 'red'; } You can give inpu to the directive
  86. Receiving Input import { Directive, ElementRef, HostListener, Input } from

    '@angular/core'; @Directive({ selector: '[appDosomething]' }) export class DosomethingDirective { @Input() color: string; ... Using @Input to get the color
  87. Cleaner Syntax import { Component } from '@angular/core'; @Component({ selector:

    'app-root', // template: '<h1 appDosomething [color]="someColor">Hello</h1>' template: '<h1 [appDosomething]="someColor">Hello</h1>' }) export class AppComponent { title = 'app'; someColor = 'red'; } The directive name and the input in the same
  88. Receiving Input import { Directive, ElementRef, HostListener, Input } from

    '@angular/core'; @Directive({ selector: '[appDosomething]' }) export class DosomethingDirective { @Input() appDosomething: string; ... Change the input name to selector. Can be confusing name?
  89. Using Alias import { Directive, ElementRef, HostListener, Input } from

    '@angular/core'; @Directive({ selector: '[appDosomething]' }) export class DosomethingDirective { @Input('appDosomething') color: string; ... Now better variable name
  90. Lab

  91. Structural Directives • Structural directives are responsible for HTML layout

    • Reshape DOM • Apply the directive to host element, just like in attributes • <div *ngIf="something">...</div> • The asterisk * precedes the structural directive as a convenience notation • Three common built-in structural directives • ngIf, ngFor, ngSwitch
  92. ngSwitch import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: `<div [ngSwitch]="day"> <p *ngSwitchCase="1">January</p> <p *ngSwitchCase="2">February</p> <p *ngSwitchDefault>Some other month</p> </div>`}) export class AppComponent { day = 1; } Will display "January"
  93. *ngIf import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: `<div *ngIf="display">Is this displayed?</div>` }) export class AppComponent { display = true; } The *ngIf is translated to something else!
  94. *ngIf => ng-template import { Component } from '@angular/core'; @Component({

    selector: 'app-root', template: `<ng-template [ngIf]="display"> <div>Is this displayed?</div> </ng-template>` }) export class AppComponent { display = true; } The end result is like this. *ngFor and *ngSwitch follows the same pattern.
  95. *ngFor import { Component } from '@angular/core'; interface Person {

    name: string; } @Component({ selector: 'app-root', template: ` <div *ngFor="let person of persons"> {{person.name}} </div>` }) export class AppComponent { persons: Person[] = [{name: 'jack'}, {name: 'tina'}]; } Iterating the list. We have more options here also...
  96. *ngFor: predefined variable index import { Component } from '@angular/core';

    interface Person { name: string; } @Component({ selector: 'app-root', template: ` <div *ngFor="let person of persons; let i=index;"> {{i}} {{person.name}} </div>` }) export class AppComponent { persons: Person[] = [{name: 'jack'}, {name: 'tina'}]; } 0,1, ...
  97. *ngFor: predefined variable odd import { Component } from '@angular/core';

    interface Person { name: string; } @Component({ selector: 'app-root', template: ` <div *ngFor="let person of persons; let i=index; let o=odd;"> {{i}} {{o}} {{person.name}} </div>` }) export class AppComponent { persons: Person[] = [{name: 'jack'}, {name: 'tina'}, {name: 'paul'}, {name: 'sara'}]; } true, false, true, false ...
  98. *ngFor uses the template import { Component } from '@angular/core';

    interface Person { name: string; } @Component({ selector: 'app-root', template: `<ng-template ngFor let-person [ngForOf]="persons" let-i="index" let-o="odd"> <div>{{i}} {{person.name}}</div> </ng-template>` }) export class AppComponent { persons: Person[] = [{name: 'jack'}, {name: 'tina'}, {name: 'paul'}, {name: 'sara'}]; } The *ngFor is compiled to this, but as you can see, *ngFor is much cleaner syntax and preferred
  99. Animations • Angular animations are built on top of standard

    Web Animations API • It helps if you know the Animations API when working with Angular animations • Angular animations have a specific modules • import { BrowserAnimationsModule } from '@angular/platform- browser/animations'; • To build a animation you can have states • When state changes, animation occurs
  100. State? export class LightbulbComponent { private _state = 'on'; toggle():

    boolean { this._state = (this._state === 'on') ? 'off' : 'on'; return false; } get state() { return this._state; } } State can be whatever. We can attach "listeners" to this. When state changes animation occurs
  101. import { BrowserModule } from '@angular/platform-browser'; import { NgModule }

    from '@angular/core'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { LightbulbComponent } from './lightbulb/lightbulb.component'; @NgModule({ declarations: [ AppComponent, LightbulbComponent ], imports: [ BrowserModule, BrowserAnimationsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } Import the animation's module
  102. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; Define animation when state changes
  103. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; Display just "On" or "Off" inside of p
  104. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; Changes the state
  105. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; Trigger lightBulbState and use "On" or "Off"..
  106. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; The lightBulbState is defined here
  107. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; When state is on, use this style
  108. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; When state is off, use this style
  109. import { Component } from '@angular/core'; import { trigger, state,

    style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" (click)="toggle()">{{state}}</p>', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on => off', animate('500ms ease-in')), transition('off => on', animate('500ms ease-out')) ]) ] }) export class LightbulbComponent { private _state = 'on'; What kind of transitions to use when changing the state
  110. @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" href="" (click)="toggle()">{{state}}</p>', styles: ['p

    { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('on <=> off', animate('500ms linear')) ]) ] }) If the same kind of animate, we can use <=> See http://easings.net/ for details about easy-in etc
  111. Using wildcards * • Match any state using * •

    on => * • * => off • * => *
  112. To define enter and leave: use void state • When

    element enters the view use • void => * • When element leaves the view use • * => void
  113. @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" href="" (click)="toggle()">{{state}}</p>', styles: ['p

    { display: inline-block; }'], animations: [ trigger('lightBulbState', [ state('on', style({ backgroundColor: 'yellow', transform: 'scale(1) rotate(360deg)' })), state('off', style({ backgroundColor: 'white', transform: 'scale(0.5) rotate(0deg)' })), transition('void => *', [...]), transition('* => void', [...]), ]) ] }) Instead of declaring the styles in states, now you declare them in the transition itself...
  114. @Component({ selector: 'app-lightbulb', template: '<p [@lightBulbState]="state" href="" (click)="toggle()">{{state}}</p>', styles: ['p

    { display: inline-block; }'], animations: [ trigger('lightBulbState', [ transition(':enter', [...]), transition(':leave', [...]), ]) ] }) You can also use a cleaner syntax here :enter == void => * :leave == * => void
  115. @Component({ selector: 'app-lightbulb', template: `<a (click)="toggle()">Toggle</a> <p *ngIf="state" [@lightBulbState]="state">Lightbulb!</p>`, styles:

    ['p { display: inline-block; }', 'a { display: block; }'], animations: [ trigger('lightBulbState', [ transition(':enter', [ style({transform: 'translateX(-200%)'}), animate(500) ]), transition(':leave', [ animate(500, style({opacity: 0, transform: 'translateX(500%)'})) ]) ]) ] }) export class LightbulbComponent { private _state = true; ... Will toggle the lightbulb off and on of the view state is now boolean because of the *ngIf When entering 1) set lightbulb off the screen 2)Animate it to original position When leaving, animate it to right and set opacity to 0 (invisible)
  116. Routing • Change HTML dynamically when URL changes • Entering

    URLs • Clicking links • Forward and back • Display particular component for a given URL • Use @Angular/Router library package • Then configure (which view is displayed on what URL)
  117. import { BrowserModule } from '@angular/platform-browser'; import { NgModule }

    from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AppComponent } from './app.component'; import { AddComponent } from './add/add.component'; import { ViewComponent } from './view/view.component'; const appRoutes: Routes = [ { path: 'add', component: AddComponent }, { path: 'view',component: ViewComponent }, ]; @NgModule({ declarations: [ AppComponent, AddComponent, ViewComponent ], imports: [ BrowserModule, RouterModule.forRoot(appRoutes, { enableTracing: true }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } Configuring Just for debugging
  118. app.component.html <h1> Welcome to {{title}}! </h1> <nav> <a routerLink="/add" routerLinkActive="active">Add</a>

    <a routerLink="/view" routerLinkActive="active">View</a> </nav> <div> <router-outlet></router-outlet> </div> Depending on the URL different components are injected after the router- outlet Creating links adds "active" CSS class so you can style the link differently
  119. @Component({ selector: 'app-root', template: `<h1> Welcome to {{title}}! </h1> <nav>

    <a routerLink="/add" routerLinkActive="thisischosen">Add</a> <a routerLink="/view" routerLinkActive="thisischosen">View</a> </nav> <div> <router-outlet></router-outlet> </div>`, styles: ['.thisischosen { background-color: lightgray; }'] }) export class AppComponent { css class
  120. wild card const appRoutes: Routes = [ { path: 'add',

    component: AddComponent }, { path: 'view', component: ViewComponent }, { path: '**', component: NotfoundComponent } ]; If any other url, then..
  121. Redirect const appRoutes: Routes = [ { path: 'add', component:

    AddComponent }, { path: 'view', component: ViewComponent }, { path: 'jussi', redirectTo: '/add', pathMatch: 'full' }, { path: '**', component: NotfoundComponent } ]; If path is http://localhost:4200/jussi the redirect to http://localhost:4200/add
  122. Redirect const appRoutes: Routes = [ { path: 'add', component:

    AddComponent }, { path: 'view', component: ViewComponent }, { path: 'jussi', redirectTo: '/add', pathMatch: 'prefix' }, { path: '**', component: NotfoundComponent } ]; If path is http://localhost:4200/jussi/jask the redirect to http://localhost:4200/add
  123. Redirect const appRoutes: Routes = [ { path: 'add', component:

    AddComponent }, { path: 'view', component: ViewComponent }, { path: '', redirectTo: '/add', pathMatch: 'full' }, { path: '**', component: NotfoundComponent } ]; If path is http://localhost:4200/ then redirect to http://localhost:4200/add
  124. import { NgModule } from '@angular/core'; import { RouterModule, Routes

    } from '@angular/router'; import { AddComponent } from './add/add.component'; import { ViewComponent } from './view/view.component'; import { NotfoundComponent } from './notfound/notfound.component'; const appRoutes: Routes = [ { path: 'add', component: AddComponent }, { path: 'view', component: ViewComponent }, { path: '', redirectTo: '/add', pathMatch: 'full' }, { path: '**', component: NotfoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot( appRoutes, { enableTracing: true } ) ], exports: [ RouterModule ] }) export class AppRoutingModule {} Separate the routing stuff into it's own module: app-routing.module.ts
  125. import { BrowserModule } from '@angular/platform-browser'; import { NgModule }

    from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { AddComponent } from './add/add.component'; import { ViewComponent } from './view/view.component'; import { NotfoundComponent } from './notfound/notfound.component'; @NgModule({ declarations: [ AppComponent, AddComponent, ViewComponent, NotfoundComponent ], imports: [ BrowserModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } Import it to the main module
  126. Lab

  127. View Module • The View part of the app displays

    • 1) List of posts • 2) When clicking a post a detailed information about the post • in routes • http://localhost:4200/view • http://localhost:4200/view/1 • We can define own module and own routing module for this • Can be overkill in small applications
  128. view/view.module.ts import { NgModule } from '@angular/core'; import { HttpClientModule

    } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { ViewComponent } from './view.component'; import { ViewDetailComponent } from './view-detail.component'; import { ViewRoutingModule } from './view-routing.module'; @NgModule({ imports: [ CommonModule, HttpClientModule, ViewRoutingModule ], declarations: [ ViewComponent, ViewDetailComponent ] }) export class ViewModule {} The view module has it's own routing Just the view components
  129. app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule

    } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { AddComponent } from './add/add.component'; import { NotfoundComponent } from './notfound/notfound.component'; import { ViewModule } from './view/view.module'; @NgModule({ declarations: [ AppComponent, AddComponent, NotfoundComponent ], imports: [ BrowserModule, ViewModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } Importing the module
  130. view/view-routing.module.ts import { NgModule } from '@angular/core'; import { RouterModule,

    Routes } from '@angular/router'; import { ViewComponent } from './view.component'; import { ViewDetailComponent } from './view-detail.component'; const mySubRoutes: Routes = [ { path: 'view', component: ViewComponent }, { path: 'view/:id', component: ViewDetailComponent } ]; @NgModule({ imports: [ // FOR CHILD! RouterModule.forChild(mySubRoutes) ], exports: [ RouterModule ] }) export class ViewRoutingModule { } "Sub routing" Instead of forRoot, use forChild!
  131. app-routing.module.ts import { NgModule } from '@angular/core'; import { RouterModule,

    Routes } from '@angular/router'; import { AddComponent } from './add/add.component'; // import { ViewComponent } from './view/view.component'; import { NotfoundComponent } from './notfound/notfound.component'; const appRoutes: Routes = [ { path: 'add', component: AddComponent }, // { path: 'view', component: ViewComponent }, { path: '', redirectTo: '/add', pathMatch: 'full' }, { path: '**', component: NotfoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot( appRoutes, { enableTracing: true } ) ], exports: [ RouterModule ] }) export class AppRoutingModule {} We can take away the view part from the main routing module
  132. view/view.component.ts import { Component, OnInit } from '@angular/core'; import {

    HttpClient } from '@angular/common/http'; import { Post } from './view-post'; @Component({ selector: 'app-view', template: `<ul><li *ngFor="let post of posts"><a routerLink="{{post.id}}">{{post.title}}</a></li></ul>` }) export class ViewComponent implements OnInit { posts: Post[] = []; constructor(private http: HttpClient) { } ngOnInit() { this.http.get<Post[]>('https://jsonplaceholder.typicode.com/posts').subscribe(jsonObject => { this.posts = jsonObject; }); } } Displays a list of posts. When clicking, change the url
  133. view/view-detail.component.ts import { Component, OnInit } from '@angular/core'; import {

    Router, ActivatedRoute, Params } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { Post } from './view-post'; @Component({ selector: 'app-view-detail', template: `<h2>{{post.title}}</h2><p>{{post.body}}</p>` }) export class ViewDetailComponent implements OnInit { post: Post = {id: undefined, title: '', body: '', userId: undefined}; constructor( private activatedRoute: ActivatedRoute, private http: HttpClient) {} ngOnInit() { // subscribe to "url" this.activatedRoute.params.subscribe((params: Params) => { // when changes, get id url value const id = params['id']; // Fetch content this.http.get<Post>('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } }
  134. import { Component, OnInit } from '@angular/core'; import { ActivatedRoute,

    Params } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { Post } from './view-post'; @Component({ selector: 'app-view-detail', template: `<h2>{{post.title}}</h2><p>{{post.body}}</p>` }) export class ViewDetailComponent implements OnInit { post: Post = {id: undefined, title: '', body: '', userId: undefined}; constructor( private activatedRoute: ActivatedRoute, private http: HttpClient) {} ngOnInit() { // subscribe to "url" this.activatedRoute.params.subscribe((params: Params) => { // when changes, get id url value const id = params['id']; // Fetch content this.http.get<Post>('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } } Getting the id value from url
  135. import { Component, OnInit } from '@angular/core'; import { Router,

    ActivatedRoute, Params } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { Post } from './view-post'; @Component({ selector: 'app-view-detail', template: `<h2>{{post.title}}</h2><p>{{post.body}}</p><button (click)="back()" href="">Back</button>` }) export class ViewDetailComponent implements OnInit { post: Post = {id: undefined, title: '', body: '', userId: undefined}; constructor( private router: Router, private activatedRoute: ActivatedRoute, private http: HttpClient) {} back() { this.router.navigate(['view']); } ngOnInit() { // subscribe to "url" this.activatedRoute.params.subscribe((params: Params) => { // when changes, get id url value const id = params['id']; // Fetch content this.http.get<Post>('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } } Navigating
  136. import { Component, OnInit } from '@angular/core'; import { Router,

    ActivatedRoute, Params } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { Post } from './view-post'; @Component({ selector: 'app-view-detail', template: `<h2>{{post.title}}</h2><p>{{post.body}}</p><button (click)="back()" href="">Back</button>` }) export class ViewDetailComponent implements OnInit { post: Post = {id: undefined, title: '', body: '', userId: undefined}; constructor( private router: Router, private activatedRoute: ActivatedRoute, private http: HttpClient) {} back() { this.router.navigate(['view', {id: this.post.id}]); } ngOnInit() { // subscribe to "url" this.activatedRoute.params.subscribe((params: Params) => { // when changes, get id url value const id = params['id']; // Fetch content this.http.get<Post>('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } } Giving the id back: http://localhost:4200/view;id=1
  137. view.component.ts import { Component, OnInit } from '@angular/core'; import {

    HttpClient } from '@angular/common/http'; import { Post } from './view-post'; import { ActivatedRoute, Params } from '@angular/router'; @Component({ selector: 'app-view', template: `<ul><li *ngFor="let post of posts" [class.selected]="post.id == selectedId"> <a routerLink="{{post.id}}">{{post.title}}</a></li></ul>`, styles: ['.selected { background-color: lightgray; }'] }) export class ViewComponent implements OnInit { posts: Post[] = []; constructor(private http: HttpClient, private activatedRoute: ActivatedRoute) { } private selectedId: number; ngOnInit() { this.activatedRoute.params.subscribe((params: Params) => { const id = params['id']; this.selectedId = id; }); this.http.get<Post[]>('https://jsonplaceholder.typicode.com/posts').subscribe(jsonObject => { this.posts = jsonObject; }); } } Get the value
  138. Example const appRoutes: Routes = [ { path: 'modify', component:

    ModifyComponent, children: [ {path: '', redirectTo: 'add', pathMatch: 'full'}, {path: 'add', component: AddComponent}, {path: 'delete', component: DeleteComponent} ]} ]; If url is modify/add then display ModifyComponent and inside of this display AddComponent
  139. App- Component (Main) import { Component } from '@angular/core'; @Component({

    selector: 'app-root', template: `<h1> Welcome to {{title}}! </h1> <nav> <a routerLink="/modify" routerLinkActive="thisischosen">Modify</a> <a routerLink="/view" routerLinkActive="thisischosen">View</a> </nav> <div> <router-outlet></router-outlet> </div>`, styles: ['.thisischosen { background-color: lightgray; }'] }) export class AppComponent { title = 'app'; } if modify/ and view/ then change the components
  140. Modify - Component import { Component, OnInit } from '@angular/core';

    @Component({ selector: 'app-modify', template: `<h3>Modify</h3> <nav> <a routerLink="add" routerLinkActive="selected">Add</a> | <a routerLink="delete" routerLinkActive="selected">Delete</a> </nav> <router-outlet></router-outlet>`, styles: ['.selected { background-color: lightgray; }'] }) export class ModifyComponent implements OnInit { constructor() { } ngOnInit() { } } Another <router-outlet>! If modify/add or modify/delete Notice the difference between /add and add
  141. Route Guards • Route Guards tells to the router if

    requested route is permitted or not • Decisision is just true or false comparison from a class that implements guard interface
  142. Simple Guard Service import { Injectable } from '@angular/core'; import

    { CanActivate } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { canActivate() { console.log('Called'); return true; } } true => can access, false => cannot access
  143. Setting up const mySubRoutes: Routes = [ { path: 'modify',

    component: ModifyComponent, canActivate: [AuthGuard], children: [ {path: '', redirectTo: 'add', pathMatch: 'full'}, {path: 'add', component: AddComponent}, {path: 'delete', component: DeleteComponent} ]}, ]; modify now protected under AuthGuard Remember to add AuthGuard to provide to the module
  144. Pipes • To display value transformations use pipes • Built-in

    pipes • currency, date, percent, uppercase, lowercase ... • Usage • {{ something | pipe }} • For example • {{ title | uppercase }}
  145. Pipe usage: uppercase import { Component } from '@angular/core'; @Component({

    selector: 'my-selector', template: `<h1>{{title | uppercase}}</h1> <planet-list></planet-list>` }) export class AppComponent { title : string = 'Planets' } PLANETS
  146. Pipe usage import { Component } from '@angular/core'; @Component({ selector:

    'my-selector', template: `<h1>{{title}}</h1> <p>Today is {{ todayDate | date:'dd.mm.yyyy'}} <planet-list></planet-list>` }) export class AppComponent { title : string = 'Planets' todayDate : Date = new Date() } Displays date in the following format
  147. Custom Pipe import { Component } from '@angular/core'; import {

    Planet } from './planet'; @Component({ selector: 'planet-list', template: `<ul> <li *ngFor="let planet of planets | myfilter: 'Alderaan'"> {{planet.name}} </li> </ul>` }) export class PlanetListComponent { planets: Planet[] = [ {"name": "Yavin IV", "diameter": 10200, population: 1000}, {"name": "Alderaan", "diameter": 12500, population: 2000000000}]; } Display only planets that contain 'Alderaan'
  148. filter.pipe.ts import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'myfilter'})

    export class MyFilterPipe implements PipeTransform { transform(items: any[], filter: string): any { // If no filter text given if(!filter) { return items } let newItems : any[] = [] for(let itemObject of items) { for(let key in itemObject) { // Make regex to the value let value : string = String(itemObject[key]) if(value.match(filter)) { newItems.push(itemObject) } } } return newItems } } items here contains the planets. The filter contains "Alderaan". Returns a array containing only planets that has "Alderaan
  149. Updating the Module.. import { BrowserModule } from '@angular/platform-browser'; import

    { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { PlanetListComponent } from './planet-list/planet-list.component'; import { MyFilterPipe } from './filter.pipe'; @NgModule({ declarations: [ AppComponent, PlanetListComponent, MyFilterPipe ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
  150. Usage import { Component } from '@angular/core'; import { Planet

    } from './planet'; @Component({ selector: 'planet-list', template: `<br><input type="text" placeholder="filter" [(ngModel)]="filterText" /> <ul> <li *ngFor="let planet of planets | myfilter: filterText"> {{planet.name}} </li> </ul>` }) export class PlanetListComponent { planets: Planet[] = [ {"name": "Yavin IV", "diameter": 10200, population: 1000}, {"name": "Alderaan", "diameter": 12500, population: 2000000000}]; filterText: string = "" } Filttering according to user input
  151. Service • Angular service is a class that encapulates some

    sort of functionality and provides a service to the rest of the application • In AngularJS 1.X you had service, factory, provider but no more. Just class.
  152. Simple Service: planet.service.ts import { Injectable } from '@angular/core'; import

    { Planet } from './planet'; @Injectable() export class PlanetService { fetch() : Planet[] { return [{"name": "Yavin IV", "diameter": 10200, population: 1000}, {"name": "Alderaan", "diameter": 12500, population: 2000000000}]; } } Now the planets are coming from a service
  153. Component: planet-list.component.ts import { Component } from '@angular/core'; import {

    Planet } from '../planet'; import { PlanetService } from '../planet.service'; @Component({ selector: 'planet-list', template: ` <ul> <li *ngFor="let planet of planets"> {{planet.name}} </li> </ul>`, providers: [PlanetService] }) export class PlanetListComponent { planets: Planet[] = [] constructor(planetService : PlanetService) { this.planets = planetService.fetch(); } } When creating the object from PlaneListComponent Angular will inject the service The Service is available only in this component. Declare providers in main component or even module if you want it to be available throughout your app.
  154. Component: planet-list.component.ts import { Component } from '@angular/core'; import {

    Planet } from '../planet'; import { PlanetService } from '../planet.service'; @Component({ selector: 'planet-list', template: ` <ul> <li *ngFor="let planet of planets"> {{planet.name}} </li> </ul>`, providers: [PlanetService] }) export class PlanetListComponent { planets: Planet[] = [] constructor(planetService : PlanetService) { this.planets = planetService.fetch(); } } We can remove this and add it to module
  155. module: app.module.ts import { NgModule } from '@angular/core'; import {

    BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { PlanetListComponent } from './planet-list/planet-list.component'; import { AppComponent } from './app.component'; import { PlanetService } from './planet.service'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, PlanetListComponent ], providers: [ PlanetService ], bootstrap: [ AppComponent ] }) export class AppModule { } Now the service available throughout your app
  156. Lab

  157. Connecting to Backend • Front-end apps communicate with backend services

    using HTTP • Two different APIs: XMLHttpRequest and Fetch API • Angular provides HttpClient • Build on top of XMLHttpRequest • HttpClient is implemented in HttpClientModule which needs to be imported first • Once installed you can inject the service
  158. import { BrowserModule } from '@angular/platform-browser'; import { NgModule }

    from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { PlanetListComponent } from './planet-list/planet-list.component'; @NgModule({ declarations: [ PlanetListComponent ], imports: [ BrowserModule, HttpClientModule ], providers: [], bootstrap: [PlanetListComponent] }) export class AppModule { } Now you can inject HttpClient in all declared components
  159. import { Component } from '@angular/core'; import { Planet }

    from './planet'; import { HttpClient } from '@angular/common/http'; import { OnInit } from '@angular/core' @Component({ selector: 'planet-list', template: `<ul> <li *ngFor="let planet of planets"> {{planet.name}} </li> </ul>` }) export class PlanetListComponent implements OnInit { public planets: Planet[] = []; private http: HttpClient constructor(http: HttpClient) { this.http = http } ngOnInit(): void { this.http.get('https://swapi.co/api/planets/').subscribe(jsonObject => { // {"count": 61, "next": "", "results": [...]} this.planets = jsonObject['results']; }); } } Import HttpClient Service Injects automatically the HttpClient AJAX call to backend
  160. ngOnInit(): void { this.http.get('https://swapi.co/api/planets/') .subscribe(jsonObject => { console.log(typeof jsonObject) //

    this.planets = jsonObject['results'] this.planets = jsonObject.results }); } It is object Will complain because it may not have results property
  161. Backend JSON: https://swapi.co/api/planets/ { "count": 61, "next": "https://swapi.co/api/planets/?page=2", "previous": null,

    "results": [ { "name": "Alderaan", "rotation_period": "24", "orbital_period": "364", "diameter": "12500", "climate": "temperate", "gravity": "1 standard", "terrain": "grasslands, mountains", "surface_water": "40", ...
  162. import { ItemResponse } from './itemresponse'; ngOnInit(): void { this.http.get<ItemResponse>('https://swapi.co/api/planets/')

    .subscribe(jsonObject => { this.planets = jsonObject.results }); } Define the type of given json object. This is an interface that we have to implement It should now work
  163. ItemResponse Interface import { Planet } from './planet' export interface

    ItemResponse { count: number; next: string; previous: string; results: [Planet] } Planet - array
  164. Getting Headers ngOnInit(): void { this.http.get<ItemResponse>('https://swapi.co/api/planets/', {observe: 'response'}).subscribe(response => {

    // application/json console.log(response.headers.get('Content-type')) // 200 console.log(response.status) this.planets = response.body.results; }); } Pass configuration object HttpResponse<ItemRespon
  165. HTTP POST • HTTP Post is easy too • http.post(url,

    body).subscribe((result) => {}) • You can also give configuration object • http.post(url, body, {observe: 'response'}).subscribe((response) => {}) • And use interfaces • http.post<SomeInterface>(url, body).subscribe((result) => {})
  166. import { Component } from '@angular/core'; import { HttpClient }

    from '@angular/common/http' interface LocationResponse { id: number, latitude: number, longitude: number } @Component({ selector: 'add-location', template: `<input type="text" placeholder="latitude" name="latitudeText" [(ngModel)]="latitude" /> <input type="text" placeholder="longitude"name="longitudeText" [(ngModel)]="longitude" /> <button (click)="sendToServer()">Send</button> <p>{{msg}}</p>` }) export class AddLocationComponent { latitude: number longitude: number msg: string = "" private url = "http://localhost:8080/api/locations/" constructor(private http: HttpClient) {} sendToServer() { let body = {"latitude": this.latitude, "longitude": this.longitude} this.http.post<LocationResponse>(this.url, body, {observe: 'response'} ).subscribe(response => { if(response.status === 201) { this.msg = `Saved with id = ${response.body.id}` } }) } } Expecting this data from backend Sending Json object to backend If we got 201 CREATED then display it in UI
  167. Error Handling • If request fails or getting 4xx results?

    • Add error handler • http.post(url).subscribe(successFunc, errFunc) • You will get argument of type HttpErrorMessage to the errFunc • The errFunc is called if backend responds with 4xx, 5xx • Also if frontend creates exception
  168. export class AddLocationComponent { latitude: number longitude: number msg: string

    = "" private url = "http://localhost:8080/api/locations/" constructor(private http: HttpClient) {} sendToServer() { let body = {"latitude": this.latitude, "longitude": this.longitude} this.http.post<LocationResponse>(this.url, body, {observe: 'response'} ) .subscribe(this.success, this.error) } error(err: HttpErrorResponse) { console.log('error' + err.error.message) } success(response : HttpResponse<LocationResponse>) { let locationObject : LocationResponse = response.body console.log(locationObject.id) console.log(locationObject.latitude) console.log(locationObject.longitude) } } Calls functions
  169. export class AddLocationComponent { latitude: number longitude: number msg: string

    = "" private url = "http://localhost:8080/api/locations/" constructor(private http: HttpClient) {} sendToServer() { let body = {"latitude": this.latitude, "longitude": this.longitude} this.http.post<LocationResponse>(this.url, body, {observe: 'response'} ) .subscribe(this.success, this.error) } error(err: HttpErrorResponse) { this.msg = "Some problem with saving data." } success(response : HttpResponse<LocationResponse>) { let locationObject : LocationResponse = response.body this.msg = `Saved with an id of ${locationObject.id}` } } Modify the UI Modify the UI Binded to view Does not work!
  170. Working Example sendToServer() { let body = {"latitude": this.latitude, "longitude":

    this.longitude} this.http.post<LocationResponse>(this.url, body, {observe: 'response'} ) .subscribe((response) => { let locationObject : LocationResponse = response.body this.msg = `Saved with an id of ${locationObject.id}` }) } How come the anonymous arrow function work? It uses closures...
  171. Under the Engine... sendToServer() { let body = {"latitude": this.latitude,

    "longitude": this.longitude} let _this = this this.http.post<LocationResponse>(this.url, body, {observe: 'response'} ) .subscribe((response) => { let locationObject : LocationResponse = response.body _this.msg = `Saved with an id of ${locationObject.id}` }) } _this is still in memory because closure
  172. export class AddLocationComponent { latitude: number longitude: number msg: string

    = "" private url = "http://localhost:8080/api/locations/" constructor(private http: HttpClient) {} sendToServer() { let body = {"latitude": this.latitude, "longitude": this.longitude} this.http.post<LocationResponse>(this.url, body, {observe: 'response'} ) .subscribe(this.success, this.error) } error(err: HttpErrorResponse) { this.msg = "Some problem with saving data." } success(response : HttpResponse<LocationResponse>) { let locationObject : LocationResponse = response.body this.msg = `Saved with an id of ${locationObject.id}` } } this does NOT refer to AddLocationComponent object anymore
  173. export class AddLocationComponent { latitude: number longitude: number msg: string

    = "" private url = "http://localhost:8080/api/locations/" constructor(private http: HttpClient) {} sendToServer() { let body = {"latitude": this.latitude, "longitude": this.longitude} this.http.post<LocationResponse>(this.url, body, {observe: 'response'} ) .subscribe(this.success.bind(this), this.error.bind(this)) } error(err: HttpErrorResponse) { this.msg = "Some problem with saving data." } success(response : HttpResponse<LocationResponse>) { let locationObject : LocationResponse = response.body this.msg = `Saved with an id of ${locationObject.id}` } } Function binding to the rescue!
  174. Material Design • Material Design is a design language developed

    by Google • Most of Google's mobile apps had applied this style • Gmail, Youtube, Docs.. • Also Web interfaces of these apps! • Google has developed prebuilt UI components for Angular that follows this Material Design • Although it's possible to override the styling of these • In addition of providing look and feel you will have basic implementation of various UI components
  175. Angular Material • Material Design Components for Angular • https://material.angular.io

    • Fast and Consistent • Versatile • Optimized for Angular
  176. Getting Started • Install Angular Material and Angular CDK (Component

    Development Kit) • npm install --save @angular/material @angular/cdk • Include BrowserAnimationsModule in your app • imports: [BrowserAnimationsModule] • Include Modules from Angular Material • import {MatButtonModule, MatCheckboxModule} from '@angular/material'; • ...imports: [MatButtonModule, MatCheckboxModule] • Include a theme in styles.css • @import "~@angular/material/prebuilt-themes/indigo-pink.css"; • Install gesture support (needed for couple of the components) • npm install --save hammerjs • Add Material Icons in index.html • <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  177. Using Components • See documentation about the components • https://material.angular.io/components/categories

    • The documentation is really basic, rely on the examples given! • Different Components for • Form, Navigation, Layout, Buttons, Popups & Modals, Data table
  178. Example Template <h3>Raised Buttons</h3> <div class="button-row"> <button mat-raised-button>Basic</button> <button mat-raised-button

    color="primary">Primary</button> <button mat-raised-button color="accent">Accent</button> <button mat-raised-button color="warn">Warn</button> <button mat-raised-button disabled>Disabled</button> </div>
  179. User Input and $event • To bind to a DOM

    event • <button (click)="onClickMe()">Click me!</button> • Using $event • <input (keyup)="onKey($event)"> • The corresponding event object is given to the onKey – method • The properties of the $event object depends on the type of DOM event • Mouse event is different than key event
  180. $event import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: `<input (keyup)="doSomething($event)" placeholder="Write"> <p (click)="doSomething($event)">Click me</p>` }) export class AppComponent { doSomething(event: Event) { console.log(event); } } Base class for all the DOM Events Either MouseEvent or KeyboardEvent
  181. $event import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: `<input (keyup)="doSomething($event)" placeholder="Write"> <p (click)="doSomething($event)">Click me</p>` }) export class AppComponent { doSomething(event: Event) { console.log(event.target); } } Either <input ..> or <p>..
  182. $event import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: `<input (keyup)="doSomething($event)" placeholder="Write"> <p (click)="doSomething($event)">Click me</p>` }) export class AppComponent { doSomething(event: Event) { console.log(event.target.value); } } Angular will warn about this. The value is not present in event.target
  183. import { Component } from '@angular/core'; @Component({ selector: 'app-root', template:

    `<input (keyup)="doSomething($event)" placeholder="Write"> <p (click)="doSomething($event)">Click me</p>` }) export class AppComponent { doSomething(event: Event) { const givenTarget: EventTarget = event.target; if (givenTarget instanceof HTMLInputElement) { const input: HTMLInputElement = givenTarget; console.log(input.value); } else if (givenTarget instanceof HTMLElement) { console.log(givenTarget.textContent); } } } Will output "Click Me" Will output given text Interface that HTMLInputElement and HTMLElement implemets
  184. import { Component } from '@angular/core'; @Component({ selector: 'app-root', template:

    `<input (keyup)="doSomething($event)" placeholder="Write">` }) export class AppComponent { doSomething(event: KeyboardEvent) { console.log(event.key); // const givenTarget: HTMLInputElement = event.target as HTMLInputElement; // console.log(givenTarget.value); } } If we use KeyboardEvent directly, then we can use it's properties..
  185. Template Reference Variable import { Component } from '@angular/core'; @Component({

    selector: 'app-root', template: `<input #donaldduck (keyup)="0" placeholder="Write"> {{donaldduck.value}}` }) export class AppComponent { /* doSomething(event: KeyboardEvent) { const givenTarget: HTMLInputElement = event.target as HTMLInputElement; console.log(givenTarget.value); }*/ } Create new variable that refers to the <input>. No need for the event function! Must have something in here
  186. keyup.enter import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: `<input (keyup.enter)="enterIsPressed($event)" placeholder="Write"> {{value}}` }) export class AppComponent { value = ''; enterIsPressed(event: KeyboardEvent) { const givenTarget: HTMLInputElement = event.target as HTMLInputElement; this.value = givenTarget.value; } } Angular's own event When enter is pressed, display the results
  187. key.enter and Template Ref Variable import { Component } from

    '@angular/core'; @Component({ selector: 'app-root', template: `<input #donaldduck (keyup.enter)="enterIsPressed(donaldduck.value)" placeholder="Write"> {{value}}` }) export class AppComponent { value = ''; enterIsPressed(value: string) { this.value = value; } } Send a string A lot simpler code now
  188. blur import { Component } from '@angular/core'; @Component({ selector: 'app-root',

    template: `<input #donaldduck (keyup.enter)="enterIsPressed(donaldduck.value)" placeholder="Write" (blur)="enterIsPressed(donaldduck.value)"> {{value}}` }) export class AppComponent { value = ''; enterIsPressed(value: string) { this.value = value; } } If user is leaving the input without typing enter, do exactly the same
  189. import { Component } from '@angular/core'; @Component({ selector: 'app-root', template:

    `<form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label> <input type="text" id="name" name="user_name"> </div> <div> <label for="mail">E-mail:</label> <input type="email" id="mail" name="user_mail" required> </div> <div> <button type="submit">Save</button> </div> </fieldset> </form>`, styles: [''] }) export class AppComponent { }
  190. interface Person { name?: string; email: string; } @Component({ selector:

    'app-root', template: `<form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label> <input type="text" id="name" name="user_name" [(ngModel)]="userInput.name"> </div> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" [ngModel]="userInput.email" required> </div> <div> <button type="submit">Save</button><button (click)="fill()" type="button">Fill</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> </form>` }) export class AppComponent { userInput: Person = {name: '', email: ''}; get debug() { return JSON.stringify(this.userInput); } fill() { this.userInput.name = 'Kalle'; this.userInput.email = '[email protected]'; } } Simple interface for user input. Name is optional [(ngModel)] => two-way binding [ngModel] => one-way binding email is not visible when user gives input email is visible in the UI
  191. omponent({ selector: 'app-root', template: `<form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label>

    <input type="text" id="name" name="user_name" #nameElement [(ngModel)]="userInput.name"> <div>{{nameElement.className}}</div> </div> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement [(ngModel)]="userInput.email" require <div>{{emailElement.className}}</div> </div> <div> <button type="submit">Save</button><button (click)="fill()" type="button">Fill</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> form>` Variable the refers to the current <input>-element Display styles (classes) of the input
  192. Tracking Changes State Class if true Class if false The

    control has been visited. ng-touched ng-untouched The control's value has changed. ng-dirty ng-pristine The control's value is valid. ng-valid ng-invalid
  193. omponent({ selector: 'app-root', template: `<form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label>

    <input type="text" id="name" name="user_name" #nameElement [(ngModel)]="userInput.name"> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement [(ngModel)]="userInput.email" require <div [hidden]="emailElement.className.includes('ng-valid')"> E-mail is required </div> </div> </div> <div> <button type="submit">Save</button><button (click)="fill()" type="button">Fill</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> form>` Will toggle css hide or show.
  194. omponent({ selector: 'app-root', template: `<form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label>

    <input type="text" id="name" name="user_name" #nameElement [(ngModel)]="userInput.name"> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement [(ngModel)]="userInput.email" require <div [hidden]="emailElement.className.includes('ng-valid')"> E-mail is required </div> </div> </div> <div> <button type="submit">Save</button><button (click)="fill()" type="button">Fill</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> form>`, yles: ['.ng-valid { border-left: 20px solid green; }', '.ng-invalid { border-left: 20px solid red; }'] Styling
  195. omponent({ selector: 'app-root', template: `<form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label>

    <input type="text" id="name" name="user_name" #nameElement [(ngModel)]="userInput.name"> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement [(ngModel)]="userInput.email" require <div [hidden]="emailElement.className.includes('ng-valid')"> E-mail is required </div> </div> </div> <div> <button type="submit">Save</button><button (click)="fill()" type="button">Fill</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> form>`, yles: ['.ng-valid[required] { border-left: 20px solid green; }', '.ng-invalid:not(form) { border-left: 20px solid red; }'] Styling
  196. Modification <form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label> <input type="text" id="name"

    name="user_name" #nameElement [(ngModel)]="userInput.name"> </div> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement [(ngModel)]="userInput.email" required> <div [hidden]="emailElement.className.includes('ng-valid') || emailElement.className.includes('ng-pristine')"> E-mail is required </div> <div><code>{{emailElement.className}}</code></div> </div> <div> <button type="submit">Save</button><button (click)="fill()" type="button">Fill</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> </form> Now error message is hidden form is loaded for the first tim
  197. Modification <form> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label> <input type="text" id="name"

    name="user_name" #nameElement [(ngModel)]="userInput.name"> </div> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement="ngModel" [(ngModel)]="userInput.email" required> <div [hidden]="emailElement.valid || emailElement.pristine"> E-mail is required </div> <div><code>{{emailElement.className}}</code></div> </div> <div> <button type="submit">Save</button><button (click)="fill()" type="button">Fill</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> </form> Now emailElement type is NgModel class (directive) And it has several properties
  198. Form Validation <form (ngSubmit)="onSubmit()" #formElement> <fieldset><legend>Person Information</legend> <div> <label for="name">Name:</label>

    <input type="text" id="name" name="user_name" #nameElement [(ngModel)]="userInput.name"> </div> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement="ngModel" [(ngModel)]="userInput.email" required> <div [hidden]="emailElement.valid || emailElement.pristine"> E-mail is required </div> <div><code>{{emailElement.className}}</code></div> </div> <div> <button type="submit">Save</button> </div> <div>User input: <code>{{debug}}</code></div> <div>{{formElement.className}}</div> </fieldset> </form> Will have ng-valid if all of the form items are valid
  199. NgForm and Form Validation <form (ngSubmit)="onSubmit()" #formElement="ngForm"> <fieldset><legend>Person Information</legend> <div>

    <label for="name">Name:</label> <input type="text" id="name" name="user_name" #nameElement [(ngModel)]="userInput.name"> </div> <div> <label for="mail">E-mail:</label> <input type="text" id="mail" name="user_mail" #emailElement="ngModel" [(ngModel)]="userInput.email" required> <div [hidden]="emailElement.valid || emailElement.pristine"> E-mail is required </div> <div><code>{{emailElement.className}}</code></div> </div> <div> <button type="submit" [disabled]="!formElement.form.valid">Save</button> </div> <div>User input: <code>{{debug}}</code></div> </fieldset> </form>`, Will disable and enable the button if whole form is valid or not
  200. Template Driven Forms and Validation • The valid of input

    control is determined by using standard HTML5 • Is valid if text given • <input type="text" ... required> • Is valid if text follows the regex • <input type="text" pattern="[a-z0-9+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" ... required> • Is valid if given 6 chars • <input type="text" minlength="6" maxlength="6" ... required> • See • https://developer.mozilla.org/en- US/docs/Learn/HTML/Forms/Form_validation
  201. Template vs Reactive Template • Easy to use • For

    simple scenarios • Two way databinding • Minimal Component code • Unit testing can be hard Reactive • More flexible • For more complex scenarios • No automatic databinding • More component code, less HTML • Easier unit testing
  202. Module import { BrowserModule } from '@angular/platform-browser'; import { NgModule

    } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } Import the needed module
  203. FormControl import { Component } from '@angular/core'; import { FormControl

    } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: ` <h2>Person Detail</h2> <h3>Person Form</h3> <label>Name:</label> <input type="text" [formControl]="name">` }) export class AppComponent { name = new FormControl(); persons: Person[] = [{name: 'jack', age: 30}, {name: 'tina', age: 20}]; } Link the input to the formcontrol
  204. FormGroup import { Component } from '@angular/core'; import { FormControl,

    FormGroup } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: ` <h2>Person Detail</h2> <h3>Person Form</h3> <form [formGroup]="personForm"> <label>Name:</label> <input type="text" formControlName="name"> <label>Age:</label> <input type="text" formControlName="age"> </form>` }) export class AppComponent { personForm = new FormGroup({name: new FormControl(), age: new FormControl()}); persons: Person[] = [{name: 'jack', age: 30}, {name: 'tina', age: 20}]; } Link the the whole form to FormGroup Because input is not standalone, instead of using [formControl] use formControlName
  205. Form Data Model import { Component } from '@angular/core'; import

    { FormControl, FormGroup } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: ` <h2>Person Detail</h2> <h3>Person Form</h3> <form [formGroup]="personForm"> <label>Name:</label> <input type="text" formControlName="name"> <label>Age:</label> <input type="text" formControlName="age"> </form> <p>Form value: {{ personForm.value | json }}</p>` }) export class AppComponent { personForm = new FormGroup({name: new FormControl(), age: new FormControl()}); persons: Person[] = [{name: 'jack', age: 30}, {name: 'tina', age: 20}]; } Use .value to get the form data. The json pipe here transforms it to string
  206. FormBuilder import { Component } from '@angular/core'; import { FormControl,

    FormGroup, FormBuilder } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: ` <h2>Person Detail</h2> <h3>Person Form</h3> <form [formGroup]="personForm"> <label>Name:</label> <input type="text" formControlName="name"> <label>Age:</label> <input type="text" formControlName="age"> </form> <p>Form value: {{ personForm.value | json }}</p>` }) export class AppComponent { persons: Person[] = [{name: 'jack', age: 30}, {name: 'tina', age: 20}]; // personForm = new FormGroup({name: new FormControl(), age: new FormControl()}); personForm: FormGroup; constructor(private fb: FormBuilder) { this.personForm = fb.group({name: '', age: ''}); } } Use FormBuilder to quickly create the FormGroup
  207. Simple validation import { Component } from '@angular/core'; import {

    FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: ` <h2>Person Detail</h2> <h3>Person Form</h3> <form [formGroup]="personForm"> <label>Name:</label> <input type="text" formControlName="name"> <label>Age:</label> <input type="text" formControlName="age"> </form> <p>Form value: {{ personForm.value | json }}</p> <p>Form status: {{ personForm.status | json }}</p>` }) export class AppComponent { persons: Person[] = [{name: 'jack', age: 30}, {name: 'tina', age: 20}]; personForm: FormGroup; constructor(private fb: FormBuilder) { this.personForm = fb.group({name: ['', Validators.required], age: ''}); } } Pass an array of values. First the default value and then a validator Will output valid or invalid depending if name was given.
  208. Using Compose and Several Validators export class AppComponent { persons:

    Person[] = [{name: 'jack', age: 30}, {name: 'tina', age: 20}]; personForm: FormGroup; constructor(private fb: FormBuilder) { this.personForm = fb.group({name: ['', Validators.required], age: ['', Validators.compose([Validators.required, Validators.min(0), Validators.max(150)])]}); } }
  209. Custom Validator import { Component } from '@angular/core'; import {

    FormControl, FormGroup, FormBuilder, Validators, AbstractControl, ValidationErrors } from '@angular/forms'; function forbiddenNameValidator(control: AbstractControl): ValidationErrors | null { const value = control.value; if (value === 'Jussi') { return {'error': 'wrong name'}; } else { return null; } } Will receive the control If value is wrong, return error object, type is ValidationError which basically means a object with one key value pair and value is string return null if validation was success
  210. Usage export class AppComponent { personForm: FormGroup; constructor(private fb: FormBuilder)

    { this.personForm = fb.group({name: ['', forbiddenNameValidator], age: ['', Validators.compose([Validators.required, Validators.min(0), Validators.max(150)])]}); } }
  211. Arguments function validateNameByRegexValidator(nameRegExp: RegExp) { function forbiddenNameValidator(control: AbstractControl): ValidationErrors |

    null { const value = control.value; if (nameRegExp.test(value)) { return {'error': 'wrong name'}; } else { return null; } } return forbiddenNameValidator; }
  212. Usage export class AppComponent { persons: Person[] = [{name: 'jack',

    age: 30}, {name: 'tina', age: 20}]; personForm: FormGroup; constructor(private fb: FormBuilder) { this.personForm = fb.group({name: ['', validateNameByRegexValidator(/^Jussi$/i)], age: ['', Validators.compose([Validators.required, Validators.min(0), Validators.max(150)])]}); } }
  213. Properties Property Description myControl.value the value of a FormControl. myControl.status

    the validity of a FormControl. Possible values: VALID, INVALID, PENDING, or DISABLED. myControl.pristine true if the user has not changed the value in the UI. Its opposite is myControl.dirty. myControl.untouched true if the control user has not yet entered the HTML control and triggered its blur event. Its opposite is myControl.touched.
  214. @Component({ selector: 'app-root', template: ` <h2>Person Detail</h2> <h3>Person Form</h3> <form

    [formGroup]="personForm"> <label>Name:</label> <input type="text" formControlName="name"> <label>Age:</label> <input type="text" formControlName="age"> </form> <p>name = {{personForm.get('name').value}}</p> <p>name status = {{personForm.get('name').status}}</p> <p>name pristine = {{personForm.get('name').pristine}}</p> <p>name dirty = {{personForm.get('name').dirty}}</p> <p>name untouched = {{personForm.get('name').untouched}}</p> <p>name touched = {{personForm.get('name').touched}}</p>` })
  215. setValue and Mapping class Person { name = ''; age:

    number = null; } export class AppComponent { personForm: FormGroup; userInput: Person = new Person(); constructor(private fb: FormBuilder) { this.personForm = fb.group({name: ['', validateNameByRegexValidator(/^Jussi$/i)], age: ['', Validators.compose([Validators.required, Validators.min(0), Validators.max(150)])]}); this.personForm.setValue({name: this.userInput.name, age: this.userInput.age}); } } Mapping user given values to userInput object
  216. Dynamic Forms • If you have a lot of forms

    that change during the lifecycle of your apps, you could consider dynamic forms • Dynamic forms are just reactive forms that are built dynamically
  217. import { Component, OnInit } from '@angular/core'; import { QuestionBase

    } from './question-base'; import { QuestionControlService } from './question-control-service'; import { FormGroup } from '@angular/forms'; import { TextboxQuestion } from './question-textbox'; @Component({ selector: 'app-root', template: `<form [formGroup]="form"> <div *ngFor="let question of questions"> <app-question [question]="question" [form]="form"></app-question> </div> <button type="submit" [disabled]="!form.valid">Save</button> </form>` }) export class AppComponent implements OnInit { questions: QuestionBase[] = []; form: FormGroup; constructor(private qcs: QuestionControlService) { this.questions.push(new TextboxQuestion({key: 'name', label: 'Name', textboxtype: 'string', required: true}), new TextboxQuestion({key: 'age', label: 'Age', textboxtype: 'number', required: false})); } ngOnInit() { this.form = this.qcs.toFormGroup(this.questions); } } For each question, create form element questions - array Add different questions. Both TextboxQuestion and QuestionBase are classes that one must implement
  218. import { Component, OnInit } from '@angular/core'; import { QuestionBase

    } from './question-base'; import { QuestionControlService } from './question-control-service'; import { FormGroup } from '@angular/forms'; import { TextboxQuestion } from './question-textbox'; @Component({ selector: 'app-root', template: `<form [formGroup]="form"> <div *ngFor="let question of questions"> <app-question [question]="question" [form]="form"></app-question> </div> <button type="submit" [disabled]="!form.valid">Save</button> </form>` }) export class AppComponent implements OnInit { questions: QuestionBase[] = []; form: FormGroup; constructor(private qcs: QuestionControlService) { this.questions.push(new TextboxQuestion({key: 'name', label: 'Name', textboxtype: 'string', required: true}), new TextboxQuestion({key: 'age', label: 'Age', textboxtype: 'number', required: false})); } ngOnInit() { this.form = this.qcs.toFormGroup(this.questions); } } Reactive Form, used for validation Service that transforms the questions into FormGroup Array to FormGroup
  219. QuestionBase // Label to be shown in UI label?: string;

    // textbox, slider, ...? controlType?: string; // key to differentiate the form elements key?: string; // Is it required or not? required?: boolean; TextboxQuestion controlType: string = "textbox" // numeric, text...? textboxtype: string;
  220. export interface Options { // Label to be shown in

    UI label?: string; // textbox, slider, ...? controlType?: string; // key to differentiate the form elements key?: string; // Is it required or not? required?: boolean; } /** * Base class for all the form input elements */ export class QuestionBase { label: string; controlType: string; key: string; required: boolean; constructor(options: Options) { this.label = options.label; this.controlType = options.controlType; this.key = options.key; this.required = options.required; } }
  221. import { QuestionBase, Options } from './question-base'; /** * TextBox

    - element */ export class TextboxQuestion extends QuestionBase { // Will be textbox, overrides the base classes control type controlType = 'textbox'; // Is it numeric, text ..? textboxtype: string; constructor(options: Object) { super(options); this.textboxtype = options['textboxtype']; } }
  222. import { Injectable } from '@angular/core'; import { FormControl, FormGroup,

    Validators } from '@angular/forms'; import { QuestionBase } from './question-base'; /** * Service that transforms questions to FormGroup. */ @Injectable() export class QuestionControlService { /** * Transforms questions-array to FormGroup. * * @param questions list of questions. * @return FormGroup containing the questions. */ toFormGroup(questions: QuestionBase[] ): FormGroup { const group: any = {}; for (const question of questions) { if (question.required) { group[question.key] = new FormControl('', Validators.required); } else { group[question.key] = new FormControl(); } } // return new FormGroup({name: new FormControl(), age: new FormControl()}) return new FormGroup(group); } } Creates on object that contains FormControls Returns the FormGroup
  223. import { Component, OnInit, Input } from '@angular/core'; import {

    FormGroup } from '@angular/forms'; import { TextboxQuestion } from '../question-textbox'; import { QuestionBase } from '../question-base'; import { Form } from '@angular/forms'; @Component({ selector: 'app-question', template: `<div [formGroup]="form"><label>{{question.label}}</label> <input [type]="question.textboxtype" [formControlName]="question.key">{{question.key}}</div>` }) export class QuestionComponent implements OnInit { @Input() question: QuestionBase; @Input() form: FormGroup; constructor() { } ngOnInit() { } } For each question create input. You could do ngSwich here and create different input elements by the type of the question formControlName requires that formGroup is present in the component