Marko Knöbl
Code verfügbar unter: https://github.com/marko-knoebl/courses-code
<todo-item [title]=" 'groceries' " [completed]="false">
</todo-item>
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
ng new playground
Angular CLI installiert im Hintergrund einige Abhängigkeiten und legt diese im Ordner node_modules ab.
Im Projektordner:
ng serve --open
ng new $projectname
: Erstellt neues Angular-Projektng serve
: Startet den Testserverng generate component $name
: Erstellt eine neue Komponenteng generate service $name
: Erstellt ein neues Serviceng build --prod
: Führt einen Production-Build aus (im dist-Ordner)Angular CLI erstellt umfangreiche Projektstruktur
Uns interessiert hauptsächlich der Ordner src/app
Mit angular-cli (ng) erstellte Komponenten gliedern sich in drei Dateien, zB:
Aufgaben
<app-time>
-Komponente, die die aktuelle Uhrzeit anzeigt<app-roulette>
-Komponente, die eine Zufallszahl von 0-36 anzeigtMittels
ng build --prod
führen wir einen Production-Build aus
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;
}
}
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() {
...
}
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';
}
Empfehlung
Dynamisches Setzen von Properties:
<input type="number" [value]="1/3">
const getImgUrl = () =>
'https://picsum.photos/200/300?image=' +
Math.floor(Math.random() * 100);
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)
Optional können wir mit Hilfe der index-Variable mitzählen:
<div *ngFor="let todo of todos; index as i">
{{i}}: {{ todo.title }}
</div>
Beispiel: Rating-Komponente, die Sterne anzeigt (mit zunächst unveränderlicher Anzahl)
Mit *ngIf können wir ein Element unter bestimmten Bedingungen ein- oder ausblenden.
Beispiele:
<div *ngIf="item.importance >= 3">{{ item.text }}</div>
Wir können CSS-Klassen dynamisch zuweisen:
<div [class.important]="isImportant()">…</div>
<span [class.negative]="item.amount < 0">
{{ item.amount }}
</span>
Weitere Möglichkeiten:
<div [ngClass]=
"{important: isImportant(), negative: item.amount < 0}">
...
</div>
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 {..} …']
In Komponentenstilen bezieht sich der besondere :host
-Selektor auf das Komponententag selbst.
:host {
display: block;
}
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>
Beispiel: Wir erstellen ein <div>
-Element, dessen Schriftgröße zufällig festgesetzt wird
Pipe = im wesentlichen eine Funktion, die im Template zur string-Formatierung zur Verfügung steht
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'}}
angular.json:
...targets.build.configurations.de.i18nLocale
= "de"
...targets.serve.configurations.de.browserTarget
= "$projectname:build:de"
im Terminal:
ng serve --configuration=de
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>
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
<button (click)="increase()"> + </button>
<input (keydown)="onKeyDown()">
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;
}
<input (keyup.enter)="onEnter()">
Mit der obigen Syntax können Events auf bestimmte Kategorien beschränkt werden
Properties und Events stellen die wichtigsten Mechanismen dar, mit denen Komponenten in SPAs miteinander interagieren.
In Angular verwendet man auch die Begriffe:
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>
Wir verändern rating.component.ts folgendermaßen:
zunächst importieren wir den Input-Decorator:
import { Input } from '@angular/core';
weiters setzen wir in der Komponentenklasse Typinformationen fest:
@Input() stars: number;
aus dem Template rufen wir die folgende Hilfsmethode auf:
getStarString = () => {
return '*'.repeat(this.stars);
};
Definition eigener Events:
import { Output, EventEmitter} from '@angular/core';
[...]
@Output() tick: EventEmitter<number> =
new EventEmitter<number>();
[...]
this.tick.emit(this.remainingTime);
Timer mit Events: start, tick, end
Nutze diese Events für Beispielnachrichten:
<app-timer
(start)="displayStartMessage()"
(tick)="updateTime($event)"
(end)="displayEndMessage()">
</app-timer>
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>
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
],
Mit Hilfe von ngModel können wir Änderungen an einem Input überwachen lassen
Einfaches (theoretisches) Beispiel mit standard HTML-Attributen und Templatevariablen:
<input ngModel #myInput required minlength="3"><br>
value: {{ myInput.value }} <br>
valid: {{ myInput.validity.valid }}
Ãœ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 }}
<input ngModel #myInput="ngModel" required minlength="3">
Was passiert hier?
#myInput="ngModel"
setzen wir dann eine Variable, die auf das entsprechende Datenmodell verweist.ngModel
) ist fest vorgegeben, den linken Namen (myInput
) können wir selbst bestimmen.Folgende Eigenschaften des ngModel-Controllers können wir überwachen:
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.
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">
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>
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 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
Formular zum Hinzufügen von Todos
Formular mit Suchfunktion und two-way data binding
Im allgemeinen: Services sind Teile einer Angular-Anwendung, die nicht direkt mit dem „Kerngebiet“ von Angular – dem View – zu tun haben.
Beispiele:
Services haben wenig Angular-spezifischen Code.
Wir lassen unsere Todos im Hintergrund von einem Service verwalten (und später von einem Server abrufen)
ng generate service todo
Dieser Befehl erstellt die Klasse TodoService
.
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.
Beispiel:
// app.module.ts
import { TodoService } from './todo.service';
[…]
providers: [TodoService]
[…]
// app.component.ts
constructor(public todoService: TodoService) {
…
}
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.
Verschiedene Möglichkeiten, um http im Browser nutzen zu können:
Standard in Angular: httpClient – Modul mit "Observables"
Observables in Angular: Möglichkeit, asynchron Daten abzufragen – Ähnlich zu Promises.
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;
});
Mittels zweitem Argument für subscribe
:
this.http.get('...').subscribe(
response => {
this.todos = response;
},
error => {
this.error = error;
}
);
// app.module.ts:
import { HttpClientModule } from '@angular/common/http';
…
imports: […, HttpClientModule]
// my-service.ts:
import { HttpClient } from '@angular/common/http';
…
Wir wollen Daten von https://jsonplaceholder.typicode.com/todos erhalten.
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 /
Wir teilen unsere Todo-App in zwei Views auf:
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()
<router-outlet>
:
Die Anzeige der Inhalte erfolgt unterhalb des <router-outlet>
-Tags
Beispiel:
{ path: 'home', redirectTo: '' },
{ path: 'add-todo', redirectTo: 'add' },
{ path: '**', redirectTo: '' },
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>
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(['/']);
}
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 },
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;
});
}
}
Nutzung vorgefertigter 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
Wir stellen die Todo-App auf Material Design um