Slide 1

Slide 1 text

Angular - platform Jussi Pohjolainen

Slide 2

Slide 2 text

Different Libraries React 2 AngularJS Angular 2+ JavaScript TypeScript Vue ... ... TypeScript Recommendation

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

TypeScript

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Example of types function repeat(mystring : string, amount : number) : string { let returnValue : string = "" for(let i=0; i

Slide 9

Slide 9 text

Let's compile the index.ts to bundle.js

Slide 10

Slide 10 text

Running in Browser Title

Loading...

Slide 11

Slide 11 text

Watch - mode By running in watch mode (-w) it detects changes in ts file and compiles it

Slide 12

Slide 12 text

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'

Slide 13

Slide 13 text

Lab

Slide 14

Slide 14 text

TypeScript types • boolean • number • string • array • tuple • enum • any • void • null • undefined • never

Slide 15

Slide 15 text

Array let list1 : number[] = [1,2,3] let list2 : Array = [1,2,3]

Slide 16

Slide 16 text

Tuple: Define the types in array let tuple: [string, number, boolean] tuple[0] = 'hello' tuple[1] = 5 tuple[2] = true

Slide 17

Slide 17 text

Enum enum Day {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday} let today : Day = Day.Monday console.log(today) // 0

Slide 18

Slide 18 text

Any: type can change let variable : any variable = 1 variable = "hello" let array : any[] = [1, 'hello', true]

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

never function error() : never { throw new Error("this will never return a type") }

Slide 21

Slide 21 text

let vs var vs const • var • scope either global or function • let • block scoping • const • block scoping, cannot change

Slide 22

Slide 22 text

Object Oriented Concepts

Slide 23

Slide 23 text

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 (?)

Slide 24

Slide 24 text

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!

Slide 25

Slide 25 text

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)

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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') } }

Slide 28

Slide 28 text

Visibility • Just like in other languages • public (default) • private • protected

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Generics class List { private list: T[] = [] public add (element: T) { this.list.push(element) } public removeEveryOther() { let newList : T[] = [] for(let i=0; i() mylist.add(1); mylist.add(2); mylist.add(3); mylist.add(4); mylist.removeEveryOther() mylist.printAll() Let's define the T - type at runtime

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Exporting: person.ts export interface Person { name: string, age: number }

Slide 35

Slide 35 text

Exporting interface Person { name: string, age: number } export { Person };

Slide 36

Slide 36 text

Rename: Exporting interface Person { name: string, age: number } export { Person as Henkilo };

Slide 37

Slide 37 text

Importing import { Person } from "./person"; let jackObject : Person = {name: "jack", age: 40}; console.log(jackObject);

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Multiexport interface Person { name: string, age: number } interface Movie { director: Person, title: string } export { Person, Movie };

Slide 40

Slide 40 text

Multi-import import { Person, Movie } from "./person"; let jackObject : Person = {name: "jack", age: 40}; let movieObject : Movie = {director: jackObject, title: "My Movie"}

Slide 41

Slide 41 text

Default Export interface Person { name: string, age: number } export default Person;

Slide 42

Slide 42 text

Default Export Option 1 interface Person { name: string, age: number } export default Person; Option 2 export default interface Person { name: string, age: number }

Slide 43

Slide 43 text

Default import import Person from "./person"; let jackObject : Person = {name: "jack", age: 40};

Slide 44

Slide 44 text

Code Generation for Modules • Compiler will develop different code depending on module target specification • Options: CommonJS, require.js, UMD, SystemJS, ES6

Slide 45

Slide 45 text

Lab

Slide 46

Slide 46 text

Angular Quick Start

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Concepts • Applications are written using HTML templates that may contain Angular markup •

{{ title }}

• 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

Slide 49

Slide 49 text

https://angular.io/guide/architect

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

HTML Template and Component import { Component } from '@angular/core'; @Component({ selector: 'custom-element', template: `

Hello {{name}}

` }) export class AppComponent { name = 'Angular'; } HTML template. Usuall in separate html file When angular finds use the template instea Component that manages the view

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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.

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

MyApp Custom element which angular will change dynamically Not a single import for javascript? tags are injected by runtime here

Slide 59

Slide 59 text

Lab

Slide 60

Slide 60 text

Several Components

Slide 61

Slide 61 text

Several Components import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `

{{title}}

` }) export class AppComponent { title : string = "Planets"; } Custom element again! This needs to be defined in other component

Slide 62

Slide 62 text

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: `
  • {{planet.name}}
` }) 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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

app/planet-list/planet-list-component.ts import { Component } from '@angular/core'; import { Planet } from './planet'; @Component({ selector: 'planet-list', template: `
  • {{planet.name}}
` }) export class PlanetListComponent { planets: Planet[] = [ {"name": "Yavin IV", "diameter": 10200, population: 1000}, {"name": "Alderaan", "diameter": 12500, population: 2000000000}]; } Let's import the interface

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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!

Slide 67

Slide 67 text

Lab

Slide 68

Slide 68 text

Displaying Data

Slide 69

Slide 69 text

import { Component } from '@angular/core'; import { Planet } from './planet'; @Component({ selector: 'planet-list', template: `
  • {{planet.name}}

3">Too many planets

` }) 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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Data Binding

Slide 72

Slide 72 text

Examples of Data Binding • One way • • One way event • Save • Save • Two way binding in Forms • All the same

Slide 73

Slide 73 text

Binding Data Between View and Component import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `

name = {{personName}}

` }) export class AppComponent { personName = 'kalle'; }

Slide 74

Slide 74 text

Result ngModel is not available by default!

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Result

Slide 77

Slide 77 text

Example import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `

bmi = {{mass / (height * height)}}

` }) export class AppComponent { } View handles the logic of calculating bmi

Slide 78

Slide 78 text

Example import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `

bmi = {{calculateBmi()}}

` }) 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

Slide 79

Slide 79 text

Event Example import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` Calculate Bmi

bmi = {{bmi}}

`, }) 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

Slide 80

Slide 80 text

Lab

Slide 81

Slide 81 text

Lifecycle Hooks

Slide 82

Slide 82 text

Component Lifecycle

Slide 83

Slide 83 text

Basic Lifecycle hooks import { Component } from '@angular/core'; import { OnInit } from '@angular/core'; import { OnDestroy } from '@angular/core'; @Component({ selector: 'life-cycle', template: `

Testing lifecycle

` }) export class LifeCycleComponent implements OnInit, OnDestroy { ngOnInit() { console.log("init") } ngOnDestroy() { console.log("destroy") } } Interfaces are voluntary here, you do not need them

Slide 84

Slide 84 text

Usage import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `Toggle ` }) export class AppComponent { isVisible : boolean = true toggle() { this.isVisible = !this.isVisible } }

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Example App Component import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `Toggle Component ` }) export class AppComponent { isVisible : boolean = true toggle() { this.isVisible = !this.isVisible } } Button will toggle the nameform component ON and OFF

Slide 88

Slide 88 text

Name Component import { Component } from '@angular/core'; import { Input } from '@angular/core'; @Component({ selector: 'nameform', template: ` {{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

Slide 89

Slide 89 text

App Loads Called once when Angular projects external content into component's view

Slide 90

Slide 90 text

Modification Called several times when view changes

Slide 91

Slide 91 text

Lab

Slide 92

Slide 92 text

Component Interaction

Slide 93

Slide 93 text

Master Component import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `
` }) export class AppComponent { } Passing Data to component

Slide 94

Slide 94 text

Child Component import { Component } from '@angular/core'; import { Input } from '@angular/core'; @Component({ selector: 'point', template: `
  • xPos = {{xPos}}, yPos = {{yPos}}
  • ` }) export class PointComponent { @Input() xPos: number @Input() yPos: number } Injecting the values given

    Slide 95

    Slide 95 text

    Iterating import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `
    ` }) export class AppComponent { points : Object[] = [{"xPos": 0, "yPos": 0}, {"xPos": 2, "yPos": 2}] } Iterating!

    Slide 96

    Slide 96 text

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

    Slide 97

    Slide 97 text

    Passing Variables import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `` }) export class AppComponent { } Passing Data to component without variable

    Slide 98

    Slide 98 text

    Parent: Parent listening to Children import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `
    ` }) 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

    Slide 99

    Slide 99 text

    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: `
  • xPos = {{xPos}}, yPos = {{yPos}} Delete
  • ` }) export class PointComponent { @Input() xPos: number @Input() yPos: number @Output() onDeleteClicked = new EventEmitter(); delete() { this.onDeleteClicked.emit('delete clicked') } } When delete button is pressed, send the event The parent listenes to this Triggering the event

    Slide 100

    Slide 100 text

    CSS

    Slide 101

    Slide 101 text

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

    Hello

    ', styles: ['h1 { color: RGB(255,0,0) }'] }) export class AppComponent { title = 'app'; }

    Slide 102

    Slide 102 text

    Attribute Directives

    Slide 103

    Slide 103 text

    Overview • Attribute directives: used in attributes in elements •

    ...

    • Structural directives: change the structure of the view • ngFor, ngIf ...

    Slide 104

    Slide 104 text

    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)

    Slide 105

    Slide 105 text

    Module import { DosomethingDirective } from './dosomething.directive'; @NgModule({ declarations: [ AppComponent, ChildComponent, DosomethingDirective ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }

    Slide 106

    Slide 106 text

    Directive import { Directive } from '@angular/core'; @Directive({ selector: '[appDosomething]' }) export class DosomethingDirective { constructor() { } } Defining selector (attribute name) Does not do anything yet.

    Slide 107

    Slide 107 text

    Usage import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: '

    Hello

    ', }) export class AppComponent { title = 'app'; } Using the directive

    Slide 108

    Slide 108 text

    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

    Slide 109

    Slide 109 text

    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

    Slide 110

    Slide 110 text

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

    Slide 111

    Slide 111 text

    Input import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: '

    Hello

    ' }) export class AppComponent { title = 'app'; someColor = 'red'; } You can give inpu to the directive

    Slide 112

    Slide 112 text

    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

    Slide 113

    Slide 113 text

    Cleaner Syntax import { Component } from '@angular/core'; @Component({ selector: 'app-root', // template: '

    Hello

    ' template: '

    Hello

    ' }) export class AppComponent { title = 'app'; someColor = 'red'; } The directive name and the input in the same

    Slide 114

    Slide 114 text

    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?

    Slide 115

    Slide 115 text

    Using Alias import { Directive, ElementRef, HostListener, Input } from '@angular/core'; @Directive({ selector: '[appDosomething]' }) export class DosomethingDirective { @Input('appDosomething') color: string; ... Now better variable name

    Slide 116

    Slide 116 text

    Lab

    Slide 117

    Slide 117 text

    Structural Directives

    Slide 118

    Slide 118 text

    Structural Directives • Structural directives are responsible for HTML layout • Reshape DOM • Apply the directive to host element, just like in attributes •
    ...
    • The asterisk * precedes the structural directive as a convenience notation • Three common built-in structural directives • ngIf, ngFor, ngSwitch

    Slide 119

    Slide 119 text

    ngSwitch import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `

    January

    February

    Some other month

    `}) export class AppComponent { day = 1; } Will display "January"

    Slide 120

    Slide 120 text

    *ngIf import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `
    Is this displayed?
    ` }) export class AppComponent { display = true; } The *ngIf is translated to something else!

    Slide 121

    Slide 121 text

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

    Slide 122

    Slide 122 text

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

    Slide 123

    Slide 123 text

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

    Slide 124

    Slide 124 text

    *ngFor: predefined variable odd import { Component } from '@angular/core'; interface Person { name: string; } @Component({ selector: 'app-root', template: `
    {{i}} {{o}} {{person.name}}
    ` }) export class AppComponent { persons: Person[] = [{name: 'jack'}, {name: 'tina'}, {name: 'paul'}, {name: 'sara'}]; } true, false, true, false ...

    Slide 125

    Slide 125 text

    *ngFor uses the template import { Component } from '@angular/core'; interface Person { name: string; } @Component({ selector: 'app-root', template: `
    {{i}} {{person.name}}
    ` }) 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

    Slide 126

    Slide 126 text

    Animations

    Slide 127

    Slide 127 text

    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

    Slide 128

    Slide 128 text

    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

    Slide 129

    Slide 129 text

    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

    Slide 130

    Slide 130 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 131

    Slide 131 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 132

    Slide 132 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 133

    Slide 133 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

    ', 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"..

    Slide 134

    Slide 134 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 135

    Slide 135 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 136

    Slide 136 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 137

    Slide 137 text

    import { Component } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 138

    Slide 138 text

    @Component({ selector: 'app-lightbulb', template: '

    {{state}}

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

    Slide 139

    Slide 139 text

    Using wildcards * • Match any state using * • on => * • * => off • * => *

    Slide 140

    Slide 140 text

    To define enter and leave: use void state • When element enters the view use • void => * • When element leaves the view use • * => void

    Slide 141

    Slide 141 text

    @Component({ selector: 'app-lightbulb', template: '

    {{state}}

    ', 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...

    Slide 142

    Slide 142 text

    @Component({ selector: 'app-lightbulb', template: '

    {{state}}

    ', styles: ['p { display: inline-block; }'], animations: [ trigger('lightBulbState', [ transition(':enter', [...]), transition(':leave', [...]), ]) ] }) You can also use a cleaner syntax here :enter == void => * :leave == * => void

    Slide 143

    Slide 143 text

    @Component({ selector: 'app-lightbulb', template: `Toggle

    Lightbulb!

    `, 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)

    Slide 144

    Slide 144 text

    End Result

    Slide 145

    Slide 145 text

    Routing

    Slide 146

    Slide 146 text

    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)

    Slide 147

    Slide 147 text

    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

    Slide 148

    Slide 148 text

    app.component.html

    Welcome to {{title}}!

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

    Slide 149

    Slide 149 text

    @Component({ selector: 'app-root', template: `

    Welcome to {{title}}!

    Add View
    `, styles: ['.thisischosen { background-color: lightgray; }'] }) export class AppComponent { css class

    Slide 150

    Slide 150 text

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

    Slide 151

    Slide 151 text

    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

    Slide 152

    Slide 152 text

    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

    Slide 153

    Slide 153 text

    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

    Slide 154

    Slide 154 text

    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

    Slide 155

    Slide 155 text

    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

    Slide 156

    Slide 156 text

    Lab

    Slide 157

    Slide 157 text

    More Modules

    Slide 158

    Slide 158 text

    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

    Slide 159

    Slide 159 text

    http://localhost:4200/view

    Slide 160

    Slide 160 text

    http://localhost:4200/view/3

    Slide 161

    Slide 161 text

    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

    Slide 162

    Slide 162 text

    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

    Slide 163

    Slide 163 text

    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!

    Slide 164

    Slide 164 text

    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

    Slide 165

    Slide 165 text

    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: `` }) export class ViewComponent implements OnInit { posts: Post[] = []; constructor(private http: HttpClient) { } ngOnInit() { this.http.get('https://jsonplaceholder.typicode.com/posts').subscribe(jsonObject => { this.posts = jsonObject; }); } } Displays a list of posts. When clicking, change the url

    Slide 166

    Slide 166 text

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

    {{post.title}}

    {{post.body}}

    ` }) 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('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } }

    Slide 167

    Slide 167 text

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

    {{post.title}}

    {{post.body}}

    ` }) 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('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } } Getting the id value from url

    Slide 168

    Slide 168 text

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

    {{post.title}}

    {{post.body}}

    Back` }) 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('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } } Navigating

    Slide 169

    Slide 169 text

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

    {{post.title}}

    {{post.body}}

    Back` }) 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('https://jsonplaceholder.typicode.com/posts/' + id).subscribe(jsonObject => { this.post = jsonObject; }); }); } } Giving the id back: http://localhost:4200/view;id=1

    Slide 170

    Slide 170 text

    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: ``, 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('https://jsonplaceholder.typicode.com/posts').subscribe(jsonObject => { this.posts = jsonObject; }); } } Get the value

    Slide 171

    Slide 171 text

    Child Routing

    Slide 172

    Slide 172 text

    Example modify/ and view/ modify/add and modify/delete

    Slide 173

    Slide 173 text

    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

    Slide 174

    Slide 174 text

    App- Component (Main) import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `

    Welcome to {{title}}!

    Modify View
    `, styles: ['.thisischosen { background-color: lightgray; }'] }) export class AppComponent { title = 'app'; } if modify/ and view/ then change the components

    Slide 175

    Slide 175 text

    Modify - Component import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-modify', template: `

    Modify

    Add | Delete `, styles: ['.selected { background-color: lightgray; }'] }) export class ModifyComponent implements OnInit { constructor() { } ngOnInit() { } } Another ! If modify/add or modify/delete Notice the difference between /add and add

    Slide 176

    Slide 176 text

    Route Guards

    Slide 177

    Slide 177 text

    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

    Slide 178

    Slide 178 text

    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

    Slide 179

    Slide 179 text

    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

    Slide 180

    Slide 180 text

    Pipes

    Slide 181

    Slide 181 text

    Pipes • To display value transformations use pipes • Built-in pipes • currency, date, percent, uppercase, lowercase ... • Usage • {{ something | pipe }} • For example • {{ title | uppercase }}

    Slide 182

    Slide 182 text

    Pipe usage: uppercase import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `

    {{title | uppercase}}

    ` }) export class AppComponent { title : string = 'Planets' } PLANETS

    Slide 183

    Slide 183 text

    Pipe usage import { Component } from '@angular/core'; @Component({ selector: 'my-selector', template: `

    {{title}}

    Today is {{ todayDate | date:'dd.mm.yyyy'}} ` }) export class AppComponent { title : string = 'Planets' todayDate : Date = new Date() } Displays date in the following format

    Slide 184

    Slide 184 text

    Custom Pipe import { Component } from '@angular/core'; import { Planet } from './planet'; @Component({ selector: 'planet-list', template: `
    • {{planet.name}}
    ` }) export class PlanetListComponent { planets: Planet[] = [ {"name": "Yavin IV", "diameter": 10200, population: 1000}, {"name": "Alderaan", "diameter": 12500, population: 2000000000}]; } Display only planets that contain 'Alderaan'

    Slide 185

    Slide 185 text

    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

    Slide 186

    Slide 186 text

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

    Slide 187

    Slide 187 text

    Usage import { Component } from '@angular/core'; import { Planet } from './planet'; @Component({ selector: 'planet-list', template: `
    • {{planet.name}}
    ` }) 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

    Slide 188

    Slide 188 text

    Services

    Slide 189

    Slide 189 text

    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.

    Slide 190

    Slide 190 text

    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

    Slide 191

    Slide 191 text

    Component: planet-list.component.ts import { Component } from '@angular/core'; import { Planet } from '../planet'; import { PlanetService } from '../planet.service'; @Component({ selector: 'planet-list', template: `
    • {{planet.name}}
    `, 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.

    Slide 192

    Slide 192 text

    Component: planet-list.component.ts import { Component } from '@angular/core'; import { Planet } from '../planet'; import { PlanetService } from '../planet.service'; @Component({ selector: 'planet-list', template: `
    • {{planet.name}}
    `, providers: [PlanetService] }) export class PlanetListComponent { planets: Planet[] = [] constructor(planetService : PlanetService) { this.planets = planetService.fetch(); } } We can remove this and add it to module

    Slide 193

    Slide 193 text

    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

    Slide 194

    Slide 194 text

    Lab

    Slide 195

    Slide 195 text

    HttpClient Consuming Real Data

    Slide 196

    Slide 196 text

    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

    Slide 197

    Slide 197 text

    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

    Slide 198

    Slide 198 text

    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: `
    • {{planet.name}}
    ` }) 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

    Slide 199

    Slide 199 text

    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

    Slide 200

    Slide 200 text

    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", ...

    Slide 201

    Slide 201 text

    import { ItemResponse } from './itemresponse'; ngOnInit(): void { this.http.get('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

    Slide 202

    Slide 202 text

    ItemResponse Interface import { Planet } from './planet' export interface ItemResponse { count: number; next: string; previous: string; results: [Planet] } Planet - array

    Slide 203

    Slide 203 text

    Planet Interface export interface Planet { name: string; diameter: number; population: number; }

    Slide 204

    Slide 204 text

    Getting Headers ngOnInit(): void { this.http.get('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

    Slide 205

    Slide 205 text

    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(url, body).subscribe((result) => {})

    Slide 206

    Slide 206 text

    No content

    Slide 207

    Slide 207 text

    import { Component } from '@angular/core'; import { HttpClient } from '@angular/common/http' interface LocationResponse { id: number, latitude: number, longitude: number } @Component({ selector: 'add-location', template: ` Send

    {{msg}}

    ` }) 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(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

    Slide 208

    Slide 208 text

    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

    Slide 209

    Slide 209 text

    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(this.url, body, {observe: 'response'} ) .subscribe(this.success, this.error) } error(err: HttpErrorResponse) { console.log('error' + err.error.message) } success(response : HttpResponse) { let locationObject : LocationResponse = response.body console.log(locationObject.id) console.log(locationObject.latitude) console.log(locationObject.longitude) } } Calls functions

    Slide 210

    Slide 210 text

    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(this.url, body, {observe: 'response'} ) .subscribe(this.success, this.error) } error(err: HttpErrorResponse) { this.msg = "Some problem with saving data." } success(response : HttpResponse) { 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!

    Slide 211

    Slide 211 text

    Working Example sendToServer() { let body = {"latitude": this.latitude, "longitude": this.longitude} this.http.post(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...

    Slide 212

    Slide 212 text

    Under the Engine... sendToServer() { let body = {"latitude": this.latitude, "longitude": this.longitude} let _this = this this.http.post(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

    Slide 213

    Slide 213 text

    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(this.url, body, {observe: 'response'} ) .subscribe(this.success, this.error) } error(err: HttpErrorResponse) { this.msg = "Some problem with saving data." } success(response : HttpResponse) { let locationObject : LocationResponse = response.body this.msg = `Saved with an id of ${locationObject.id}` } } this does NOT refer to AddLocationComponent object anymore

    Slide 214

    Slide 214 text

    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(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) { let locationObject : LocationResponse = response.body this.msg = `Saved with an id of ${locationObject.id}` } } Function binding to the rescue!

    Slide 215

    Slide 215 text

    Angular Material Components

    Slide 216

    Slide 216 text

    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

    Slide 217

    Slide 217 text

    Angular Material • Material Design Components for Angular • https://material.angular.io • Fast and Consistent • Versatile • Optimized for Angular

    Slide 218

    Slide 218 text

    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 •

    Slide 219

    Slide 219 text

    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

    Slide 220

    Slide 220 text

    Example Template

    Raised Buttons

    Basic Primary Accent Warn Disabled

    Slide 221

    Slide 221 text

    Angular Forms in Detail

    Slide 222

    Slide 222 text

    User Input and $event • To bind to a DOM event • Click me! • Using $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

    Slide 223

    Slide 223 text

    $event import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `

    Click me

    ` }) export class AppComponent { doSomething(event: Event) { console.log(event); } } Base class for all the DOM Events Either MouseEvent or KeyboardEvent

    Slide 224

    Slide 224 text

    $event import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `

    Click me

    ` }) export class AppComponent { doSomething(event: Event) { console.log(event.target); } } Either or

    ..

    Slide 225

    Slide 225 text

    $event import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `

    Click me

    ` }) export class AppComponent { doSomething(event: Event) { console.log(event.target.value); } } Angular will warn about this. The value is not present in event.target

    Slide 226

    Slide 226 text

    import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `

    Click me

    ` }) 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

    Slide 227

    Slide 227 text

    import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: `` }) 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..

    Slide 228

    Slide 228 text

    Template Reference Variable import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` {{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 . No need for the event function! Must have something in here

    Slide 229

    Slide 229 text

    keyup.enter import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` {{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

    Slide 230

    Slide 230 text

    key.enter and Template Ref Variable import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` {{value}}` }) export class AppComponent { value = ''; enterIsPressed(value: string) { this.value = value; } } Send a string A lot simpler code now

    Slide 231

    Slide 231 text

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

    Slide 232

    Slide 232 text

    Forms Template Driven and Reactive Forms

    Slide 233

    Slide 233 text

    Template Driven Forms

    Slide 234

    Slide 234 text

    import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` Person Information
    Name:
    E-mail:
    Save
    `, styles: [''] }) export class AppComponent { }

    Slide 235

    Slide 235 text

    interface Person { name?: string; email: string; } @Component({ selector: 'app-root', template: ` Person Information
    Name:
    E-mail:
    SaveFill
    User input: {{debug}}
    ` }) 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

    Slide 236

    Slide 236 text

    omponent({ selector: 'app-root', template: ` Person Information
    Name:
    {{nameElement.className}}
    E-mail: {{emailElement.className}}
    SaveFill
    User input: {{debug}}
    form>` Variable the refers to the current -element Display styles (classes) of the input

    Slide 237

    Slide 237 text

    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

    Slide 238

    Slide 238 text

    Result Allowing empty input This was required

    Slide 239

    Slide 239 text

    omponent({ selector: 'app-root', template: ` Person Information
    Name:
    E-mail: E-mail is required
    SaveFill
    User input: {{debug}}
    form>` Will toggle css hide or show.

    Slide 240

    Slide 240 text

    omponent({ selector: 'app-root', template: ` Person Information
    Name:
    E-mail: E-mail is required
    SaveFill
    User input: {{debug}}
    form>`, yles: ['.ng-valid { border-left: 20px solid green; }', '.ng-invalid { border-left: 20px solid red; }'] Styling

    Slide 241

    Slide 241 text

    Result

    Slide 242

    Slide 242 text

    omponent({ selector: 'app-root', template: ` Person Information
    Name:
    E-mail: E-mail is required
    SaveFill
    User input: {{debug}}
    form>`, yles: ['.ng-valid[required] { border-left: 20px solid green; }', '.ng-invalid:not(form) { border-left: 20px solid red; }'] Styling

    Slide 243

    Slide 243 text

    Result

    Slide 244

    Slide 244 text

    Result: when writing the email

    Slide 245

    Slide 245 text

    Result Error message is displayed when opening the app

    Slide 246

    Slide 246 text

    Modification Person Information
    Name:
    E-mail:
    E-mail is required
    {{emailElement.className}}
    SaveFill
    User input: {{debug}}
    Now error message is hidden form is loaded for the first tim

    Slide 247

    Slide 247 text

    From Documentation

    Slide 248

    Slide 248 text

    Modification Person Information
    Name:
    E-mail:
    E-mail is required
    {{emailElement.className}}
    SaveFill
    User input: {{debug}}
    Now emailElement type is NgModel class (directive) And it has several properties

    Slide 249

    Slide 249 text

    Form Validation Person Information
    Name:
    E-mail:
    E-mail is required
    {{emailElement.className}}
    Save
    User input: {{debug}}
    {{formElement.className}}
    Will have ng-valid if all of the form items are valid

    Slide 250

    Slide 250 text

    NgForm

    Slide 251

    Slide 251 text

    NgForm and Form Validation Person Information
    Name:
    E-mail:
    E-mail is required
    {{emailElement.className}}
    Save
    User input: {{debug}}
    `, Will disable and enable the button if whole form is valid or not

    Slide 252

    Slide 252 text

    Validation

    Slide 253

    Slide 253 text

    Template Driven Forms and Validation • The valid of input control is determined by using standard HTML5 • Is valid if text given • • Is valid if text follows the regex • • Is valid if given 6 chars • • See • https://developer.mozilla.org/en- US/docs/Learn/HTML/Forms/Form_validation

    Slide 254

    Slide 254 text

    Reactive Forms

    Slide 255

    Slide 255 text

    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

    Slide 256

    Slide 256 text

    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

    Slide 257

    Slide 257 text

    FormControl import { Component } from '@angular/core'; import { FormControl } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: `

    Person Detail

    Person Form

    Name: ` }) export class AppComponent { name = new FormControl(); persons: Person[] = [{name: 'jack', age: 30}, {name: 'tina', age: 20}]; } Link the input to the formcontrol

    Slide 258

    Slide 258 text

    FormGroup import { Component } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: `

    Person Detail

    Person Form

    Name: Age: ` }) 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

    Slide 259

    Slide 259 text

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

    Person Detail

    Person Form

    Name: Age:

    Form value: {{ personForm.value | json }}

    ` }) 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

    Slide 260

    Slide 260 text

    FormBuilder import { Component } from '@angular/core'; import { FormControl, FormGroup, FormBuilder } from '@angular/forms'; interface Person { name: string; age: number; } @Component({ selector: 'app-root', template: `

    Person Detail

    Person Form

    Name: Age:

    Form value: {{ personForm.value | json }}

    ` }) 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

    Slide 261

    Slide 261 text

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

    Person Detail

    Person Form

    Name: Age:

    Form value: {{ personForm.value | json }}

    Form status: {{ personForm.status | json }}

    ` }) 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.

    Slide 262

    Slide 262 text

    Standard Validators

    Slide 263

    Slide 263 text

    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)])]}); } }

    Slide 264

    Slide 264 text

    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

    Slide 265

    Slide 265 text

    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)])]}); } }

    Slide 266

    Slide 266 text

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

    Slide 267

    Slide 267 text

    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)])]}); } }

    Slide 268

    Slide 268 text

    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.

    Slide 269

    Slide 269 text

    @Component({ selector: 'app-root', template: `

    Person Detail

    Person Form

    Name: Age:

    name = {{personForm.get('name').value}}

    name status = {{personForm.get('name').status}}

    name pristine = {{personForm.get('name').pristine}}

    name dirty = {{personForm.get('name').dirty}}

    name untouched = {{personForm.get('name').untouched}}

    name touched = {{personForm.get('name').touched}}

    ` })

    Slide 270

    Slide 270 text

    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

    Slide 271

    Slide 271 text

    Dynamic Forms

    Slide 272

    Slide 272 text

    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

    Slide 273

    Slide 273 text

    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: `
    Save ` }) 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

    Slide 274

    Slide 274 text

    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: `
    Save ` }) 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

    Slide 275

    Slide 275 text

    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;

    Slide 276

    Slide 276 text

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

    Slide 277

    Slide 277 text

    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']; } }

    Slide 278

    Slide 278 text

    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

    Slide 279

    Slide 279 text

    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: `
    {{question.label}} {{question.key}}
    ` }) 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