‏ ‏ ‎ ‏ ‏ ‎

Credits to Rainer Stropek. This lecture note are based on his Angular Course.

1. Angular CLI

chapter001 cli webpage

Windows
npm install -g @angular/cli
Linux, MacOS
sudo npm install -g @angular/cli

chapter001 cli version

Ivy is the new compiler of Angular.

Angular CLI is a command line interface.

-g → installs global

Angular CLI is a kind of meta-programming environment.

meta data → data about data meta programming → writing programs that write programs

Angular CLI is based on schematics (aka scaffolding)

The structure of the program files is given. With schematics you can change the way Angular CLI creates the files.

mkdir angular-intro
cd angular-intro
Angular CLI is just a node project
 $ where ng
/usr/local/bin/ng
Version of Angular CLI
ng --version
List of Commands
ng
Table 1. Description
Command Description

new

Creates a new workspace and initialize an Angular app. It is possible to host multiple apps in a workspace

List of Commands
ng new --help
Table 2. ng new --help
Command Description

--dry-run (-d)

Very important. Kind of simulator. Run through and reports activity without writing out results.

--skip-git (-g)

Do not initialize a git repository.

--routing

In the beginning we will not deal with it

--style

SASS → like typescript to javascript, you code in SASS and compile it down to CSS. SASS supports variables, nestings, etc

ng new my-great-app --dry-run
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
CREATE my-great-app/README.md (1028 bytes)
CREATE my-great-app/.editorconfig (274 bytes)
CREATE my-great-app/.gitignore (631 bytes)
CREATE my-great-app/angular.json (3614 bytes)
CREATE my-great-app/package.json (1255 bytes)
CREATE my-great-app/tsconfig.base.json (458 bytes)
CREATE my-great-app/tsconfig.json (426 bytes)
CREATE my-great-app/tslint.json (3184 bytes)
CREATE my-great-app/.browserslistrc (853 bytes)
CREATE my-great-app/karma.conf.js (1024 bytes)
CREATE my-great-app/tsconfig.app.json (292 bytes)
CREATE my-great-app/tsconfig.spec.json (338 bytes)
CREATE my-great-app/src/favicon.ico (948 bytes)
CREATE my-great-app/src/index.html (296 bytes)
CREATE my-great-app/src/main.ts (372 bytes)
CREATE my-great-app/src/polyfills.ts (2835 bytes)
CREATE my-great-app/src/styles.css (80 bytes)
CREATE my-great-app/src/test.ts (753 bytes)
CREATE my-great-app/src/assets/.gitkeep (0 bytes)
CREATE my-great-app/src/environments/environment.prod.ts (51 bytes)
CREATE my-great-app/src/environments/environment.ts (662 bytes)
CREATE my-great-app/src/app/app.module.ts (314 bytes)
CREATE my-great-app/src/app/app.component.css (0 bytes)
CREATE my-great-app/src/app/app.component.html (25725 bytes)
CREATE my-great-app/src/app/app.component.spec.ts (960 bytes)
CREATE my-great-app/src/app/app.component.ts (216 bytes)
CREATE my-great-app/e2e/protractor.conf.js (869 bytes)
CREATE my-great-app/e2e/tsconfig.json (299 bytes)
CREATE my-great-app/e2e/src/app.e2e-spec.ts (645 bytes)
CREATE my-great-app/e2e/src/app.po.ts (301 bytes)

NOTE: The "dryRun" flag means no changes were made.
This syntax is called kebap-syntax (aka kebap-case)
ng new my-great-app
  • no routing

  • choose CSS

One of the biggest disadvantages is the amount of file Angular installs for a hello-world-app
417 MB and 37.298 files !!!

chapter001 cli size of app

Don’t check in `node_modules`in your git repo!!!!
cd my-great-app
webstorm .
  • Angular CLI generates .gitignor, package.json, tsconfig.json etc.

  • package.json-File

    • @angular/ - dependencies come from the Angular core team

    • the other dependencies are (open-source) libraries that the Angular team choosed to use. Parts of theselibraries (like rxjs) are maintained by members of the Angular core team.

    • devDependencies

      • @types/

      • Jasmine, karma, protractor are used for tests.

        • one hour programming → minimum one hour writing automated tests

        • w/o automated tests, we ship crappy software

        • every project needs some level of manual testing, but we want to reduce manual testing and increase automated tests

      • Jasmine is a framework for writing tests

      • Karma is a framework for running tests (run tests remotely; in the cloud; on 10 different smartphones)

      • Protractor is a tool for end-to-end testing. End-to-end testing means a user is simulated (the system is simulating: moving the mouse, clicking on a button, entering text in a textfield)

    • the versions are responsible for the compability of the libraries to each other.

Never change a single version. If you want to update an project → https://update.angular.io

1.1. Scripts

  • scripts:

    • start → like browser-sync

    • lint → linter checks the quality of your source code

    • build → compiles an Angular app into an output directory named dist/.

    • …​

1.1.1. start

Open a terminal in Webstorm and run the app
npm start

Stop the server with Ctrl + C

1.1.2. build

npm run build

The result appears in the dist-folder. This is called a debug-buils. It will run on any web-server. But don’t use it in a production environment at the moment. We will hear why later on.

chapter001 cli build script

1.1.3. test

npm run test

chapter001 cli script test

1.1.4. linting

npm run lint

chapter001 cli script lint

1 WebStorm will tell you immediately the problem
2 When running the lint-script you get a list of errors

1.1.5. end-to-end testing

npm run e2e

based on protractor

1.2. EditorConfig

chapter001 editorconfig

WebStorm supports EditorConfig. There is a plugin available (and bundled in the defailt configuration)

Sets the configuration for several IDE’s / editors (VSC, Atom, WebStorm, …​.)

1.3. angular.json

Setting file. Don’t touch it at the moment.

1.4. tsconfig.json

1.5. Bootstrapping Process

  • index.html

  • main.ts

1.6. styles.css

Styles can applied globally.

changes the font to serifs
* {
  font-family: "Times New Roman", Times, serif;
}

2. Configuring WebStorm

2.1. Format Space within braces for ES6 import/export

Instead of import brackets w/o spaces …​
import {Component, OnInit} from '@angular/core';
... you can configure IntelliJ WebStorm to
import { Component, OnInit } from '@angular/core';

2.1.1. Option 1: Config in preferences

enable Preferences | Editor | Code Style | TypeScript, Spaces / Within / ES6 import/export braces

900 spaces es6 import

Use the shortcut Option+Command+L to apply the changes to the code

901 format code in webstorm

2.1.2. Option 2: Config in tslint.json

906 use editorconfig

# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single
ij_javascript_spaces_within_imports = true (1)
ij_typescript_spaces_within_interpolation_expressions = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false
1 config-options for IntelliJ start with 'ij_'
to reformat the code files use

907 ctrl alt l

2.2. Turn Off IntelliJ Auto Adding to VCS/GIT

File → Settings → Version Control → Confirmation → When Files are created

905 turn off intellij auto adding to vcs git

2.3. Turn Off Warning about Missing Return Type of Function

tslint.json
...
"typedef": [
  false,
  "call-signature"
],
...

3. Compiler

3.1. For Debugging

Angular compiles your html code into javascript. You write Angular templates (which looks like html)

npm run build

Normally you have a html page and javascript is used for dynamic behaviour. Angular works different. Angular compiles all (html) down to javascript.

chapter001 syntax error

the compiler runs just in time → just-in-time-compiler

3.2. For Production

npm run build -- --prod

-- → appends "--prod" to "ng build" -→ ng build "--prod"

chapter002 compiler production

  • Now the size of the folder is smaller then before (debug-build).

  • The file names look pretty strange → a hash of the content of the file is added. For performance reasons. So the compiler knows which file changed.

  • When there is a error (ie wrong closed html element) the error is at build time (not at runtime like before)

  • This compiler is a ahead-of-time compiler (AOT Compiler)

  • Maybe the JIT compiler (for debugging) will be removed in the future

3.2.1. Run the app in the dist-folder locally

Python Web Server
open a terminal (in Webstorm)
cd dist/my-great-app
python3 -m http.server

open an browser with http://localhost:8000

Chrome Extension - Web Server for Chrome

chapter002 compiler webserver for chrome

4. Angular Ecosystem

4.1. StackBlitz

StackBlitz is not as powerful than a local installation

  • for demonstrating small projects

4.2. Angular Material

4.3. Angular Flex Layout

The real power of Flex Layout, however, is its responsive engine.

4.4. Kendo UI for Angular

Very interesting controls

4.6. Semantic UI

5. Templates and Data Binding

5.1. Interpolation

src/app/app.component.html
<h1>
  Welcome to {{ title }}
</h1>
src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-great-app';

  constructor() {
    setInterval(() => this.title += '!', 250);
  }
}

chapter004 data interpolation

→ one-way interpolation

5.2. Event-Binding

app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-great-app';

  constructor() {
  }

  public onClicked(): void { (1)
    this.title += '!!';
  }
}
1 add this function
app.component.html
<h1>
  Welcome to {{ title }}
</h1>

<button type="button" (click)="onClicked()">  (1)
  Click me!
</button>
1 add this element. Use AutoComplete

It would als possible

<h1>
  Welcome to {{ title }}
</h1>

<button type="button"
  (click)="title = title + '!'">  (1)
  Click me!
</button>
1 There is a danger of getting spaghetti code - mixing ie view and logic → never add business logic to a view

5.3. Template Expressions

Binding from the view (html) to the logic (ts)

app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-great-app';
  myNumber = 41;   (1)

  constructor() {
  }

  public onClicked(): void {
    this.title += '!!';
  }
}
app.component.html
<h1>
  Welcome to {{ title }}
</h1>

<button type="button" (click)="onClicked()">
  Click me!
</button>

<h2>{{ myNumber + 1 }}</h2> (1)
  • Calculations are possible

5.4. Property Binding

A string works
...
<input type="text" value="asdf" />
Result

chapter004 property binding 001

Bind the 'title' does not work
...
<input type="text" value="title" />
Result

chapter004 property binding 002

We have to use "Property Binding"
...
<input type="input" [value]="title" />
Result

chapter004 property binding 003

5.5. Two-Way-Binding

Combine data binding with event binding

We have to use "Property Binding"
...
<input type="input" [(ngModel)]="title" />

chapter004 two way binding 001

  • The site is broken

  • Click on "Import FormsModule"

  • Check the result in app.module.ts

The FormsModule has to be imported

chapter004 two way binding 002

  • Eventually restart the server

The two-way-binding works as expected

chapter004 two way binding 003

5.6. Structural Directive - *ngFor

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-great-app';
  myNumber = 41;
  todos = [   (1)
    'Shopping',
    'Homework',
    'Listen to music'
  ];

  constructor() {
  }

  public onClicked(): void {
    this.title += '!!';
  }
}
<h1>
  Welcome to {{ title }}
</h1>

<button type="button" (click)="onClicked()">
  Click me!
</button>

<h2>{{ myNumber + 1 }}</h2>

<input type="input" [(ngModel)]="title" />

<p>{{ todos }}</p>  (1)
not very promising

chapter004 arrays 001

app.component.html
...
<ul>
  <li *ngFor="let item of todos">
    {{item}}
  </li>
</ul>
  • The presentation of the array is much prettier now.

  • So we can also add a button

app.components.html
...
<button type="button" (click)="addItem()">
  Add item
</button>
app.components.ts
public addItem(): void {
this.todos.push('something');
}
The button is working

chapter004 arrays 002

5.7. Event with Parameter

public removeItem(item: string): void {
    this.todos.splice(this.todos.indexOf(item),1);
}
<ul>
  <li *ngFor="let item of todos">
    {{item}}
    <button type="button"
            (click)="removeItem(item)">X
    </button>
  </li>
</ul>
We don’t have to use the indexOf()-method

chapter004 ngfor documentation

5.8. Structural Directive - *ngIf

app.component.html
...
<p *ngIf="alert">
  We have a critical alert!!!
</p>

5.9. Exercise: Turmrechnen

The solutions are included. But give it a try.

6. Components and Modules

6.1. Components

In Angular is all based on components. The AppComponent is the root component which is auto-generated when creating an Angular app.

Components structure your user interface into pieces like structuring the logic in to pieces with classes.

An Angular component represents a visual part of your application. Together you can use all components to arrange them on the screen.

The Decorator for Components
@Component({   (1)
  selector: 'app-root', (2)
  templateUrl: './app.component.html',  (3)
  styleUrls: ['./app.component.css'] (4)
})
export class AppComponent {
...
}
1 component decorator (comparable to annotations in Java)
2 Selector: The name of the selector to create your own html-tags
index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MyGreatApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root> (1)
</body>
</html>
1 html-element of the root component
3 Instead of using a template html file you can also write the html code directly here
app.component.ts
@Component({
  selector: 'app-root',
  template: '<h2>xxx</h2>',  (1)
  styleUrls: ['./app.component.css']
})
export class AppComponent {
...
}
1 with template you can include html code (for small templates). It is not recommended to do so.
4 The style only applies to the current component.
app.component.css
h1 {
  background: darkorange;
}

chapter005 style

This is a local style for this component.

6.2. Module

All components are arranged in modules.

A javascript module is a file.

In larger projects your modules tend to grow. It is not a practical approach to put all in a single file.

In Angular modules represent a broader concept. Angular provides a module-system which includes several files.

Angular libraries are NgModules, such as FormsModule, HttpClientModule, and RouterModule. Many third-party libraries are available as NgModules such as Material Design, Ionic, and AngularFire2.

NgModules consolidate

  • components,

  • directives, and

  • pipes

into cohesive blocks of functionality, each focused on a feature area, application business domain, workflow, or common collection of utilities.

You can group your components ie to:

  • invoice module

  • customer management module

  • administration module

  • accounting module

  • …​

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [  (1)
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
1 This declaration section contains all the components which are part of this module.

6.2.1. Creating a component

ng generate component numeric-input --dry-run

chapter005 create module dry run

chapter005 new module folder structure

app.component.html
<h1>
  Welcome to {{ title }}
</h1>

<app-numeric-input></app-numeric-input> (1)

chapter005 new module result in browser

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { NumericInputComponent } from './numeric-input/numeric-input.component'; (1)

@NgModule({
  declarations: [
    AppComponent,
    NumericInputComponent  (1)
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
1 The NumericComponent is imported and added to the module-declaration automatically, when creating the module by Angular CLI.

It is also possible to use npm packages to distribute your modules.

6.2.2. Programming a component

numeric-input.component.html
<input type="number"><button>Reset</button>
app.component.html
<h1>
  Welcome to {{ title }}
</h1>

<app-numeric-input></app-numeric-input>

chapter005 module 001

app.component.html
<h1>
Welcome to {{ title }}
</h1>

<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>

chapter005 module 002

6.2.3. Input Parameter

Communication between child- and parent-component

@Input-Decorator for passing a parameter

chapter005 module 003

numeric-input.component.ts
import {Component, Input, OnInit} from '@angular/core';  (1)

@Component({
  selector: 'app-numeric-input',
  templateUrl: './numeric-input.component.html',
  styleUrls: ['./numeric-input.component.css']
})
export class NumericInputComponent implements OnInit {

  @Input() public value = 0; (2)

  constructor() { }

  ngOnInit(): void { (3)
  }
}
1 Input was added to the imports
2 @Input()
3 btw: The ngOnInit() is a lifecycle hook. Angular calls ngOnInit() shortly after creating a component. It’s a good place to put initialization logic.
app.component.html
<h1>
  Welcome to {{ title }}
</h1>

<app-numeric-input [value]="42"></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
numeric-input.component.html
<input type="number" [value]="value">
<button>Reset</button>

chapter005 module 004

<input type="number" [value]="value">
<button>Reset</button>

6.2.4. Output Event

  • @Output()

  • not so easy

  • what should be the outpur?

  • a signal telling the parent that something happened (ie clicked)

  • In Angular exists the class EventEmitter

chapter005 module 005

numeric-input.component.ts
import {Component, Input, OnInit, Output, EventEmitter} from '@angular/core'; (1)

@Component({
  selector: 'app-numeric-input',
  templateUrl: './numeric-input.component.html',
  styleUrls: ['./numeric-input.component.css']
})
export class NumericInputComponent implements OnInit {

  @Input() public value = 0;
  @Output() public resetClicked = new EventEmitter<void>(); (2)

  constructor() { }

  ngOnInit(): void {
  }

  public onResetClicked() {  (3)
    this.resetClicked.emit();
  }

}
1 a
2 b
3 we delegate it to the parent. The child informs the parent, that the click event happened.
In the parent component now the resetClicked-event is available

chapter005 module 006

numeric-input.component.html
<input type="number" [value]="value">
<button (click)="onResetClicked()">Reset</button>

chapter005 module 007

1 Create method with <Alt>-Enter …​
2 …​ or wait until the popup appears
app.component.ts
...
  onReset() {
    console.log('Reset clicked!');
  }
...

chapter005 module 008

app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-great-app';

  myFirstValue = 42;   (1)

  constructor() {
  }

  onReset() {
    this.myFirstValue = 0;  (2)
  }
}
app.component.html
<h1>
  Welcome to {{ title }}
</h1>

<app-numeric-input
     [value]="myFirstValue"     (1)
     (resetClicked)="onReset()">
</app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>
<app-numeric-input></app-numeric-input><br/>

Now the number is set to 0 after clicking Reset

So you can build generic usable components
  • Repeat less code!

  • Reuse more code!

6.3. Exercise: Einheitenumrechner

6.3.1. Simple Design of the Application

unitconvertgui
<table>
  <td><app-unit-ps-kw
       [value]="30"
       [unit]="'PS'"
  >

6.3.2. Set up the Project

Create new project (dry run)
ng new unit-converter --dry-run
Create new project
ng new unit-converter
Create a new component "input-ps-kw"

chapter005 001

Alternatively you can use;

ng generate component input-ps-kw
The new component is automaticallly added to app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { InputPsKwComponent } from './input-ps-kw/input-ps-kw.component';  (1)

@NgModule({
  declarations: [
    AppComponent,
    InputPsKwComponent   (2)
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Start the app

chapter005 002

Alternatively you can use npm start or ng serve

chapter005 003

6.3.3. Start Coding

input-ps-kw.component.html
<input type="number">
<select>
  <option>PS</option>
  <option>kW</option>
</select>
app.component.html
<table>
  <thead>
  <tr>
    <td>Car Model</td>
    <td>Power</td>
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>VW Käfer 1936</td>
    <td>
      22PS
      <app-input-ps-kw></app-input-ps-kw>  (1)
    </td>
  </tr>
  </tbody>
</table>
1 The parent component recognises already the new component

chapter005 004

Make sure, that you import the FormsModule

chapter005 005

input-ps-kw.component.html
<input type="number" [(ngModel)]="value"> (1)
<select>
  <option>PS</option>
  <option>KW</option>
</select>

chapter005 006

input-ps-kw.component.html
<input type="number" [(ngModel)]="value">
<select [(ngModel)]="uom">  (1)
  <option>PS</option>
  <option>kW</option>
</select>
input-ps-kw.component.ts
...
export class InputPsKwComponent implements OnInit {
  value = 30;
  uom = "PS"; // unit of measures
...

Now change the uom to "KW" and check if it’s working.

6.3.4. Add Communication to the Parent Component

value
  1. Add the @Input-Decorator to the field (value).

  2. Check, if input is imported.

  3. Add the parameter in the parent component to the elements.

input-ps-kw.component.ts
import {Component, Input, OnInit} from '@angular/core'; (1)

@Component({
  selector: 'app-input-ps-kw',
  templateUrl: './input-ps-kw.component.html',
  styleUrls: ['./input-ps-kw.component.css']
})
export class InputPsKwComponent implements OnInit {
  @Input() value = 30;  (2)
  uom = "KW"; // unit of measures

  constructor() { }

  ngOnInit(): void {
  }

}
app.component.html
<table>
  <thead>
  <tr>
    <td>Car Model</td>
    <td>Power</td>
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>VW Käfer 1936</td>
    <td>
      22PS
      <app-input-ps-kw value="22"></app-input-ps-kw> (1)
    </td>
  </tr>
  <tr>
    <td>Ferrari LaFerrari</td>
    <td>
      588KW
      <app-input-ps-kw value="588"></app-input-ps-kw> (2)
    </td>
  </tr>
  </tbody>
</table>

chapter005 007

unit-of-measure
...
export class InputPsKwComponent implements OnInit {
  @Input() value = 30;
  @Input() uom = "KW"; // unit of measures (1)

  constructor() { }

  ngOnInit(): void {
  }
...
...
  <tr>
    <td>VW Käfer 1936</td>
    <td>
      <app-input-ps-kw value="22" [uom]="'PS'"></app-input-ps-kw> (1)
    </td>
  </tr>
  <tr>
    <td>Ferrari LaFerrari</td>
    <td>
      <app-input-ps-kw value="588" uom="KW"></app-input-ps-kw> (2)
    </td>
  </tr>
...
1 because here the square brackets are used (one-way-data-binding), "PS" must surrounded additionally be single quotes. Otherwise PS would be interpreted as variable name.
2 without the square brackets, "KW" is fine w/o single quotes
When using one-way-data-binding, single quotes are necessary when using strings (→ uom) but not when using numbers (→ value)
After removing the fixed values "22PS" and "588KW"

chapter005 008

app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  kaeferPower = 22;  (1)
  kaeferUom = 'PS';  (2)
}
app.component.html
...
  <tr>
    <td>VW Käfer 1936</td>
    <td>
      <app-input-ps-kw
         [value]="kaeferPower"  (1)
         [uom]="kaeferUom"      (2)
      ></app-input-ps-kw>
    </td>
  </tr>
  <tr>
    <td>Ferrari LaFerrari</td>
    <td>
      <app-input-ps-kw value="588" uom="KW"></app-input-ps-kw>
    </td>
  </tr>
  </tbody>
</table>

<h1>Our VW K&auml;fer has {{ kaeferPower }} {{ kaeferUom }}</h1> (3)

chapter005 009

But changes are not reflected (because of one-way-data-binding)

chapter005 010

input-ps-kw.component.ts
...
export class InputPsKwComponent implements OnInit {
  @Input() value = 30;
  @Output() valueChange = new EventEmitter();

  @Input() uom = "KW"; // unit of measures
  @Output() uomChange = new EventEmitter();
...
The name of the output is the same as the name of the input + the suffix 'Change' → naming convention
input-ps-kw.component.html
<input type="number" [(ngModel)]="value" (change)="onPowerChanged()">
<select [(ngModel)]="uom" (change)="onUomChanged()">
  <option>PS</option>
  <option>KW</option>
</select>
input-ps-kw.component.js
...
  onPowerChanged() {
    this.valueChange.emit();
  }

  onUomChanged() {
    this.uomChange.emit();
  }
}
app.component.html
...
  <tr>
    <td>VW K&auml;fer 1936</td>
    <td>
      <app-input-ps-kw
        [(value)]="kaeferPower"
        [(uom)]="kaeferUom"
      ></app-input-ps-kw>
    </td>
  </tr>
...
  • Unfortunately this doesn’t work

  • we have to send also the value in the event

input-ps-kw.component.js
...
export class InputPsKwComponent implements OnInit {
  @Input() value = 30;
  @Output() valueChange = new EventEmitter<number>(); (1)

  @Input() uom = "KW"; // unit of measures
  @Output() uomChange = new EventEmitter<string>(); (2)

  constructor() { }

  ngOnInit(): void {
  }

  onPowerChanged() {
    this.valueChange.emit(this.value); (3)
  }

  onUomChanged() {
    this.uomChange.emit(this.uom); (4)
  }
}
This is a often used pattern.

6.3.5. Exercise

Create a single file component
ng generate component --help
terminal output
  --flat
    When true, creates the new files at the top level of the current project.
  ...
  --inline-style (-s)
    When true, includes styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file.
  --inline-template (-t)
    When true, includes template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file.
long version
ng generate component hello --inline-style --inline-template --flat --dry-run
short version
ng g c hello -s -t --flat -d
terminal output
CREATE src/app/hello.component.spec.ts (621 bytes)
CREATE src/app/hello.component.ts (262 bytes)
UPDATE src/app/app.module.ts (545 bytes)

NOTE: The "dryRun" flag means no changes were made.

Now create the component and delete afterwards the test-file.

ng g c hello -s -t --flat
rm ./src/app/hello.component.spec.ts

chapter005 012 project tree

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

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [
  ]
})
export class HelloComponent implements OnInit {
  @Input() name: string;

  constructor() { }

  ngOnInit(): void {
  }

}
The selector should be kebab-cased (ie app-hello)
  • Don’t forget to insert the <hello>-Tag into the app.component.html

Extend the unit-converter-example
  • fill the options of the select-element from an array

  • refactor the conversion algorithm

chapter005 011

  • use css to

    • make the width of the input field to 50px

    • use the font-family "Lato"

    • Table → border-collapse: collapse;

    • make the padding 5px

    • make the table-header background lightgrey with a solit black border at the bottom

input-ps-kw.component.ts
import {Component, Input, OnInit, Output, EventEmitter} from '@angular/core';

@Component({
  selector: 'app-input-ps-kw',
  templateUrl: './input-ps-kw.component.html',
  styleUrls: ['./input-ps-kw.component.css']
})
export class InputPsKwComponent implements OnInit {
  @Input() value = 0;
  @Output() valueChange = new EventEmitter<number>();

  @Input() uom = "PS"; // unit of measures
  @Output() uomChange = new EventEmitter<string>();

  private readonly onePsIsKw = 0.73549875;

  public uoms = ['PS', 'KW'];

  constructor() { }

  ngOnInit(): void {
  }

  onValueChanged() {
    this.valueChange.emit(this.value);
  }

  onUomChanged() {
    var factor = this.onePsIsKw;

    if (this.uom === 'PS') {
      factor = 1 / factor;
    }

    this.value = Math.round(this.value * factor);
    this.valueChange.emit(this.value);
    this.uomChange.emit(this.uom);

    this.valueChange.emit(this.value);
    this.uomChange.emit(this.uom);
  }
}
input-ps-kw.component.html
<input type="number" [(ngModel)]="value" (change)="onValueChanged()">
<select [(ngModel)]="uom" (change)="onUomChanged()">
  <option *ngFor="let uomOption of uoms">{{ uomOption }}</option>
</select>
input-ps-kw.component.css
input {
  width: 50px;
}
app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  kaeferPower = 30;
  kaeferUom = 'PS';
}
app.component.html
<table>
  <thead>
  <tr>
    <td>Car</td>
    <td>Combustion Engine</td>
    <td>Electric Engine</td>
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>VW K&auml;fer 1964</td>
    <td>
      <app-input-ps-kw [(value)]="kaeferPower" [(uom)]="kaeferUom"></app-input-ps-kw>
    </td>
    <td>&nbsp;</td>
  </tr>
  <tr>
    <td>Ferrari LaFerrari</td>
    <td><app-input-ps-kw [value]="588" [uom]="'KW'"></app-input-ps-kw></td>
    <td><app-input-ps-kw [value]="120" [uom]="'KW'"></app-input-ps-kw></td>
  </tr>
  <tr>
    <td>Tesla Model S Performance</td>
    <td>&nbsp;</td>
    <td><app-input-ps-kw [value]="449" [uom]="'KW'"></app-input-ps-kw></td>
  </tr>
  </tbody>
</table>
app.component.css
p {
  font-family: Lato;
}

table {
  border-collapse: collapse;
}

td {
  padding: 5px;
}

thead td {
  border-bottom: 1px solid black;
  background-color: lightgray;
}

7. Accessing Web APIs

7.1. Run Demo Server

  • Here we use a simple Quarkus Server

  • available as jar-file and on github

download
curl -L https://github.com/htl-leonding-example/demo-api/releases/download/1.0.0/demo-api.jar -o demo-api.jar
run
java -jar demo-api.jar
Use nodejs
git clone https://github.com/htl-leonding-college/htl-mobile-computing.git
cd htl-mobile-computing/angular/0010-demo-api
npm install
npm run build
npm run start
retrieve all persons

http://localhost:8080/api/people

7.2. Use REST Client in WebStorm

Create an empty .http - file (here: requests.http) …​

902 create http request 01

... in a folder (here: http-requests)

903 create http request 02

you can access examples for GET, POST, …​ - requests

904 create http request 03

"Add environment file" → http-client.env.json
{
  "dev": {
    "host": "http://localhost:8080/api"
  }
}
requests.http
// Get a list of people
GET {{host}}/people

###
// Get a list of todo items
GET {{host}}/todos

###
// Add a todo item
POST {{host}}/todos
Content-Type: application/json

{
  "description": "Shopping",
  "assignedTo": "Eve"
}

###
// Add a todo item with unknown person
POST {{host}}/todos
Content-Type: application/json

{
  "description": "Shopping",
  "assignedTo": "Nobody"
}

###
// Get first todo item
GET {{host}}/todos/0

###
// Update todo item
PATCH {{host}}/todos/0
Content-Type: application/json

{
  "description": "Homework"
}

###
// Update todo item to unknown person
PATCH {{host}}/todos/0
Content-Type: application/json

{
  "assignedTo": "Nobody"
}

###
GET {{host}}/todos

###
// Update todo item
PATCH {{host}}/todos/0
Content-Type: application/json

{
  "done": true
}

###
GET {{host}}/todos

###
// Delete todo item
DELETE {{host}}/todos/0

###
GET {{host}}/todos
Install REST Client Plugin in VsCode

For vscode you can install a plugin for similar functionality

chapter006 001

code .

open demo.http

chapter006 002

7.3. Consuming Web APIs

import {HttpClient} from '@angular/common/http';

@Component({ ...
})
export class MyComponent {

  constructor(private http: HttpClient) { ... }
  ...
}
  • HttpClient is a service

  • Dependency Injection is used

ng new todo-rest
cd todo-rest
webstorm .
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import {HttpClientModule} from '@angular/common/http'; (2)

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule, HttpClientModule (1)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
1 add HttpClientModule
2 the import will be done automatically
app.component.ts
import {Component} from '@angular/core';
import {HttpClient} from '@angular/common/http'; (2)

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'todo-rest';

  constructor(private http: HttpClient) { (1)
  }
}

7.3.1. Get a List of Persons

1 add the constructor
2 the import will be done automatically

chapter006 003

Observable

You get notified, when a asynchronous operation is done

app.component.ts
import {Component} from '@angular/core';
import {HttpClient} from '@angular/common/http';

interface IPerson {
  name: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  people: IPerson[] = [];  (1)

  constructor(private http: HttpClient) {
  }

  async loadPeople() {  (2)
    this.people = await this.http
      .get<IPerson[]>('http://localhost:8080/api/people')
      .toPromise();

  }
}
app.component.html
<button (click)="loadPeople()">Load People</button>

<ul>
  <li *ngFor="let person of people">{{person.name}}</li>
</ul>

chapter006 004

7.3.2. Get a List of ToDos

app.component.html
<button (click)="loadPeople()">Load People</button>

<ul>
  <li *ngFor="let person of people">{{person.name}}</li>
</ul>

<button (click)="loadTodos()">Load Todos</button>

<ul>
  <li *ngFor="let todo of todos">{{todo.description}}</li>
</ul>
app.component.ts
import {Component} from '@angular/core';
import {HttpClient} from '@angular/common/http';

interface IPerson {
  name: string;
}

interface ITodoItem {
  id: number;
  description: string;
  assignedTo: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  people: IPerson[] = [];
  todos: ITodoItem[] = [];

  constructor(private http: HttpClient) {
  }

  async loadPeople() {
    this.people = await this.http
      .get<IPerson[]>('http://localhost:8080/api/people')
      .toPromise();
  }

  async loadTodos() {
    this.todos = await this.http
      .get<ITodoItem[]>('http://localhost:8080/api/todos')
      .toPromise();
  }
}
  • The response is empty, but it works

7.3.3. Add a Todo

app.component.html
<button (click)="loadPeople()">Load People</button>

<ul>
  <li *ngFor="let person of people">{{person.name}}</li>
</ul>

<button (click)="loadTodos()">Load Todos</button>
<button (click)="addDemoData()">Add Demo Data</button>

<ul>
  <li *ngFor="let todo of todos">{{todo.description}}</li>
</ul>
app.component.ts
...
  async addDemoData() {
    await this.http
      .post('http://localhost:8080/api/todos', {
        'description': 'Shopping',
        'assignedTo': 'Eve'
      }).toPromise();
    this.loadTodos();
  }
...

7.3.4. Add Delete Buttons

app.component.html
<button (click)="loadPeople()">Load People</button>

<ul>
  <li *ngFor="let person of people">{{person.name}}</li>
</ul>

<button (click)="loadTodos()">Load Todos</button>
<button (click)="addDemoData()">Add Demo Data</button>

<ul>
  <li *ngFor="let todo of todos">
    {{todo.description}} <button (click)="deleteItem(todo.id)">X</button>
  </li>
</ul>
app.component.ts
...
  async deleteItem(id: number) {
    await this.http.delete(`http://localhost:8080/api/todos/${id}`).toPromise();
    this.loadTodos();
  }
...
  • Parameter: ${id}

  • use backticks, because of parameters

chapter006 005

7.3.5. Cooking Recipe

  • async

  • await

  • .toPromise()

In real world nobody uses http directly. You create your own service.

8. Routing

8.1. What is Routing?

A router is a component that takes the url of a page and turns it into html. Navigation in the app

Diese Definition überarbeiten

8.2. Implement Routing

Create a demo project with routing (first with "-d")
ng new router-demo --routing
A project with routing
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module'; (1)
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule  (2)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
This is the default in an Angular application
The routing module - app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';  (1)

const routes: Routes = []; (2)

@NgModule({
  imports: [RouterModule.forRoot(routes)],  (3)
  exports: [RouterModule]  (4)
})
export class AppRoutingModule { }

The RouterModule ⓸ is loaded from Angular ⓵. Thr RouterModule ⓶ uses a route connection ⓷ to control the routing.

001 routing module

Create a new component
ng generate component Customers
ng generate component CustomersDetail

002 routing entries

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'customers' }, (1)
  { path: 'customers', component: CustomersComponent },  (2)
  { path: 'customers/:id', component: CustomersDetailComponent } (3)
];
1 When the full path is empty, redirect to 'customers'.

8.3. router-outlet

app-component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'router-demo';
}

In app.component.html we see the <router-outlet></router-outlet>-Tag. But we will use a template now.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Welcome</h1>
    <router-outlet></router-outlet>
    <p>Footer, (c) by ...</p>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'router-demo';
}

003 router example 004 router example

8.3.1. loading the full page

customers.component.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-customers',
  templateUrl: './customers.component.html',
  styleUrls: ['./customers.component.css']
})
export class CustomersComponent implements OnInit {

  public customers = [
    { name: 'Customer One', id: 1},
    { name: 'Customer Two', id: 2},
    { name: 'Customer Three', id: 3},
  ];

  constructor() { }

  ngOnInit(): void {
  }
}
customers.component.html
<ul>
  <li *ngFor="let c of customers">{{ c.name }}</li>
</ul>

005 router example

<ul>
  <li *ngFor="let c of customers">
    <a href="/customers/{{ c.id }}">{{ c.name }}</a>
  </li>
</ul>

006 router example

Problem:
  • Every time when loading a detail page and when returning to the overview page, the whole pages are loaded.

1
2
3
4
5
<ul>
  <li *ngFor="let c of customers">
    <a [routerLink]="c.id">{{ c.name }}</a>
  </li>
</ul>
Now, there is no network activity anymore

007 router example

8.3.3. Access the PathParam (the id in the route)

customers.component.html
<h1>This is Customer Number {{ ?? }}</h1>

We call a service Activatedroute also part of @angular/router.

008 activated route 009 param map

The paramMap is an observable. Because in single-page-applications, there could be back and forward - buttons for ie the detail data. That means the id would change often. But it is not necessary to load the whole page. So the paramMap is observable to react on changes of the id.

In our simple app this is not necessary, the id wil not change all the time. We will simplify the work with the paramMap and work w/o observables.

customers-detail.components.ts
public id = '';

constructor(private route: ActivatedRoute) {
route.paramMap.subscribe(map => this.id = map.get('id'));
}

We subscribe to the paramMap and get the id once at the beginnening, when the customer is loaded.

customers.component.html
<h1>This is Customer Number {{ id }}</h1>

010 customer1 011 customer2 012 customer3

9. RxJS Basics

9.1. Recap

908 angular blocking nonblocking

909 angular async programming

  • For better understanding please view this …​

910 angular async services

9.2. Introduction

Reactive extensions for Javascript

  • It is a collection of helper methods which operates on top of the observer pattern

  • Observable: object where an outsider (observer) can look at and the observer is notified when something interesting happens

  • ie an asynchronous operation has completed. The the observable pushes information to the observer

  • Our user interface could be the observer. Whenever an asynchronous operation completes we will like to display the result on the user interface.

  • Our typescript code (the ui) is the observer, the observable could be a button.

  • When clicking the button an event is sent to the observer (our typescript project)

  • Another observable is an web api call. If we call a web api the browser is doing the network traffic in an asynchronous way. So you are just initiating the network call, but then you have to wait until the browser actively notifies you: "I am done"

  • This is the reason why the network call (http client GET, POST, …​) is an observable.

  • You will be notified once the network request has been completed

  • The http client is the observable.

  • Our project is the observer.

  • A very important concept of observables is the concept of pushing

  • The observer does not define, when he gets the data.

  • The observable is NOT be active until somebody asks me, wether something is happening. This would be polling.

  • The observable activiely pushes data to the observer when something happens.

  • The observer (as the consumer) only registrates a function and the observable will call this function.

ng new rxjs-demo --inlineStyle=true --inlineTemplate=true --routing=false --style=css --skip-tests=true
cd rxjs-demo
webstorm .
app.component.ts
import { Component } from '@angular/core';
//import {} from 'rxjs';   // will be auto-imported when using Observable
import {Observable} from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="doSomethingWithRx()">Start</button>
  `,
  styles: []
})
export class AppComponent {

  doSomethingWithRx() {
    console.log("Let's start ...");
    const observable = new Observable()
  }
}
RxJS is available in many languages

http://reactivex.io/languages.html

013 rxjs observable

  • Observable gets a subscribe method

  • This is the method which will be called as soon as the observer starts to listen.

  • The observer provides a method which will be called when something happens.

app.component.ts
...
const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
...
  • With .next(…​) the observable can send data to the subscriber (observer).

  • This observable is inactive, cold, dead, …​ Because nobody is listening.

  • if we want somebody to listen, we have to call a method

014 rxjs subscribe methods

There are three methods. They will be called …​:
  • next: …​ whenever something interesting happens

  • error: …​ whenever an error happens

  • complete: …​ when the observer says: "I am done"

app.component.ts
...

  doSomethingWithRx() {
    console.log("Let's start ...");
    const observable = new Observable(subscriber => {
      subscriber.next(1);
      subscriber.next(2);
      subscriber.next(3);
      subscriber.complete();
    });

    observable.subscribe(      (1)
      val => console.log(`I got ${val}`)
    );
  }
}
1 this method is called, when the observable calls .next(…​)

015 rxjs browser result

add delayed actions
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
doSomethingWithRx() {
console.log('Let\'s start ...');
const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {  (1)
    subscriber.next(4);
    subscriber.next(5);
    subscriber.complete();
  }, 250);
});

observable.subscribe(
  val => console.log(`I got ${val}`)
);
}
1 here we simulate the execution of asynchronous code
addan error message
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  doSomethingWithRx() {
    console.log('Let\'s start ...');
    const observable = new Observable(subscriber => {
      subscriber.next(1);
      subscriber.next(2);
      subscriber.next(3);
      setTimeout(() => {
        subscriber.next(4);
        subscriber.error('Something bad happened ...');
        subscriber.next(5);
        subscriber.complete();
      }, 250);
    });

    observable.subscribe(
      val => console.log(`I got ${val}`),
      err => console.log(`Ops: ${err}`)
    );
  }

016 rxjs observable error

9.3. Method 'of()'

Helper-method which converts the arguments to an observable sequence.

// observable.subscribe(
//   val => console.log(`I got ${val}`),
//   err => console.log(`Ops: ${err}`)
// );

of(1, 2, 3, 4, 5, 6).subscribe(
  val => console.log(`I got ${val}`)
);

017 rxjs observable of

  • This is still synchronous!!!

9.4. Method 'interval(…​)'

Creates an Observable that emits sequential numbers every specified interval of time, on a specified

    interval(250).subscribe(
      val => console.log(`I got ${val}`)
    );

018 rxjs observable interval

  • This runs noow for ever

  • But how can we control this?

  • Any kind of observable gives me back a subscription

    • Observer

    • Observable

    • Subscription

  • The Observer has a subscription at the Observable

  • With a timeout we can unsubscribe.

9.5. Unsubscribe with a timer

...
const subscription = interval(250).subscribe(
  val => console.log(`I got ${val}`)
);

setTimeout(() => subscription.unsubscribe(), 5000);
...

But there is a more elegant way.

9.6. Operator 'take(…​)'

...
interval(250)
  .pipe(take(5))
  .subscribe(
  val => console.log(`I got ${val}`)
);

9.7. Operator 'map(…​)'

...
 interval(250)
  .pipe(map(x => x * 2), take(5))
  .subscribe(val => console.log(`I got ${val}`)
);
...

9.8. Operator 'filter(…​)'

...
 interval(250)
  .pipe(
    map(x => x * 2),
    filter(x => x <= 2),
    take(5)
   )
  .subscribe(val => console.log(`I got ${val}`)
);
  • The order of the operators (map and filter) does matter

9.9. Function 'concat(…​)'

concat(
  interval(250)
    .pipe(take(5)),
  interval(100)
    .pipe(take(5))
).pipe(map(x => x * 2))
  .subscribe(val => console.log(`I got ${val}`));

9.10. Function 'merge(…​)'

merge(
  interval(250)
    .pipe(take(5)),
  interval(100)
    .pipe(take(5))
).pipe(map(x => x * 2))
  .subscribe(val => console.log(`I got ${val}`));
What is the difference between concat and merge?

RxJS is very powerful. But what does this mean for Angular?

9.11. RxJS for the Angular UI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {Component} from '@angular/core';
import {concat, interval, merge, Observable, of} from 'rxjs';
import {filter, map, take} from 'rxjs/operators';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="doSomethingWithRx()">Start</button>
    <p>{{ values }}</p>
  `,
  styles: []
})
export class AppComponent {

  public values: Observable<number>;

  doSomethingWithRx() {
    ...

    this.values = merge(
      interval(250)
        .pipe(take(5)),
      interval(100)
        .pipe(take(5))
    ).pipe(map(x => x * 2));
  } (1)
}
1 Because we removed the .subscribe(…​) this Observable is cold and will not work.
{{ values }} shows only a "object"

019 rxjs

@Component({
  selector: 'app-root',
  template: `
    <button (click)="doSomethingWithRx()">Start</button>
    <p>{{ values | async }}</p> (1)
  `,
  styles: []
})
1 when we add "| async" the ui subscribes to the observable and it works

020 rxjs

9.12. RxJS for Web APIs (REST)

cd ./htl-mobile-computing/angular/0010-demo-api
npm start
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule} from '@angular/common/http';  (1)

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule  (2)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
return type is an observable of an array

021 rxjs rest api

app.component.ts
import {Component} from '@angular/core';
import {concat, interval, merge, Observable, of} from 'rxjs';
import {filter, map, retry, take} from 'rxjs/operators';
import {HttpClient} from '@angular/common/http';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="doSomethingWithRx()">Start</button>
    <ul>
      <li *ngFor="let v of values | async">{{v.name}}</li> (1)
    </ul>
  `,
  styles: []
})
export class AppComponent {

  public values: Observable<any[]>;

  constructor(private http: HttpClient) {
  }

  doSomethingWithRx() {
    this.values = this.http.get<any[]>("http://localhost:8080/api/people")
      .pipe(retry());  (2)
  }
}
1 Pay attention to the subscription (| async)

022 rxjs rest api

10. Flexbox

responsive apps

10.1. flex-direction: row and column

display:flex enables Flexbox
.box {
  display: flex;
}

10.2. flex-wrap

10.3. flex-items

  • Google-Suche: css flexbox

10.4. Media Query

  • Google-Suche: css media query

10.5. Angular Flex Layout

10.5.1. Installation

  • install @angular/flex-layout, @angular/cdk

  • import FlexLayoutModule

10.5.2. Usage

  • instead of css-files you can use Angular-directives

    <div fxLayout="row" fxLayoutAlign="space-between">
    </div>
  • it is enabled for data-binding

10.5.4. Exercise: conference-agenda

<div class="slot" *ngFor="let slot of slots" fxLayout.lt-md="column" fxLayout.gt-sm="row"
  fxLayoutAlign.gt-sm="start center">
  <div class="time nowrap">{{ slot.start }}-{{ slot.end }}</div>
  <div fxLayout.xs="column" fxLayout.gt-xs="row">
    <div *ngFor="let session of getSessionsOfSlot(slot.id)">
      <div class="session">
        <div class="session-title">{{ session.title }}</div>
        <div class="session-speaker">{{ session.speaker }}</div>
        <div class="session-room">Room {{ session.room }}</div>
      </div>
    </div>
  </div>
</div>

10.5.6. Grid System

gdAlignColumns, gdAlignRows, gd Areas, …​

10.5.7. TypeScript API

  • A service injectable through the constructor

import {MediaObserver} from '@angular/flex-layout';
constructor(public mediaObserver: MediaObserver ) {
  mediaObserver.media$.subscribe(...); (1)
}
1 media$ is an observable ($)

11. Testing with Jasmine

11.1. Testing

  • Customers want error free, working software.

  • Errors (always) occurs.

  • Errors will be fixed.

  • Sometimes, when fixing an error (ie login) another error occurs (ie in registration page).

  • The error on the registration page exists, because of fixing the login.

  • This is called a regression bug. Fixing or extending your code may have side-effects to existing parts of code.

  • Therefore you have to use regresseion testing: Testing the existing code again and again.

  • For regression testing you use automated tests.

  • With automated tests you are preventing bugs not only fixing bugs.

11.2. Testing in Angular

Create a demo project
ng new jasmine-intro --routing=false --style=css
cd jasmine-intro
webstorm .
Open a terminal in Webstorm
npm test
The browser is opened automatically

023 testing npm test

  • Angular runs the application when testing

The terminal shows the success of the test

024 testing webstorm output

  • When changing something, and clicking into the terminal window, the tests run again automatically.

Now the tests breaks

025 testing webstorm output error

026 testing browser output error

11.3. How to write tests

11.3.1. Add a Simple Service

  • Services are available in the whole Angular app.

ng generate service circle-math

11.3.2. Writing a Function to test

circle-math.service.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class CircleMathService {

  constructor() { }

  calculateCircleArea(radius: number): number {
    return radius * radius * Math.PI;
  }
}
The test file for our simple test is unnecessarily too complex
import { TestBed } from '@angular/core/testing';

import { CircleMathService } from './circle-math.service';

describe('CircleMathService', () => {
  let service: CircleMathService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(CircleMathService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});
Only two lines remain
import { TestBed } from '@angular/core/testing';

import { CircleMathService } from './circle-math.service';

describe('CircleMathService', () => {  (1)
  it('should be created', () => {  (2)
  });
});
1 describe is like a headline for tests (part of jasmine)
2 it should be created

11.3.3. AAA-Pattern

import { TestBed } from '@angular/core/testing';

import { CircleMathService } from './circle-math.service';

describe('CircleMathService', () => {
  it('should calculate the circles area correctly', () => {
    // Prepare (arrange, given)


    // Execute (act, when)


    // Check results (assert, then)
  });
});

11.3.4. Implement the Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { TestBed } from '@angular/core/testing';

import { CircleMathService } from './circle-math.service';

describe('CircleMathService', () => {
  it('should calculate the circles area correctly', () => {
    // Prepare (arrange, given)
    const mathService = new CircleMathService();

    // Execute (act, when)
    const result = mathService.calculateCircleArea(1);

    // Check results (assert, then)
    expect(result).toBe(Math.PI);
  });
});

027 testing first test ide

  • Now there are 4 tests.

Write nearly passing tests at the beginning. Don’t write passing tests. Every test should fail once. (ie const result = mathService.calculatorCircleArea(1.1);)

11.4. To Focus on one Test

  • use fit`instead of `it.

  • The other test will not be executed

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { TestBed } from '@angular/core/testing';

import { CircleMathService } from './circle-math.service';

describe('CircleMathService', () => {
  fit('should calculate the circles area correctly', () => {
    // Prepare (arrange, given)
    const mathService = new CircleMathService();

    // Execute (act, when)
    const result = mathService.calculateCircleArea(1.1);

    // Check results (assert, then)
    expect(result).toBe(Math.PI);
  });
});

028 testing first test fit

11.4.1. How to set a breakpoint in the browser

Open the source code in the browser

029 testing first test debug test

Reload the Browser (F10 or Cmd-R)

030 testing first test debug paused on breakpoint

  • Now you can use the button in the upper right corner to continue …​

  • ie step into the function call

  • remove the error and the test works again

11.4.2. How to set a breakpoint in Webstorm

031 testing first test debug in webstorm

1 Set the breakpoint in the code.
2 Choose the test-configuration.
3 Click the Debug button
4 When reaching the breakpoint, you get the debug window
You can start/test/debug the app in the terminal but also in Webstorm itself.

11.5. Testing "Edge Cases"

ng generate service graphic-math
graphic-math.service.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { Injectable } from '@angular/core';
import {CircleMathService} from './circle-math.service';

@Injectable({
  providedIn: 'root'
})
export class GraphicMathService {

  constructor(private circleMath: CircleMathService) { } (1)

  circleRadiuses: number[] = [];
  circleAreas: number[] = [];

  calculateAreasOfAllCircles() {
    this.circleAreas = [];
    for (const r of this.circleRadiuses) {
      this.circleAreas.push(this.circleMath.calculateCircleArea(r));
    }
  }
}
1 Because CircleMathService is a service you can use it like a httpService
  • The unit-tests still run

  • Create a test

graphic-math.service.spec.ts
import { TestBed } from '@angular/core/testing';

import { GraphicMathService } from './graphic-math.service';
import {CircleMathService} from './circle-math.service';

describe('GraphicMathService', () => {
  it('should calculate areas of circles correctly', () => {
    // arrange
    const graphicMathService = new GraphicMathService(new CircleMathService());
    graphicMathService.circleRadiuses.push(1);
    graphicMathService.circleRadiuses.push(2);

    // act
    graphicMathService.calculateAreasOfAllCircles();

    // assert
    expect(graphicMathService.circleAreas.length).toBe(2);
    expect(graphicMathService.circleAreas[0]).toBe(Math.PI);
    expect(graphicMathService.circleAreas[1]).toBe(2 * 2 * Math.PI);  (1)
  });
});
1 2 * 2 * Math.PI → This is not good. Don’t repeat the business logic. When the algorithm for calculting the area would change, all tests must be rewritten
add an "edge test"
  it('should handle empty input array correctly', () => {
    // arrange
    const graphicMathService = new GraphicMathService(new CircleMathService());

    // act
    graphicMathService.calculateAreasOfAllCircles();

    // assert
    expect(graphicMathService.circleAreas.length).toBe(0);
  });
  • In a ideal project the end users will give you the test cases to implement

  • The customers tells you: "If I input this, I will get that" → write SysSpec and or User Stories

  • Ask the customers for Acceptance Criterias

These are no unit tests
  • We are not testing one "thing"

  • We are not only testing GraphicMathService, we are also testing CircleMathService

  • We need mock objects to test only one aspect that means only one class.

11.6. Mock Objects

11.6.1. Excursus: Inline Reference Information in WebStorm

How to show inline reference information of an object in WebStorm

032 testing inline reference

11.6.2. Create a Spy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  it('should calculate areas of circles correctly with mocking', () => {
    // arrange
    const graphicMathServiceMock = {
      calculateCircleArea: jasmine.createSpy('calculateCircleArea').and.returnValue(42)
    };

    const graphicMathService = new GraphicMathService(graphicMathServiceMock); (1)
    graphicMathService.circleRadiuses.push(1);
    graphicMathService.circleRadiuses.push(2);

    // act
    graphicMathService.calculateAreasOfAllCircles();

    // assert
    expect(graphicMathService.circleAreas.length).toBe(2);
    expect(graphicMathService.circleAreas[0]).toBe(42); (2)
    expect(graphicMathService.circleAreas[1]).toBe(42); (2)
  });
1 you use now the mock object
2 the return value is always 42

11.6.3. Resources for Jasmine

033 testing jasmine introduction

11.7. Disable a Test (Test is Pending)

  • use xit instead of it

app.component.html
<h1>Calculator</h1>
  • While writing the html code, the tests break

  • So we can disable the tests

  • Remove the x after refactoring your app !!!

app.component.html
<h1>Calculator</h1>

<p>
  <label>Radius:</label>
  <input type="number" [(ngModel)]="radius"><br/>
  <button (click)="calculate()">Calculate</button>
</p>
<p>
  The result is <span>{{ result }}</span>
</p>

11.8. Test Modules in Angular

  • A separate TestModule has to be defined for every Test Suite

  • Because of Mockups

app.component.spec.ts
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents(); (1)
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'jasmine-intro'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.title).toEqual('jasmine-intro');
  });

  it('should render title', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('.content span').textContent).toContain('jasmine-intro app is running!');
  });
});
1 this module is for testing only with mockups. It does not run dependencies.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import {FormsModule} from '@angular/forms';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        FormsModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'jasmine-intro'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.title).toEqual('jasmine-intro');
  });

  // it('should render title', () => {
  //   const fixture = TestBed.createComponent(AppComponent);
  //   fixture.detectChanges();
  //   const compiled = fixture.nativeElement;
  //   expect(compiled.querySelector('.content span').textContent).toContain('jasmine-intro app is running!');
  // });
});
  • Now the test are passing

11.8.1. Fixing the example

<h1>Calculator</h1>

<p>
  <label>Radius:</label>
  <input type="number" [(ngModel)]="radius"><br/>
  <button (click)="calculate()">Calculate</button>
</p>
<p>
  The result is <span>{{ result }}</span>
</p>
import { Component } from '@angular/core';
import {CircleMathService} from './circle-math.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'jasmine-intro';
  radius = 1;
  result: number;

  constructor(private calculator: CircleMathService) {
  }

  calculate() {
    this.result = this.calculator.calculateCircleArea(this.radius);
  }
}

034 testing jasmine browser result 035 testing jasmine browser result

12. OpenID Connect

OAUTH2

036 no central auth

  • In former times each app had its own user data

  • Now the user data is centralized, the apps / services are accessing this central store

  • example: employee leaves the company,

    • hires at a competitor

    • you forget to remove this employee

    • now a competitor can access this service ie discussion group for important stuff

  • resource owner

  • id-token → passport

  • access-token → visa