Angular Training
Rafał Brzoska
Bottega IT Minds
Rafał Brzoska
- Senior Software Developer @ Future Processing
- Angular Trainer @ FutureProcessing & Bottega IT Minds
- Speaker (ngPoland, 4developers, FDD)
- Angular, JS, TS
Agenda
-
Wprowadzenie
-
Komponenty, Szablony komponentów,
-
Dyrektywy wbudowane
-
Pipes (filtry)
-
Routing
-
Serwisy
-
HttpClient
-
RxJS
-
Formularze( Template driven, Reactive )
-
Dependency Injection
-
Moduły (NgModule) i architektura
-
Testy
-
Własne dyrektywy i dynamiczne komponenty
-
Zarządzanie stanem
-
Biblioteki
- Imię i nazwisko
- Aktualnie pracuję na stanowisku … i zajmuję się …
- Mam doświadczenie w …
- Moja znajomość zagadnień z agendy szkolenia …
- Spodziewam się, że w trakcie szkolenia …
{ będę przeprowadzony/na | będę podejmować decyzje |
będziemy uczyć się od siebie wzajemnie w grupie | będziemy konfrontować podejścia |
będziemy „układać puzzle” | będziemy „budować z klocków ” } - Oczekuję, że po szkoleniu …
{ posiądę metody działania | pojawi się wiele nowych pomysłów |
posiądę solidne podstawy } - Oczekuję od prowadzącego...
{ będzie towarzyszem | będzie ukazywał możliwości | będzie umożliwiał wgląd |
będzie podawał przykłady | będzie pomagał w odkryciach |
będzie pomagał coś stworzyć | będzie prowadził Eksperyment } - Moje główne pytanie: Jak? Co? Po co? Kiedy? Dlaczego?
Wprowadzenie
- Angular 1.x => AngularJS
- Angular 2+ => Angular
- Semantic Versioning
- TypeScript
- RxJS
- Change Detection (Zone.js)
- Komponenty
- Ogromny ekosystem modułów utrzymywany przez Google
- Angular CLI (Schematics!!)
- Wsparcie dla PWA, SSR, Mobile (NativeScript), WebWorkers, Material, CDK
Typescript
- Typowanie
- Inferencja typów
- ES6...ESNext
- Rozszerzone Klasy
- Dekoratory
- Wsparcie IDE
Typy podstawowe
- string
- boolean
- number
- array
- enum
- tuple
- any
- void
- null i undefined
- never
Typy zaawansowane
- interfejsy
- kombinacje typów
- typy generyczne
- inferencja
- duck typing
- type guards
Interface
interface MyCustomType {
name: string;
}
interface TestType {
testNum: number;
testType: MyCustomType;
}
interface ClassInterface {
myFn(): void;
mySecondFunction(num: number): string;
}
Class
class Greeter {
greeting: string;
constructor(message: string, private userId: number) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world", 1);
Type
type C = { a: string, b?: number }
type stringOrNumber = string | number // Union Type
Generics
function myGenericFunction<T>(arg: T): T {
return arg;
}
class CustomList<T> {
private list: T[];
push(element: T) { list.push(element) };
getELements(): T[] {
return this.list;
}
}
Angular CLI
- Schematics
- Aplikacje
- Biblioteki
- Preprocesory CSS
- AOT
- Inteligentne generowanie kodu
- ...
Angular CLI
- ng new
- ng generate
- ng serve
- ng build
- ng test
- ng add
- ng update
- ng deploy
ng generate
- component (c)
- directive (d)
- service (s)
- module (m)
- guard (g)
- pipe, class, interface ...
--inline-style
--inline-template
--no-spec
--flat
--routing
Budowa Aplikacji
Drzewo Komponentów
Root
S
D
D
Komponent
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
@Component
...
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
// template: '<div></div>',
// styles: [`.btn { color: red }`],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
providers: [DataService]
})
...
NgModule
@NgModule({
declarations: [
AppComponent,
HomeComponent
],
imports: [
BrowserModule,
RouterModule.forRoot(routes),
providers: [MyService],
bootstrap: [AppComponent]
})
export class AppModule { }
Routing
- Root Routes, Child Routes
- Route (path, component, redirect, data)
- <router-outlet>
- Router Links
- Router Services (ActivatedRoute, ActivatedRouteSnapshot)
- Guards (CanActivate, CanDeactivate, CanLoad, Resolve…)
- Lazy Loading (loadChildren: lazy loaded module)
Routing
const routes: Routes = [
{ path: 'home', component: HomeComponent }
];
...
imports: [ RouterModule.forRoot(routes) ],
<nav>
<a routerLink="/">Home</a>
<a routerLink="/kontakt">Kontakt</a>
<a [routerLink]="['/about']">About</a>
</nav>
<router-outlet></router-outlet>
AppModule
AppComponent/NavigationComponent
Szablony
<p> {{ title }} </p>
<my-component [myInput]="inputData"></my-component>
<my-component (myOutput)="outputHandler($event)"></my-component>
<h1 #myTemplateReference></h1>
Interpolacja
Input - property binding
Output - event binding
template reference variable
Input/Output
@Component({
selector: 'child-component',
template: `
<h3>{{user.name}} says:</h3>
<p>I, {{user.name}}, am at your service, {{masterName}}.</p>
<button (click)="handleClick()">Say Hello</button>
`
})
export class ChildComponent {
@Input() user: User;
@Input('master') masterName: string;
@Output() sayHello = new EventEmitter<string>();
handleClick() {
this.sayHello.emit(this.user.name)
}
}
ChildComponent
Input/Output
@Component({
selector: 'ParentComponent',
template: `
<child-component
[user]="user"
[master]="master"
(sayHello)="handleSayHello($event)">
</child-component>
`
})
export class ParentComponent {
user = { name: 'Alojzy' };
master = 'Majster';
handleSayHello(name: string) { alert(name) }
}
ParentComponent
Lifecycle Hooks
Dyrektywy
- ngIf
- ngFor
- ngClass
- ngSwitch
ngIf
<div *ngIf="show; else elseBlock">Pokaz to jesli show = true</div>
<ng-template #elseBlock>Pokaż jesli show = false</ng-template>
<div *ngIf="warunek">...</div>
<ng-template [ngIf]="warunek"><div>...</div></ng-template>
*ngIf
[ngIf]
* przy dyrektywie dodaje <ng-template> i upraszcza notacje
ngFor
<ul>
<li *ngFor="let user of users">{{user.name}}</li>
</ul>
<ng-template ngFor let-user
[ngForOf]="users"
[ngForTrackBy]="trackById">
<div {{user.name}}</div>
</ng-template>
* przy dyrektywie dodaje <ng-template> i upraszcza notacje
ngClass
<element [ngClass]="'className1 className2'">...</element>
<element [ngClass]="['className1', 'className2']">...</element>
<element
[ngClass]="{'className1 ': true, 'className2 ': true, 'className3 ': false}">
...
</element>
<div [class.isOnline]="isOnline">Status</div>
<div [style.color]="isOnline ? 'red' : 'green'">Status</div>
Attribute binding
ngSwitch
<container [ngSwitch]="myNumber">
<element *ngSwitchCase="1"> jeden </element>
<element *ngSwitchCase="2"> dwa </element>
<element *ngSwitchDefault>jakiś numer</element>
</container>
Pipe
- AsyncPipe
- DatePipe
- JsonPipe
- LowerCasePipe, UpperCasePipe, TitleCasePipe, SlicePipe
- CurrencyPipe, DecimalPipe, PercentPipe
- ...
<pre> {{ myObject | json }} </pre>
<p> {{ jakasData | date | uppercase }} </p>
<p> {{ myNumber | currency:'USD' }} </p>
Serwisy
- Dekorator @Injectable
- Domyślnie jako Singleton
- Globalny dla całej aplikacji (providedIn: 'root')
- Konieczny wpis do 'providers' w NgModule
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class UserService {
}
Serwisy
class MyComponent {
users: User[];
constructor(private userService: UserService) { }
someFunction() { this.users = this.userService.getUsers() }
}
HttpClient
// import in AppModule
import: [... HttpClientModule]
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class MyDataService {
constructor(private http: HttpClient) { }
getMyData() {
this.http.get('http://url...')
}
}
AppModule
Data Service
RxJS
- Observable, Observer, Subject
- Obsługuje Strumień danych
- Cancel, Retry
- fromEvent, fromPromise, toPromise
- Reactive Forms
- Angular HTTP Client
- Cold vs Hot Observable
- Ponad 100 operatorów
Observable
Funkcja która przekazuje observerowi dane ze strumienia
let myObservable = new Observable( observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
});
i udostępnia metodę subscribe w której przekazujemy observera
myObservable.subscribe(observer)
Observer
Obiekt posiadający 3 metody: next, complete, error
myObservable.subscribe({
next: () => ... ,
error: () => ... ,
complete: () => ...
});
lub poprostu zestaw tych metod podawanych osobno
myObservable.subscribe(
() => ..., // next
() => ..., // error
() => ... // complete
);
Subject
Jest Observablem
let mySubject = new Subject();
mySubject.subscribe(observer);
i jednocześnie jest observerem
mySubject.next(1);
mySubject.complete();
mySubject.error(error);
Cold vs Hot Observable
Cold - Observable 'produkuje' dane i przy każdym subscribe podaje ten sam strumień
new Observable( observer => {
observer.next(1);
observer.next(2);
});
Hot - Observable reaguje na zewnętrzne źródło danych (np Event) i zamienia go w strumień
let element = document.getElementById('myButton');
const myHotObservable = new Observable(obs =>
element.addEventListener('click', e => obs.next(e))
)
myHotObservable.subscribe(x => console.log(x));
Cold vs Hot Observable
Cold - generuje dane tylko po subscribe
Hot - generuje dane bez względu na to czy ktoś je zasubskrybował czy nie
Cold - może mieć jednocześnie tylko jednego observera i jeden subscribe (unicast)
Hot - może mieć kilku observerow i kilka subscribe (multicast)
Angular Observable
- httpClient
- Router.events
- AbstractControl.valueChanges
- ActivatedRoute.params
- EventEmitter (@Output)
Formularze
- Template Driven Forms
- Reactive Forms
Template Driven Forms
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
BrowserModule,
FormsModule // <-- import
],
....
})
export class AppModule { }
Template Driven Forms
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
<input name="myName" ngModel required #myName="ngModel">
<input name="age" ngModel>
<button>Submit</button>
</form>
<p>Name value: {{ myName.value }}</p>
<p>Name valid: {{ myName.valid }}</p>
<p>Form value: {{ myForm.value | json }}</p>
<p>Form valid: {{ myForm.valid }}</p>
Szablon + dyrektywa NgForm
Reactive Forms
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule // <-- import
],
....
})
export class AppModule { }
Reactive Forms
export class UserFormComponent {
userForm = new FormGroup({
myName: new FormControl(''),
age: new FormControl(''),
});
}
FormControl & FormGroup
export class UserFormComponent {
constructor(private fb: FormBuilder){}
userForm = fb.group({
myName: '',
age: '',
});
}
FormBuilder
Reactive Forms
<form [formGroup]="userForm">
<label>
Name:
<input type="text" formControlName="myName">
</label>
<label>
Age:
<input type="number" formControlName="age">
</label>
</form>
Szablon
NgModule
Feature Module | Declarations | Providers | Exports | Imported by |
---|---|---|---|---|
Domain | Yes | Rare | Top component | Feature, AppModule |
Routed (Lazy Loaded) | Yes | Rare | No | None |
Routing | No | Yes (Guards) | RouterModule | Feature (for routing) |
Service (Core) | No | Yes | No | AppModule |
Shared (Widget) | Yes | Rare | Yes | Feature |
NgModule
Module Type | Declarations | Providers | Exports | Imported by |
---|---|---|---|---|
Domain (Feature) | Yes | Rare | Top component | Feature, AppModule |
Routed (Lazy Loaded Feature) | Yes | Rare | No | None |
Routing | No | Yes (Guards) | RouterModule | Feature (for routing) |
Service (Core) | No | Yes | No | AppModule |
Shared (Widget) | Yes | Rare | Yes | Feature |
Podstawy Angulara
By Rafał Brzoska
Podstawy Angulara
- 159