Angular

Präsentationen

https://marko-knoebl.github.io/slides/

Ihr Trainer

Marko Knöbl

  • aus Wien
  • ehemaliger Mathematiklehrer
  • Programmierthemen:
    • JavaScript, TypeScript und React
    • Python, Data Science

Vorstellung der Teilnehmer

  • Aktuelle Projekte
  • Vorkenntnisse
  • Erwartungen / Wünsche

Organisatorisches

  • Kursdauer
  • Pausen
  • Mittagessen
  • Unterlagen
  • Fragen, Feedback? - Jederzeit erwünscht

Code

Code verfügbar unter: https://github.com/marko-knoebl/courses-code

Agenda

  • Einführung in Angular 7 und erste Komponenten
  • Einführung in ES2015+ und TypeScript
  • Komponenten – Templates, Properties und Events
  • Forms
  • Daten-Services
  • Server-Kommunikation
  • Routing
  • Material Design - Komponenten

Angular

Was ist Angular?

  • Eine der 3 großen JavaScript-UI-Libraries (neben React.js, vue.js)

Grundlagen moderner JavaScript-UI-Libraries

  • Deklarativ / datengetrieben
  • Komponenten-Struktur

Deklarativ / datengetrieben

  • Im Hintergrund steht ein Datenmodell, das den gesamten Anwendungszustand abbildet
  • Man ändert das Modell, das View wird von alleine (möglichst effizient) aktualisiert

Komponenten-Struktur

  • "eigene" HTML-Tags
  • Datenfluss via Properties und Events
  • Ãœblicherweise unidirektionaler Datenfluss (vom Eltern- zum Kindelement)

Komponenten-Struktur: Tags und Properties

<todo-item [title]=" 'groceries' " [completed]="false">
</todo-item>

Was macht Angular besonders?

  • Verwendung fast ausschließlich mit TypeScript
  • Beinhaltet als Framework einiges an zusätzlichen Tools:
    • Forms
    • Angular Router
    • HTTP-Kommunikation

Geschichte von Angular

  • AngularJS: Entwicklungsbeginn 2009
  • Angular 2: Erschienen im September 2016 – Komplette Neuentwicklung
  • Seither neue Releases ca alle 6 Monate
  • Aktuell: Angular 7 (Oktober 2018)

Beispiel: Datenmodell und -fluss in einer Todo-App

Angular - Grundlagen

Entwicklung mit node.js und npm

  • node.js: JS-Runtime
    • Angular CLI
    • Ausführen des Testservers
    • Unit-Tests
  • npm: Paketmanager
    • zum Verwalten von Abhängigkeiten
    • Pakete im node_modules-Ordner
    • Konfiguration in package.json

Angular CLI

Meistgenutzte Methode zum Erstellen von Angular-Anwendungen: Angular CLI (ng)

setup:

npm install -g @angular/cli

siehe auch: https://github.com/angular/angular-cli/wiki

Angular CLI: neues Projekt

ng new playground

Angular CLI installiert im Hintergrund einige Abhängigkeiten und legt diese im Ordner node_modules ab.

Angular CLI: Konfiguration

  • add Angular routing?
  • stylesheet format?

Angular CLI: Testserver

Im Projektordner:

ng serve --open

Angular CLI: Befehle

  • ng new $projectname: Erstellt neues Angular-Projekt
  • ng serve: Startet den Testserver
  • ng generate component $name: Erstellt eine neue Komponente
  • ng generate service $name: Erstellt ein neues Service
  • ng build --prod: Führt einen Production-Build aus (im dist-Ordner)

Standard Projektstruktur

Angular CLI erstellt umfangreiche Projektstruktur

Uns interessiert hauptsächlich der Ordner src/app

Standard Projektstruktur

  • package.json: npm-Konfiguration
  • karma.conf, protractor.conf: Tests
  • tsconfig.json: Typescript-Konfiguration
  • src/index.html: Einstiegspunkt
  • src/polyfills.ts: Polyfills für „ältere“ Browser
  • src/app/: eigentliche Angular-App

src/polyfills.ts

  • Wird für Unterstützung älterer Browser benötigt (zB IE9-IE11)
  • Zum testen auf / deployen für ältere Browser: entsprechende Zeilen „einkommentieren“ und entsprechende Abhängigkeiten mittels npm installieren
  • Details: https://angular.io/guide/browser-support

Komponentenstruktur

Mit angular-cli (ng) erstellte Komponenten gliedern sich in drei Dateien, zB:

  • app.component.html (Template)
  • app.component.css (auf Komponente beschränkter Stil)
  • app.component.ts (Programmcode)

Beispiel: Änderungen an der Komponente

Aufgaben

  • Die Komponente soll „Hallo, $name“ ausgeben, wobei der Name in der .ts-Datei definiert wird

Weitere Beispiele

  • <app-time>-Komponente, die die aktuelle Uhrzeit anzeigt
  • <app-roulette>-Komponente, die eine Zufallszahl von 0-36 anzeigt

ng build

Mittels

ng build --prod

führen wir einen Production-Build aus

Grundlagen für die Schulung

Grundlagen für die Schulung

TypeScript für Angular

Properties im Constructor

class Person {
  constructor(public name: string, public age: number) {}
}

// Kurzform für:
class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

Decorators

Mit Decorators lassen sich Funktionen und Klassen nach ihrer Erstellung mittels einer Funktion – dem Decorator – verändern
Beispiel:

// Hypothetischer cache-Decorator,
// der die Resultate eines Funktionsaufrufs speichert
import { cache } from 'cache';

@cache
function getResults() {
  ...
}

Decorators in Angular

In Angular werden Decorators verwendet, um Metadaten zu einer Klasse zu ergänzen:

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

Angular - Grundlagen II

VS Code - Plugins

Empfehlung

  • Angular Snippets (by John Papa)
  • Angular Language Service
  • TSLint

Templatesprache: Properties

Dynamisches Setzen von Properties:

<input type="number" [value]="1/3">

Properties: Aufgaben

  • Zeige ein zufälliges Bild an. Verwende dazu:
const getImgUrl = () =>
  'https://picsum.photos/200/300?image=' +
  Math.floor(Math.random() * 100);

Templatesprache: *ngFor

Mit *ngFor können wir HTML-Elemente wiederholen:

<div *ngFor="let todo of todos">
  {{ todo.title }}
</div>

*ngFor wiederholt das Element, auf das es angewendet wird (hier: das div-Element)

Templatesprache: *ngFor

Optional können wir mit Hilfe der index-Variable mitzählen:

<div *ngFor="let todo of todos; index as i">
  {{i}}: {{ todo.title }}
</div>

Templatesprache: *ngFor

Beispiel: Rating-Komponente, die Sterne anzeigt (mit zunächst unveränderlicher Anzahl)

Templatesprache: *ngIf

Mit *ngIf können wir ein Element unter bestimmten Bedingungen ein- oder ausblenden.

Beispiele:

<div *ngIf="item.importance >= 3">{{ item.text }}</div>

Templatesprache: Klassen

Wir können CSS-Klassen dynamisch zuweisen:

<div [class.important]="isImportant()">…</div>

<span [class.negative]="item.amount < 0">
  {{ item.amount }}
</span>

Templatesprache: Klassen

Weitere Möglichkeiten:

<div [ngClass]=
  "{important: isImportant(), negative: item.amount < 0}">
  ...
</div>

Templatesprache: Stile

Für jede Komponente können wir CSS-Stile festlegen. Diese betreffen dann nur eine Komponente.

Dazu gibt es zwei Möglichkeiten in der Konfiguration:

styleUrls: ['./app.component.css']
oder
styles: ['h1 {..} …']

Templatesprache: Stile

In Komponentenstilen bezieht sich der besondere :host-Selektor auf das Komponententag selbst.

:host {
  display: block;
}

Templatesprache: Stile

Für einzelne HTML-Elemente können wir direkt mit einer Angular-eigenen Syntax Stile via Properties setzen:

<div [style.color]="getTextColor()">…</div>
<span [stlye.font-size.px]="getFontSize()">…</span>
<div [style.width.%]="100 / n">…</div>

Templatesprache: Stile

Beispiel: Wir erstellen ein <div>-Element, dessen Schriftgröße zufällig festgesetzt wird

Templatesprache: Pipes

Pipe = im wesentlichen eine Funktion, die im Template zur string-Formatierung zur Verfügung steht

Templatesprache: Pipes

Beispiele:

  • Today is {{ today | date }}
  • My name is {{ name | uppercase }}
  • The price is {{ price | number:'1.2-2'}}
  • Debugging information: {{ todo | json }}
  • Total amount: {{total | currency:'EUR':'symbol':'1.2-2'}}

Lokalisierung von Pipes

angular.json:

...targets.build.configurations.de.i18nLocale = "de"

...targets.serve.configurations.de.browserTarget = "$projectname:build:de"

im Terminal:

ng serve --configuration=de

Templatesprache: Templatevariablen

Mit Templatevariablen können wir direkt auf Elemente aus dem Template zugreifen. Dazu verwenden wir das #-Zeichen.

Todo: <input #newtodo>
<button (click)="addTodo(newtodo.value)">
  Add
</button>

Templatesprache: Events

Wir können alle Standard-DOM-Events über die folgende Syntax überwachen:

<div (eventname)="eventHandler()">…</div>

Liste von Standard-DOM-Events: https://www.w3schools.com/jsref/dom_obj_event.asp

Templatesprache: Events

<button (click)="increase()"> + </button>

<input (keydown)="onKeyDown()">

Standard-Events: Event-Objekt

Das JavaScript Event-Objekt können wir über den Parameter mit dem Namen $event erhalten.

<input (keydown)="onKeyDown($event)">
onKeyDown(event: KeyboardEvent) {
  event.preventDefault();
  this.key = event.key;
}

Standard-Events: Event-Filter

<input (keyup.enter)="onEnter()">

Mit der obigen Syntax können Events auf bestimmte Kategorien beschränkt werden

Beispiele

  • counter-Komponente
  • diashow-Komponente

Eigene Komponenten: Properties & Events

Eigene Properties & Events

Properties und Events stellen die wichtigsten Mechanismen dar, mit denen Komponenten in SPAs miteinander interagieren.

In Angular verwendet man auch die Begriffe:

  • input = Property
  • output = Event

Eigene Properties

Beispiel Ratingkomponente

Wir erstellen eine Komponente, die eine bestimmte Anzahl an Sternen anzeigt, welche sich über eine Property steuern lässt:

<app-rating [stars]="4"></app-rating>

Eigene Properties

Wir verändern rating.component.ts folgendermaßen:

zunächst importieren wir den Input-Decorator:

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

Eigene Properties

weiters setzen wir in der Komponentenklasse Typinformationen fest:

  @Input() stars: number;

Eigene Properties

aus dem Template rufen wir die folgende Hilfsmethode auf:

getStarString = () => {
  return '*'.repeat(this.stars);
};

Properties: weitere Beispiele

  • diashow - Komponente (mit Properties)
  • roman-number - Komponente
  • playing-card - Komponente

Eigene Events

Definition eigener Events:

  • Eventname
  • evtl zugehöriger Parameter (zB Zahl, String, oder auch ein komplexeres Objekt) - dieser wird im $event-Parameter übergeben
  • für den zugehörigen Parameter muss ein Typ festgelegt werden (kann auch void sein)

Eigene Events - Definition

import { Output, EventEmitter} from '@angular/core';

[...]

  @Output() tick: EventEmitter<number> =
    new EventEmitter<number>();

  [...]

  this.tick.emit(this.remainingTime);

Eigene Events - Beispiel

Timer mit Events: start, tick, end

Nutze diese Events für Beispielnachrichten:

  • "Der Timer ist gestartet"
  • "Nur noch 3 Sekunden"
  • "Zeit abgelaufen"

Eigene Events - Beispiele

<app-timer
  (start)="displayStartMessage()"
  (tick)="updateTime($event)"
  (end)="displayEndMessage()">
</app-timer>

Eigene Events - Beispiele

Beispiel: rating

Wir ändern die rating-Komponente, sodass durch einen Klick auf einen Stern ein entsprechendes Event an die Elternkomponente übergeben wird

<app-rating (change)="onRatingChange($event)"></app-rating>

Beispiel: Todo-App

Inputs & Forms

Inputs & Forms - Grundlagen

Um Forms und Inputs mit Angular nutzen zu können, müssen wir zunächst in app.module.ts das FormsModule importieren:

import {FormsModule} from '@angular/forms';
  …
  imports: [
    BrowserModule,
    FormsModule
  ],

Inputs: ngModel

Mit Hilfe von ngModel können wir Änderungen an einem Input überwachen lassen

Inputs: ngModel

Einfaches (theoretisches) Beispiel mit standard HTML-Attributen und Templatevariablen:

<input ngModel #myInput required minlength="3"><br>
value: {{ myInput.value }} <br>
valid: {{ myInput.validity.valid }}

Inputs: ngModel

Ãœblicherweise greift man nicht auf das input-Element selbst zu, sondern auf dessen ngModel-Controller:

<input ngModel #myInput="ngModel" required minlength="3"> <br>
value: {{ myInput.value }} <br>
valid: {{ myInput.valid }} <br>
touched: {{ myInput.touched }} <br>
pristine: {{ myInput.pristine }}

Inputs: ngModel

<input ngModel #myInput="ngModel" required minlength="3">

Was passiert hier?

  • Mit ngModel bringen wir Angular dazu, den Inhalt des Inputs zu überwachen.
  • Mit #myInput="ngModel" setzen wir dann eine Variable, die auf das entsprechende Datenmodell verweist.
  • Zu beachten: Der Wert rechts (ngModel) ist fest vorgegeben, den linken Namen (myInput) können wir selbst bestimmen.

Inputs: ngModel

Folgende Eigenschaften des ngModel-Controllers können wir überwachen:

  • value: Wert – dieser ist oft automatisch vom passenden Typ (zB bei type="number" oder type="checkbox" – nicht aber bei type="date")
  • valid
  • touched: ändert sich auf true, wenn der Fokus in das Feld gesetzt wird und dann wieder auf etwas anderes
  • pristine: ändert sich auf false, sobald der Wert zum ersten Mal geändert wird.

Ãœbung zu ngModel: Passwort

Wir setzen eine Passworteingabe um:

  • Es soll zwei Eingabefelder geben, deren Inhalt wir mit ngModel überwachen.

  • Solange die Eingabefelder unterschiedliche Werte haben, soll der zugehörige OK-Button auf disabled gesetzt sein

  • Solange die Eingabefelder unterschiedliche Werte haben und ins zweite Feld schon etwas eingegeben wurde, soll darunter (in einem extra <div>) eine Warnung angezeigt werden.

ngModel und two-way data binding

Bisher haben wir ngModel nur im Template – mit Hilfe von Templatevariablen – verwendet.

Wir können auch eine Bindung auf eine im .ts-File definierte Variable herstellen:

// app.component.ts
myVar = 'abc';
<!-- app.component.html -->
myVar: <input [(ngModel)]="myVar">

Forms in Angular

Neues Event in Angular: ngSubmit

<form (ngSubmit)="logForm(firstName.value, lastName.value)">
  <input name="fn" ngModel #firstName="ngModel" required>
  <input name="ln" ngModel #lastName="ngModel" required>
  <button>Submit</button>
</form>

Forms in Angular

Neuer Forms-Controller: ngForm (analog zu ngModel für input-Elemente)

<form #f="ngForm" (ngSubmit)="logForm(f.value)">
  <input name="firstName" ngModel>
  <input name="lastName" ngModel>
  <button>Submit</button>
</form>

f.value beinhaltet ein Objekt der Form:

{ "firstName": "John", "lastName": "Smith" }

Form-Attribute: Ãœberblick

<form ngForm …>: fügt Controller zu einem form hinzu (eigentlich automatisch, daher nicht wirklich notwendig)

<input ngModel …>: fügt Controller zu einem input hinzu

<form ngForm #f="ngForm" …>: "exportiert" den Form Controller als Templatevariable

<input ngModel #firstName="ngModel" …>: "exportiert" den Input Controller als Templatevariable

Forms: Beispiele

  • Formular zum Hinzufügen von Todos

  • Formular mit Suchfunktion und two-way data binding

Services

Services allgemein

Im allgemeinen: Services sind Teile einer Angular-Anwendung, die nicht direkt mit dem „Kerngebiet“ von Angular – dem View – zu tun haben.

Beispiele:

  • Datenservice
  • Loggingservice
  • App-Konfiguration
  • Hilfsmethoden

Services haben wenig Angular-spezifischen Code.

Beispiel: Todo-Service

Wir lassen unsere Todos im Hintergrund von einem Service verwalten (und später von einem Server abrufen)

Beispiel: Todo-Service

ng generate service todo

Dieser Befehl erstellt die Klasse TodoService.

Services und Dependency Injection

Services werden in Angular nicht direkt von einer Komponente verwendet, sondern ihr mittels Dependency Injection im Constructor zur Verfügung gestellt. Das erleichtert unter anderem das Schreiben von Tests.

Services und Dependency Injection

Beispiel:

// app.module.ts
import { TodoService } from './todo.service';
[…]
  providers: [TodoService]
[…]
// app.component.ts
constructor(public todoService: TodoService) {
  …
}

Services und Dependency Injection

Wir geben an, dass wir für unsere Komponente eine Instanz der Klasse TodoService benötigen.

Angular erstellt im Hintergrund eine Instanz davon und injiziert diese in jede Komponente, die sie benötigt.

HTTP

HTTP

Verschiedene Möglichkeiten, um http im Browser nutzen zu können:

  • XMLHttpRequest
  • jQuery
  • Fetch

Standard in Angular: httpClient – Modul mit "Observables"

HTTP in Angular: Observables

Observables in Angular: Möglichkeit, asynchron Daten abzufragen – Ähnlich zu Promises.

HTTP in Angular: Observables

this.http
  .get('https://jsonplaceholder.typicode.com/todos')
  // .subscribe() … ähnlich zu .then() bei promises
  // wir senden einen Request, wenn dieser beantwortet wird,
  // wird die Pfeilfunktion aufgerufen und das Resultat
  // unter .todos gespeichert
  .subscribe(response => {
    this.todos = response;
  });

HTTP in Angular: Fehlerbehandlung

Mittels zweitem Argument für subscribe:

this.http.get('...').subscribe(
  response => {
    this.todos = response;
  },
  error => {
    this.error = error;
  }
);

HTTP in Angular: Einbindung

// app.module.ts:
import { HttpClientModule } from '@angular/common/http';
…
imports: […, HttpClientModule]

// my-service.ts:
import { HttpClient } from '@angular/common/http';
…

Beispiel: TODO-Daten vom Server

Wir wollen Daten von https://jsonplaceholder.typicode.com/todos erhalten.

Routing

Routing

Nutzung von HTML5-Routen (client-seitig): https://mywebsite.com/items/28

Früher / für ältere Browser: https://mywebsite.com/#/items/28

Achtung: Server muss entsprechend konfiguriert sein und für /items/28 das gleiche zurückliefern wie für /

Routing - Beispiel

Wir teilen unsere Todo-App in zwei Views auf:

  • Liste aller Todos unter /
  • Formular zum hinzufügen von Todos unter /add

Routing - Grundlagen

Zuweisung von Komponenten zu Routen:

import { RouterModule } from '@angular/router';

imports: [
  ...,
  RouterModule.forRoot([
    { path: '', component: TodoListComponent },
    { path: 'add', component: AddFormComponent }
  ])
],

.forRoot() definiert Routen für die Root-Komponente – im Gegensatz zu .forChild()

Routing - Grundlagen

<router-outlet>:

Die Anzeige der Inhalte erfolgt unterhalb des <router-outlet>-Tags

Routing - Redirects und Wildcards

Beispiel:

  { path: 'home', redirectTo: '' },
  { path: 'add-todo', redirectTo: 'add' },

  { path: '**', redirectTo: '' },

Routing - Links

Der Link

<a href="/add">add Todo</a>

würde zum neu Laden der Seite führen.

Stattdessen verwenden wir:

<a routerLink="/add">add Todo</a>

Routing - Navigation aus TypeScript

Auf den Angular-Router kann in TypeScript zugegriffen werden, um die aktuell aktive Route abzufragen / zu ändern:

// add-form.component.ts
import { Router } from '@angular/router';
[…]
  constructor(public router: Router) { }
    addTodo(todo: {description: string, done: boolean}) {
    this.todoService.addTodo(todo);
    this.router.navigate(['/']);
  }

Routen-Parameter

Wir erstellen neue Routen der Form: /todo/$todoId.
Dort soll jeweils ein einzelnes Todo-Item angezeigt werden

// app.module.ts
[…]
  { path: 'todo/:todoId', component: TodoDetailsComponent },

Routen-Parameter

Die aktive Route bekommen wir über die Klasse ActivatedRoute, die wir mit Dependency Injection initialisieren.

ActivatedRoute.params ist ein Observable mit Routenparametern

// todo-details.component.ts
export class TodoDetailsComponent {
  todoId: string;
  constructor(private route: ActivatedRoute) {
    route.params.subscribe(params => {
      this.todoId = params.todoId;
    });
  }
}

Routing - mehr zum Thema

https://angular.io/guide/router

Material Design - Komponenten

Nutzung vorgefertigter Komponenten

Material - Komponenten

Von Google bereitgestellte Angular-Komponenten im Material design – Stil

Einstieg / Setup: https://material.angular.io – Schritte 1, 2, 4

Einbinden konkreter Komponenten - Schritt 3

Material - Ãœbung

Wir stellen die Todo-App auf Material Design um