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

  • 152