Credits to Rainer Stropek. This lecture note are based on his Angular Course.
1. Angular CLI
npm install -g @angular/cli
sudo npm install -g @angular/cli
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
$ where ng
/usr/local/bin/ng
ng --version
ng
Command | Description |
---|---|
new |
Creates a new workspace and initialize an Angular app. It is possible to host multiple apps in a workspace |
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 |
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.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.
2. Configuring WebStorm
2.1. Format Space within braces for ES6 import/export
import {Component, OnInit} from '@angular/core';
import { Component, OnInit } from '@angular/core';
2.1.1. Option 1: Config in preferences
Use the shortcut Option+Command+L to apply the changes to the code
2.1.2. Option 2: Config in tslint.json
# 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_' |
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.
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"
-
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
cd dist/my-great-app
python3 -m http.server
open an browser with http://localhost:8000
4. Angular Ecosystem
5. Templates and Data Binding
5.1. Interpolation
<h1>
Welcome to {{ title }}
</h1>
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);
}
}
→ one-way interpolation
5.2. Event-Binding
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 |
<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)
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 += '!!';
}
}
<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
...
<input type="text" value="asdf" />
...
<input type="text" value="title" />
...
<input type="input" [value]="title" />
5.5. Two-Way-Binding
Combine data binding with event binding
...
<input type="input" [(ngModel)]="title" />
-
The site is broken
-
Click on "Import FormsModule"
-
Check the result in
app.module.ts
-
Eventually restart the server
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)
...
<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
...
<button type="button" (click)="addItem()">
Add item
</button>
public addItem(): void {
this.todos.push('something');
}
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>
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.
@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 |
3 | Instead of using a template html file you can also write the html code directly here |
4 | The style only applies to the current 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
-
…
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
<h1>
Welcome to {{ title }}
</h1>
<app-numeric-input></app-numeric-input> (1)
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
<input type="number"><button>Reset</button>
<h1>
Welcome to {{ title }}
</h1>
<app-numeric-input></app-numeric-input>
<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/>
6.2.3. Input Parameter
Communication between child- and parent-component
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. |
<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/>
<input type="number" [value]="value">
<button>Reset</button>
<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
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. |
<input type="number" [value]="value">
<button (click)="onResetClicked()">Reset</button>
1 | Create method with <Alt>-Enter … |
2 | … or wait until the popup appears |
...
onReset() {
console.log('Reset clicked!');
}
...
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)
}
}
<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.2. Set up the Project
ng new unit-converter --dry-run
ng new unit-converter
Alternatively you can use;
ng generate component input-ps-kw
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 { }
Alternatively you can use npm start
or ng serve
6.3.3. Start Coding
<input type="number">
<select>
<option>PS</option>
<option>kW</option>
</select>
<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 |
<input type="number" [(ngModel)]="value"> (1)
<select>
<option>PS</option>
<option>KW</option>
</select>
<input type="number" [(ngModel)]="value">
<select [(ngModel)]="uom"> (1)
<option>PS</option>
<option>kW</option>
</select>
...
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
-
Add the @Input-Decorator to the field (value).
-
Check, if input is imported.
-
Add the parameter in the parent component to the elements.
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 {
}
}
<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>
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) |
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)
}
...
<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äfer has {{ kaeferPower }} {{ kaeferUom }}</h1> (3)
...
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 type="number" [(ngModel)]="value" (change)="onPowerChanged()">
<select [(ngModel)]="uom" (change)="onUomChanged()">
<option>PS</option>
<option>KW</option>
</select>
...
onPowerChanged() {
this.valueChange.emit();
}
onUomChanged() {
this.uomChange.emit();
}
}
...
<tr>
<td>VW Kä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
...
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
--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.
ng generate component hello --inline-style --inline-template --flat --dry-run
ng g c hello -s -t --flat -d
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
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
-
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äfer 1964</td>
<td>
<app-input-ps-kw [(value)]="kaeferPower" [(uom)]="kaeferUom"></app-input-ps-kw>
</td>
<td> </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> </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
curl -L https://github.com/htl-leonding-example/demo-api/releases/download/1.0.0/demo-api.jar -o demo-api.jar
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
7.2. Use REST Client in WebStorm
requests.http
) …
http-requests
)
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
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 .
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 |
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 |
You get notified, when a asynchronous operation is done
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();
}
}
<button (click)="loadPeople()">Load People</button>
<ul>
<li *ngFor="let person of people">{{person.name}}</li>
</ul>
7.3.2. Get a List of ToDos
<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>
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
<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>
...
async addDemoData() {
await this.http
.post('http://localhost:8080/api/todos', {
'description': 'Shopping',
'assignedTo': 'Eve'
}).toPromise();
this.loadTodos();
}
...
7.3.4. Add Delete Buttons
<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>
...
async deleteItem(id: number) {
await this.http.delete(`http://localhost:8080/api/todos/${id}`).toPromise();
this.loadTodos();
}
...
-
Parameter: ${id}
-
use backticks, because of parameters
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
ng new router-demo --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 |
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.
ng generate component Customers
ng generate component CustomersDetail
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
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';
}
8.3.1. loading the full page
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 {
}
}
<ul>
<li *ngFor="let c of customers">{{ c.name }}</li>
</ul>
8.3.2. routerLink - partially loading a page
<ul>
<li *ngFor="let c of customers">
<a href="/customers/{{ c.id }}">{{ c.name }}</a>
</li>
</ul>
-
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>
8.3.3. Access the PathParam (the id in the route)
<h1>This is Customer Number {{ ?? }}</h1>
We call a service Activatedroute
also part of @angular/router
.
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.
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.
<h1>This is Customer Number {{ id }}</h1>
9. RxJS Basics
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 .
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()
}
}
-
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.
...
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
-
next: … whenever something interesting happens
-
error: … whenever an error happens
-
complete: … when the observer says: "I am done"
...
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(…) |
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 |
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}`)
);
}
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}`)
);
-
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}`)
);
-
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. |
@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 |
9.12. RxJS for Web APIs (REST)
cd ./htl-mobile-computing/angular/0010-demo-api
npm start
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 { }
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) |
10. Flexbox
responsive apps
Semantic-ui is based on flexbox: https://semantic-ui.com/examples/grid.html
10.5. Angular Flex Layout
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.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
ng new jasmine-intro --routing=false --style=css
cd jasmine-intro
webstorm .
npm test
-
Angular runs the application when testing
-
When changing something, and clicking into the terminal window, the tests run again automatically.
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
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;
}
}
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();
});
});
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);
});
});
-
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);
});
});
11.5. Testing "Edge Cases"
ng generate service graphic-math
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
|
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 |
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 testingCircleMathService
-
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
-
View - Quick Definition or Alt-Space
-
in VSC: peek-in-definition
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.7. Disable a Test (Test is Pending)
-
use
xit
instead ofit
<h1>Calculator</h1>
-
While writing the html code, the tests break
-
So we can disable the tests
-
Remove the x after refactoring your app !!!
<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
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);
}
}
12. OpenID Connect
OAUTH2
-
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