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 2 -> • 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 • tsc -version • 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 01 + 02: Install and Hello World

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] = ['h', 5, true] 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

Lab 03: Basic Types

Slide 23

Slide 23 text

Functions

Slide 24

Slide 24 text

Function type function sum(a: number, b: number) : number { return a + b } let f : ((a : number, b : number) => number) = sum

Slide 25

Slide 25 text

Function type function sum(a: number, b: number, f: ((result : number) => void)) : void { f(a + b) } sum(5,5, result => console.log(result))

Slide 26

Slide 26 text

JavaScript function buildName(firstName, lastName) { if (lastName) return firstName + " " + lastName; else return firstName; } let result1 = buildName("Bob"); let result2 = buildName("Bob", "Adams", "Sr."); let result3 = buildName("Bob", "Adams") console.log(result1) console.log(result2) console.log(result3) Two arguments You can pass several...

Slide 27

Slide 27 text

TypeScript function buildName(firstName : string, lastName : string) : string { if (lastName) return firstName + " " + lastName; else return firstName; } let result1 = buildName("Bob"); let result2 = buildName("Bob", "Adams", "Sr."); let result3 = buildName("Bob", "Adams") console.log(result1) console.log(result2) console.log(result3) We will now have problems...

Slide 28

Slide 28 text

TypeScript function buildName(firstName : string, lastName? : string) : string { if (lastName) return firstName + " " + lastName; else return firstName; } let result1 = buildName("Bob"); // let result2 = buildName("Bob", "Adams", "Sr."); let result3 = buildName("Bob", "Adams") console.log(result1) // console.log(result2) console.log(result3) You can now use 1 – 2 arguments! Will be undefined..

Slide 29

Slide 29 text

Default parameter function buildName(firstName: string, lastName = "Smith") { // ... }

Slide 30

Slide 30 text

Rest arguments function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" "); } // employeeName will be "Joseph Samuel Lucas MacKinzie" let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

Slide 31

Slide 31 text

Lab 04: Functions

Slide 32

Slide 32 text

Keyword this

Slide 33

Slide 33 text

Outputting global object function doIt() { console.log(this); } doIt();

Slide 34

Slide 34 text

Restrict that this must be defined tsc --noImplicitThis index.ts The type of this must be defined!

Slide 35

Slide 35 text

Defining type function doIt(this: type) { console.log(this); } doIt();

Slide 36

Slide 36 text

Example interface Person { name: string; hello: () => void; } let jack: Person = { name: "jack", hello: function (): void { console.log(this.name); }, }; jack.hello(); let f = jack.hello; f(); We will get undefined

Slide 37

Slide 37 text

Example interface Person { name: string; hello: (this: Person) => void; } let jack: Person = { name: "jack", hello: function (this: Person): void { console.log(this.name); }, }; jack.hello(); let f = jack.hello; f(); this must be typeof Person, otherwise fail Won't compile..

Slide 38

Slide 38 text

Lab 06 + 07 + 08 + 09: this keyword

Slide 39

Slide 39 text

Interfaces and Classes

Slide 40

Slide 40 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 41

Slide 41 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 42

Slide 42 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 43

Slide 43 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 44

Slide 44 text

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

Slide 45

Slide 45 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" console.log(tina.name) Invoking the set method

Slide 46

Slide 46 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 47

Slide 47 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 48

Slide 48 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 49

Slide 49 text

Lab 10 + 11

Slide 50

Slide 50 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 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 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 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Lab 12: Modules

Slide 63

Slide 63 text

Angular Quick Start

Slide 64

Slide 64 text

Quickstart • Angular CLI - command line app • To install globally • npm install -g @angular/cli • ng --version • Create skeleton project • ng new my-app --strict • Run your app • ng serve –o Enables strict mode, additional typescript / js checking

Slide 65

Slide 65 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 66

Slide 66 text

https://angular.io/guide/architecture

Slide 67

Slide 67 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 68

Slide 68 text

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

Hello {{name}}

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

Slide 69

Slide 69 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 70

Slide 70 text

Module Files app.component.ts the component app.component.html the view app.component.css the styling app.component.spec.ts unit testing app.module.ts boxing the components into module

Slide 71

Slide 71 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 72

Slide 72 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 73

Slide 73 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 74

Slide 74 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 75

Slide 75 text

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

Slide 76

Slide 76 text

Lab

Slide 77

Slide 77 text

Several Components

Slide 78

Slide 78 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 79

Slide 79 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 directory for each component Iterate all the planets

Slide 80

Slide 80 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 81

Slide 81 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 82

Slide 82 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 83

Slide 83 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 84

Slide 84 text

Lab

Slide 85

Slide 85 text

Displaying Data

Slide 86

Slide 86 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 87

Slide 87 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 88

Slide 88 text

Data Binding

Slide 89

Slide 89 text

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

Slide 90

Slide 90 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 91

Slide 91 text

Result ngModel is not available by default!

Slide 92

Slide 92 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 93

Slide 93 text

Result

Slide 94

Slide 94 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 95

Slide 95 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 96

Slide 96 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 97

Slide 97 text

Lab

Slide 98

Slide 98 text

Component Interaction

Slide 99

Slide 99 text

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

Slide 100

Slide 100 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 101

    Slide 101 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 102

    Slide 102 text

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

    Slide 103

    Slide 103 text

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

    Slide 104

    Slide 104 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 that the Parent catches. The onDeleteClicked is something that must be implemented in child

    Slide 105

    Slide 105 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 106

    Slide 106 text

    Lifecycle Hooks

    Slide 107

    Slide 107 text

    Component Lifecycle • constructor • ngOnChanges • If simple input changes • ngOnInit • When component is initialized • ngDoCheck • If "complicated" input changes • ngOnDestroy • When component is destroyed

    Slide 108

    Slide 108 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 109

    Slide 109 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 110

    Slide 110 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 111

    Slide 111 text

    ngOnChanges – Example using Primitive Type @Component({ selector: 'app-parent', template: `

    Parent

    Update ` }) export class ParentComponent { name = 'tina'; updateUser(): void { this.name = 'jack'; } }

    Slide 112

    Slide 112 text

    ngOnChanges – Example using Primitive Type @Component({ selector: 'app-child', template: '

    {{user}}

    ' }) export class ChildComponent implements OnChanges { @Input() user = ''; ngOnChanges(): void { console.log('name changed'); } } Triggers event when input changes. user here is primitive type!

    Slide 113

    Slide 113 text

    ngOnChanges – Reference type import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-parent', template: `

    Parent

    Update ` }) export class ParentComponent { user = { name: 'tina' }; updateUser(): void { this.user.name = 'jack'; } }

    Slide 114

    Slide 114 text

    ngOnChanges – Reference type @Component({ selector: 'app-child', template: '

    {{user.name}}

    ' }) export class ChildComponent implements OnChanges { @Input() user = { name: '' }; ngOnChanges(): void { console.log('name changed'); } } This is not called! ngOnChanges won't detect changes inside of reference type!

    Slide 115

    Slide 115 text

    ngDoCheck() • Detect and act upon changes that Angular can't or won't detect of its own.

    Slide 116

    Slide 116 text

    ngDoCheck - example @Component({ selector: 'app-child', template: '

    {{user.name}}

    ' }) export class ChildComponent implements DoCheck { @Input() user = { name: '' }; ngDoCheck(): void { console.log('name changed'); } } Detects also reference type updates

    Slide 117

    Slide 117 text

    Lab

    Slide 118

    Slide 118 text

    CSS

    Slide 119

    Slide 119 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 120

    Slide 120 text

    Attribute Directives

    Slide 121

    Slide 121 text

    Overview • Attribute directives: used in attributes in elements •

    ...

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

    Slide 122

    Slide 122 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 123

    Slide 123 text

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

    Slide 124

    Slide 124 text

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

    Slide 125

    Slide 125 text

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

    Hello

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

    Slide 126

    Slide 126 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 127

    Slide 127 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 128

    Slide 128 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 129

    Slide 129 text

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

    Hello

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

    Slide 130

    Slide 130 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 131

    Slide 131 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 132

    Slide 132 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 133

    Slide 133 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 134

    Slide 134 text

    Lab

    Slide 135

    Slide 135 text

    Structural Directives

    Slide 136

    Slide 136 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 137

    Slide 137 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 138

    Slide 138 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 139

    Slide 139 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 140

    Slide 140 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 141

    Slide 141 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 142

    Slide 142 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 143

    Slide 143 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 144

    Slide 144 text

    Animations

    Slide 145

    Slide 145 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 146

    Slide 146 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 147

    Slide 147 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 148

    Slide 148 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 149

    Slide 149 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 150

    Slide 150 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 151

    Slide 151 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 152

    Slide 152 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 153

    Slide 153 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 154

    Slide 154 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 155

    Slide 155 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 156

    Slide 156 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 157

    Slide 157 text

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

    Slide 158

    Slide 158 text

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

    Slide 159

    Slide 159 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 160

    Slide 160 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 161

    Slide 161 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 162

    Slide 162 text

    End Result

    Slide 163

    Slide 163 text

    Routing

    Slide 164

    Slide 164 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 165

    Slide 165 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 166

    Slide 166 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 167

    Slide 167 text

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

    Welcome to {{title}}!

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

    Slide 168

    Slide 168 text

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

    Slide 169

    Slide 169 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 170

    Slide 170 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/jaska the redirect to http://localhost:4200/add

    Slide 171

    Slide 171 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 172

    Slide 172 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 173

    Slide 173 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 174

    Slide 174 text

    Lab

    Slide 175

    Slide 175 text

    More Modules

    Slide 176

    Slide 176 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 177

    Slide 177 text

    http://localhost:4200/view

    Slide 178

    Slide 178 text

    http://localhost:4200/view/3

    Slide 179

    Slide 179 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 180

    Slide 180 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 181

    Slide 181 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 182

    Slide 182 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 183

    Slide 183 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 184

    Slide 184 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 185

    Slide 185 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 186

    Slide 186 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 187

    Slide 187 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 188

    Slide 188 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 189

    Slide 189 text

    Child Routing

    Slide 190

    Slide 190 text

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

    Slide 191

    Slide 191 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 192

    Slide 192 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 193

    Slide 193 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 194

    Slide 194 text

    Route Guards

    Slide 195

    Slide 195 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 196

    Slide 196 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 197

    Slide 197 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 providers to the module

    Slide 198

    Slide 198 text

    Pipes

    Slide 199

    Slide 199 text

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

    Slide 200

    Slide 200 text

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

    {{title | uppercase}}

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

    Slide 201

    Slide 201 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 202

    Slide 202 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 203

    Slide 203 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 204

    Slide 204 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 205

    Slide 205 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 206

    Slide 206 text

    Services

    Slide 207

    Slide 207 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 208

    Slide 208 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 209

    Slide 209 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 210

    Slide 210 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 211

    Slide 211 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 212

    Slide 212 text

    Lab

    Slide 213

    Slide 213 text

    HttpClient Consuming Real Data

    Slide 214

    Slide 214 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 215

    Slide 215 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 216

    Slide 216 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 217

    Slide 217 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 218

    Slide 218 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 219

    Slide 219 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 220

    Slide 220 text

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

    Slide 221

    Slide 221 text

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

    Slide 222

    Slide 222 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 223

    Slide 223 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 224

    Slide 224 text

    No content

    Slide 225

    Slide 225 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 226

    Slide 226 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 227

    Slide 227 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 228

    Slide 228 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 229

    Slide 229 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 230

    Slide 230 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 231

    Slide 231 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 232

    Slide 232 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 233

    Slide 233 text

    Angular Material Components

    Slide 234

    Slide 234 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 235

    Slide 235 text

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

    Slide 236

    Slide 236 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 237

    Slide 237 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 238

    Slide 238 text

    Example Template

    Raised Buttons

    Basic Primary Accent Warn Disabled

    Slide 239

    Slide 239 text

    Angular Forms in Detail

    Slide 240

    Slide 240 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 241

    Slide 241 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 242

    Slide 242 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 243

    Slide 243 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 244

    Slide 244 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 245

    Slide 245 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 246

    Slide 246 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 247

    Slide 247 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 248

    Slide 248 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 249

    Slide 249 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 250

    Slide 250 text

    Forms Template Driven and Reactive Forms

    Slide 251

    Slide 251 text

    Template Driven Forms

    Slide 252

    Slide 252 text

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

    Slide 253

    Slide 253 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 = 'kalle@tamk.fi'; } } 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 254

    Slide 254 text

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

    Slide 255

    Slide 255 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 256

    Slide 256 text

    Result Allowing empty input This was required

    Slide 257

    Slide 257 text

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

    Slide 258

    Slide 258 text

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

    Slide 259

    Slide 259 text

    Result

    Slide 260

    Slide 260 text

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

    Slide 261

    Slide 261 text

    Result

    Slide 262

    Slide 262 text

    Result: when writing the email

    Slide 263

    Slide 263 text

    Result Error message is displayed when opening the app

    Slide 264

    Slide 264 text

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

    Slide 265

    Slide 265 text

    From Documentation

    Slide 266

    Slide 266 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 267

    Slide 267 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 268

    Slide 268 text

    NgForm

    Slide 269

    Slide 269 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 270

    Slide 270 text

    Validation

    Slide 271

    Slide 271 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 272

    Slide 272 text

    Reactive Forms

    Slide 273

    Slide 273 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 274

    Slide 274 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 275

    Slide 275 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 276

    Slide 276 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 277

    Slide 277 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 278

    Slide 278 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 279

    Slide 279 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 280

    Slide 280 text

    Standard Validators

    Slide 281

    Slide 281 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 282

    Slide 282 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 283

    Slide 283 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 284

    Slide 284 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 285

    Slide 285 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 286

    Slide 286 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 287

    Slide 287 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 288

    Slide 288 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 289

    Slide 289 text

    Dynamic Forms

    Slide 290

    Slide 290 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 291

    Slide 291 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 292

    Slide 292 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 293

    Slide 293 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 294

    Slide 294 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 295

    Slide 295 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 296

    Slide 296 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 297

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