Angular

Stránka zachycuje zásadní teorie pro další práci JavaScriptovou platformou Angular. Vychází z oficiální dokumentace Angular, která je také zdrojem případných obrázků. Zabývá se architekturou a hlavními konstrukty této platformy. Předpoklád základní znalosti objektově orientovaného programování, HTML a CSS a JavaScriptu.

Instalace Angular

  • Vyžaduje Node.js, pro instalaci a správu verzí je vhodné použít správce npm:

sudo chown -R $USER:$(id -gn $USER) /home/kosik/.config/

  • nvm umožňuje instalovat aktuální Node.js (např. instalace aktuální LTS verze nvm install --lts), spravovat více verzí a přepínat mezi nimi (např. na LTS přepneme nvm use --lts)
  • Použitou verzi Node.js lze ověřit node --version 
  • Nyní používám Node v10 (LTS)

npm install -g @angular/cli

  • npm vyřeší závislosti
  • popř. lze určit konkrétní verzi instalovaného Angularu:

npm install -g @angular/cli@7.3.8

  • Nyní používám Angular 7.3.8

Základ Angular CLI

  • Nápověda ng help 
  • Časté příkazy:
  • ng new <nazev> (vytvoří nový projekt v dané složce, zadat lze routing a jazyk pro stylování css/less apod.)
  • ng serve (sestaví a spustí projekt v dané složce s výchozí adresou a portem http://localhost:4200/)
  • ng lint (kontroluje kvalitu kódu, pravidla jsou definována v tslint.json)
  • ng build (vygeneruje adresář dist pro deploy na web server)
  • ng test (spustí unit testy, výchozí unit test engine je Karma, výsledek se zobrazí v prohlížeči)
  • soubory s koncovkou .spec.ts ??
  • ng e2e (spustí end to end testy založené na Seleniu)

Základní struktura Angular projektu

  • Běžná struktura projektu vygenerovaného přes Angular CLI:
  • Hlavní adresáře: dist (výsledek buildu určený pro deploy), e2e (selenium testy), src (zdrojové kódy)
  • Obsah src: app (soubory aplikace - Angular komponenty, moduly apod.), asset (zdroje, které se překopírují tak jak jsou), environments (k definici různých prostředí)
  • Důležité soubory ve workspace: angular.json s výchozí CLI konfigurací pro celý workspace (mj. určuje unit test engine apod.),  tsconfig.json zajistí kanonizaci TypeScript na JS (popř. potomci tsconfig.*.json), tslint.json s konfigurací pro kontrolu kvality kódu, README.md s dokumentací, package.json definuje npm závislosti k dotažení (obdoba mvn).
  • Důležité soubory v projektu:
  • pro “bootstrap” aplikace: index.html (vstupní stránka) a main.ts (hlavní vstupní bod při spuštění aplikace)
  • Další důležité soubory: polyfills.ts (zajišťuje kompatibilitu aplikace mezi prohlížeči), styles.css (globální styly aplikace, koncovka se liší podle jazyka pro styl zvoleného  při vytvoření projektu, např. styles.less), test.ts (hlavní vstupní bod pro unit testy)

Angular architektura

  • Původnímu AngularJS byl založen na MVC. Angular2 (a vyšší) je založen na komponentách a místo čistého JavaScriptu využívá TypeScript.
  • Základem jsou komponenty (components) a služby (servises).
  • Jde o TypeScript třídy označené pomocí decoraters (obdoba anotací v Javě), které umožňují Angularu specifikovat, jak se k těmto třídám chovat.
  • Z komponent se skládá view. Component je třída definující data a logiku. Třída komponenty je asociována s šablonou (template) definující samotné view. Šablona je HTML + Angular markup.
  • Služby zpřístupňují další data a logiku pro jednu nebo více komponent a využívají k tomu denpendency injection.
  • Angular je modulární - umožňuje členění pomocí NgModules a poskytuje compilation context.
  • Více viz https://angular.io/guide/architecture 

TypeScript

Př.:

export class Hero {

  constructor(

    public id: number,

    public name: string) { }

}

Atributy třídy lze deklarovat výše uvedeným způsobem v konstruktoru - deklarujeme tím současně parametry konstruktoru + veřejné atributy (properties).

Moduly

Angular moduly (NgModul) slouží k členění kódu (obdobně jako package v Javě).

NgModules vs. JavaScript module

NgModulu koexistují s klasickými  JavaScript moduly.

  • JS moduly jsou jednodušší koncept, který pouze umožňuje export určitého kódu (třídy) tak, aby mohl být vyčleněn do samostatného souboru a mohl být následně importována jinam.
  • NgModule v Angularu jsou komplexnější než JS module. Viz níže.
  • V Angularu se kombinují s NgModuly (viz příklad níže).

NgModules

  • K definici modulu se používá decorater @NgModule 
  • Konvence pro název souboru .module.ts
  • V aplikaci je vždy alespoň jeden modul - tzv. kořenový modul app.module.ts
  • NgModule umožňuje organizovat namespace z hlediska kompilátoru a závislosti pro dependenci injection
  • Metadata v NgModule:
  • declarations - k deklarování závislostí (třídy components, pipe a directives) používané v tomto modulu; účelem deklarace (chyběla v AngularJS) je možnost injektovat (např. použít komponentu v HTML) aniž by hrozil konflikt (např. u rozsáhlých projektů nebo při kombinování knihoven)
  • providers - k definování singleton služeb (services) viditelných v celé aplikaci (vs. pokud to není potřeba, tak vytváříme služby v komponentách a nikoliv globální)
  • exports - definuje viditelné “public” třídy tohoto modulu, tj. definovat API použitelné v jiném modulu; jde o podmnožinu declarations
  • imports - importuje jiné moduly a zpřístupnit tak jejich exportované třídy
  • bootstrap - slouží k definování root komponenty (entry point) tohoto modulu, která slouží jako application view hostující všechna ostatní view; bootstrap je obvykle definován v root modul, další moduly nemusí mít žádný entry point.

Př::

import { NgModule }          from '@angular/core';   //import JS modulu

import { BrowserModule } from '@angular/platform-browser';  //import JS modulu

@NgModule({

  //metadata:

  imports:          [ BrowserModule ],

  //jiné moduly jejichž třídy jsou v tomto modulu používány

  providers:    [ Logger ],

  //je zkráceným zápisem pro:

  // providers: [provide(Logger, { useClass: Logger })]

  //vytváří globální services (ale ve většině případů je lépe specifikovat service na úrovni komponent)

  declarations: [ AppComponent ], 

  //deklaruje třídy (components, directives, pipes) tohoto modulu

  exports:      [ AppComponent ],

  //podmnožina declarations viditelná vně modul

  bootstrap:    [ AppComponent ]

  //hlavní view aplikace, tzv. root component, hostuje všechna ostatní view, nachází se pouze v kořenovém modulu

})

export class AppModule { }

Declarations vs. provides:

  • Deklaruje množinu tříd, které jsou součástí jednoho logického celku, kterým je NgModule. Utváří tak hranici deklarovaných tříd (pouze tyto jsou relevantní pro Angular kompilátor).
  • NgModule může deklarovat pouze své vlastní třídy nebo třídy, které sám importuje.
  • Umožňuje zabránit konfliktu stejnojmenných tříd (např. komponent) mezi moduly.
  • Třída by měla být v aplikaci deklarována jen jednou.
  • Deklarovaná třída je viditelná pouze v daném modulu.
  • Pokud má být třída “public”, protože je potřeba mimo modul, pak musí být exportována (metamodel exports) a v každém modulu, kde je potřeba importovat její modul (metamodel imports).
  • např.: CommonModule importujeme skoro všude (kromě root modulu, kde je již součástí BrowserModule), protože obsahuje základ pro Angular templates. Obdobně UIModules (slouží k tvorbě forms apod.) bude ve všech modulech prezentujících data.
  • NgModule umožňuje vložení globální service do celé aplikace
  • Oproti deklarovaným třídám má service globální scope(!???) a lze injektovat kdekoliv v aplikaci (v jiných modulech) aniž bychom znovu specifikovali providera, stačí jen importovat příslušný NgModule.
  • Jedná se o koncept singleton, a proto by service měla být v celé aplikaci definována jen jednou (jinak nastává problém s lazy-loading), proto se znovu neimportuje
  • např. HttpClientModule importujeme v aplikaci jen jednou, protože poskytuje pouze služby. Obdobně GlobalServicesModules (pro HTTP, auth,...) nebo AppRoutingModule (pro směrování HTTP requestů)

Moduly poskytují komponentám compilation context.

Více viz

V začátcích pozor na:

If you want to use a component in another module, make sure you add it to declarations and exports before you put it in imports (this bit me several times).

Second, you can find the common directives like NgIf, NgFor, etc. by import { CommonModule } from '@angular/common'. This happens by default when using BrowserModule but if you're writing tests you may not import BrowserModule and thus you'd get an error.

The advice generalizes to: make sure you understand the module dependency flow, especially when testing. Failure to import modules properly will result in failing tests (and the error messages aren't always clear).

Angular komponenty

  • Angular stránka se skládá z views
  • View je dáno TypeScript třídou komponenty (component.ts), která definuje chování a data (methods + properties) a je svázána s šablonou (.component.html) + CSS style (.component.css).
  • HTML šablona je HTML + Angular template syntax umožňující promítnout do šablony logiku a stav.
  • V šabloně může využívat:
  • data binding (různé způsoby mapování popsané níže)
  • pipes k transformaci dat před zobrazení (např. lokalizace)
  • directives k implementaci logiky, např. iterování pomocí direktivy *ngFor
  • Šablona a style lze psát do samostatného souboru (metadata templateUrl, resp. styleUrl) nebo přímo do komponenty (uvnitř metadat šablony, resp. style).
  • Použití styles and styleUrls lze kombinovat.
  • Angular zajistí, že styl komponenty  scope pouze pro komponentu, kde je definován. Nedotkne se ani potomků (Angular zajistí, že CSS třídy a selektory mají kontext jen v dané komponentě, a to i v případě použití obecných selektorů, např. pro div).
  • Chování lze měnit pomocí metadat encapsulation

 https://angular.io/guide/component-styles#view-encapsulation 

  • Pro globální stylování lze použít styl jako v každé jiné webové aplikaci. Lze používat tagy <style> a <link>. Pokud jsou definovány externí styly je potřeba o nich říct CLI, aby byly zahrnuty pro nasazení.
  • Jedinou výjimkou jak cílit předka je použití pseudo-class sektoru :host, do závorky za něj lze uvést omezení na konkrétní CSS třídu, př.: :host(.active) {  border-width: 3px; }
  • Styl komponenty můžeme chtít omezit na případ, kdy je zabalena do předka (v libovolné hloubce) určité CSS třídy. To lze provést pomocí selektoru :host-context , př.: :host-context(.theme-light) { background-color: #eef; }
  • Nedoporučuje se potlačovat omezení scopu stylu, ale lze provést pomocí selektoru /deep/ (s aliasy >>> a ::ng-deep). Selektor je depreceted a může být brzy odstraněn.
  • Více viz https://angular.io/guide/component-styles 
  • Kostru všech potřebných souborů (component.ts, component.spec.ts, .component.html, .component.css). nové komponenty lze jednoduše vygenerovat vč. jejího přidání do modulu naráz za pomocí Angular CLI :

ng generate component <nazevKomponenty>

nebo zkráceně

ng g c <nazevKomponenty>

za <nazevKomponenty> se automaticky doplní Component

Komponenty - component

K definování komponenty slouží soubor .ts, který označuje decorater @Component

Metadata komponenty:

  • selector - element (název tagu) pomocí něhož je referencována tato komponenta v HTML

Pozn.: Angular při výskytu v HTML vytvoří instanci komponenty a použije ji uvnitř elementu

  • templateUrl / template - odkaz na soubor s HTML šablonou nebo “inline” přímo samotné HTML
  • Pozn.: Více řádkový inline text lze zapsat uvnitř backticks (`) dle specifikace ECMAScript 2015
  • styleUrl / style - odkaz na soubor se styly nebo přímo styly dané komponenty  
  • selector - lokální services vyžadované komponentou

Za decorator @Component s metadaty následuje třída s metodami a properties.

Př.:

@Component({

  selector:    'mytag',

//tag pro použití této komponenty v HTML

  templateUrl: './mytag.component.html', 

  //umístění šablony s HTML

  styleUrls: './mytag.component.css',

  //umístění stylu

})

export class MytagComponent { //data binding - umožňuje definovat data

  title = ‘test’ //proměnná, kterou lze použít z HTML {{ title }}

}

Šablony - template

Součástí komponenty je HTML šablona určující uživatelský pohled.

  • Lze v ní použít téměř vše z HTML kromě tagu <script> (z bezpečnostních důvodů). Příliš smysl nemají tagy <html>, <body> a <base>.

Př. šablony hero-list.component.html:

<h2>Hero List</h2>

<p><i>Pick a hero from the list</i></p>

<ul>

  <li *ngFor="let hero of heroes[a]" (click)="selectHero(hero)">

    {{hero.name}}

  </li>

</ul>

<app-hero-detail *ngIf="requiredHero" [hero]="requiredHero"></app-hero-detail>

  • Pomocí direktivy *ngFor říkáme Angularu, aby iteroval nad kolekcí.
  • Hvězdička je nezbytnou součástí syntaxe!!
  • V uvozovkách následuje za direktivou tzv. microsyntax
  • {{hero.name}}, (click), and [hero] mapují data požadovaného vstupu do/ DOM.
  • <app-hero-detail> element reprezentuje komponentu HeroDetailComponent. Definuje potomka hero-detail child view ke komponentě (HeroListComponent). Elementy komponent se v Angularu objevují spolu s běžným HTML.

View jsou uspořádána hierarchicky (jedno view “hostuje”  další vnořená view tzv. embeded views).

V rámci této hierarchie lze míchat komponenty z různých modulů.

Více o komponentách viz

Data binding

Mapování zajišťuje vzájemnou komunikaci mezi šablonou a komponentou, ale také v hierarchii komponent. Angular umožňuje mapování a push/pull hodnot v obou směrech mezi HTML a aplikací.

Angular vyhodnotí všechna mapování dat jednou za “JavaScript event cycle” v celém stromu komponent, a to směrem od kořenové komponenty k vnořeným potomkům.

Angular nabízí 4 způsoby mapování dat:

  1. Tzv. interpolation

Př. <h1>{{title}}</h1>

  1. Přiřazení hodnoty, tzv. property binding
  • Slouží k předání do proměnné vnořené komponenty nebo direktivy ale také k přiřazení hodnoty do vlastnosti (property) elementu v DOM stromu (čímž umožní změnit chování oproti výchozí hodnotě atributu HTML elementu)[1] nebo určitého stylu či třídy (a výjimečně přímo atributu HTML elementu - viz níže).
  • Předání hodnoty komponentě umožňuje komunikaci mezi nimi.
  • Při volání v šabloně se hodnota umístí do hranatých závorek [] nebo lze označit prefixem bind-
  • [target]="expression"
  • bind-target="expression"

Př. předání hodnoty requiredHero z rodičovské komponenty do property hero v dceřiné komponentě HeroDetailComponent:

 <app-hero-detail [hero]="currentHero"></app-hero-detail>

Př. předání hodnoty do property elementu v DOM:

 <img [src]="heroImageUrl">

Př. předání hodnoty do property elementu v DOM:

 <button [disabled]="isUnchanged">Save</button>

  • V případě stylu, třídy a atributu provádíme property binding přidáním prefix style., class., attr.
  • [style.nazev_atributu]="expression"
  • [class.nazev_stylu]="expression"
  • [attr.nazev_tridy]="expression"

style. Angular umožňuje měnit hodnotu vlastnosti určitého CSS stylu.

Př.  <button [style.color]="isSpecial ? 'red' : 'green'">

V případě rozměru přidáme suffix jednotky.

Př.  <button [style.font-size.em]="isBig ? 3 : 1" >Big</button>

Př.  <button [style.font-size.%]="!isMy ? 150 : 50" >Small</button>

Pro snadnou manipulaci s více třídami existuje direktiva NgStyle

class. Třídu lze nastavit přes vlastnost v DOM. Angular navíc umožňuje snadno (dynamicky) konkrétní jednu třídu přidat nebo odebrat v případě, že je rovna výrazu true nebo false.

Př.  <div class="visible" [class.visible]="isVisible">...</div>

Pro snadnější manipulaci s více třídami existuje direktiva NgClass.

attr. Upřednostňujeme property v DOM, ale ne vždy existuje, a tak může být potřeba předat hodnotu přímo atributu HTML elementu, např. colspan pro sloupce tabulky), SVG, speciální atributy pro nevidomé ARIA.

Př.  <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>

Důležité vlastnosti property binding a interpolation
  • Jsou jednosměrné! Nelze číst hodnotu cílové proměnné.
  • Angular u nich provádí sanitizaci, a tak se například provede escape <script> a neprovede se.
  • Property binding lze používat místo interpolation, ta je ale mnohdy čitelnější. Naopak nelze, protože interpolace neumožňuje přiřazení, pouze dosadí řetězec vyhodnoceného výrazu.

Př.: <span>"{{test}}"</span> ~ <span [innerHTML]="test">

  1. Mapování metody na událost, tzv. event binding 
  • Umožňuje naslouchat určité události a reagovat na ni (nastaví event handler, který při události provede statement v šabloně), např. předá vstupní hodnotu z HTML do modelu.
  • Při volání v šabloně se hodnota umístí do kulatých závorek () nebo lze označit prefixem on-
  • (target_event_name)="statement"
  • on-target_event_name="statement"

Př. zavolání metody komponenty při kliknutí na element

 <button (click)="onSave()">Save</button>

Př. předání události dceřiné komponentě:

 <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>

  • Mapování události umožňuje předává informace o události  (hodnotu/objekt/řetězec) pod názvem $event. Často jde o nativní DOM event object s vlastnostmi jako target.value.

Př. mapování události a předání jejího objektu:

<input [value]="currentItem.name"        (input)="currentItem.name=$event.target.value" >

U objektu $event se neprovádí typová kontrola při kompilaci (zda existují volané vlastnosti), pokud neprovedeme typování explicitně.

Předávat metodě celý objektu $event je proti filozofii architektury Angularu, protože obsah a jeho podoba závisí na HTML elementu, čímž je narušeno oddělení šablony (vizuální části) od komponenty (zpracování). Proto je lépe nepředávat celý $event, ale jen potřebné jeho vlastnosti. Obvykle nepoužíváme $event ale snadnější mapování přes Template reference variable (viz níže).  

this.values += (<HTMLInputElement>event.target).value;

  • Mapování událostí z dceřiné do nadřazené komponenty je umožněno pomocí EventEmitter.

Například umístíme tlačítko delete na komponentu s detailem objektu z listu rodičovské komponenty. Dceřiná komponenta neví nic o listu v rodiči a událost je potřeba “emitovat”:

  • V šabloně dceřiné komponenty mapujeme událost na metodu delete() její komponenty.
  • Dceřiné komponentě definujeme vlastnost deleteRequest typu EventEmiter a v metodě delete() zavoláme na objektu metodu emit (a předáme potřebné parametry z potomka).
  • Při použití v rodičovské komponentě deleteRequest poslouží jako direktiva události, které můžeme namapovat potřebnou metodu rodičovské komponenty.

Př.:

src/app/item-detail/item-detail.component.html (šablona)  

  <span [style.text-decoration]="lineThrough">{{ item.name }}</span>

  <button (click)="delete()">Delete</button> 

src/app/item-detail/item-detail.component.ts (deleteRequest)

  // This component makes a request but it can't actually delete a hero.

  @Output() deleteRequest = new EventEmitter<Item>();

  delete() {

      this.deleteRequest.emit(this.item.name);

      this.lineThrough = this.lineThrough ? '' : 'line-through';

  }

src/app/app.component.html (event-binding-to-component)

  <app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>

  1. Obousměrné mapování (two-way data binding)

kombinuje property binding a event binding v jedné notaci. Používá se především v tzv. template-driven forms, kdy se komponentě předá hodnota z property a následně může být změněna událostí.

  • Syntax odpovídá kombinaci property a event binding, pro které je jen jakousi syntaktickou zkratkou. v šabloně se cíl mapování umístí do hranatých a kulatých závorek [()] tzv. “banán v krabici” nebo lze označit prefixem bindon-
  • [(target)]="expression"
  • bindon-target="expression"                           
  • V případě, že dodržíme konvenci, kdy ve vnitřní komponentě vstupní hodnotu pojmenujeme x a událost (výstupní EventEmiter) xChange, pak je v rodičovské šabloně můžeme jednoduše obousměrně mapovat [(x)].

Př. dceřiné komponenty se vstupní hodnotou emitovanou do rodičovské šablony:

  export class ChildComponent{

    @Input() amount: number;

    @Output() amountChange = new EventEmitter();

    withdraw(){

         this.amount -= 100;

        this.amountChange.emit(this.amount);

    }

  }

Použití s obousměrným mapováním v rodičovské komponentě:

  <child [(amount)]="amount"> </child>

  • Formulářové HTML elementy (např. input) se neřídí žádným podobným vzorem (hodnota x s událostí xChange). Angular proto poskytuje direktivu NgModel, která obousměrné mapování zprostředkuje a usnadňuje práci s formuláři (mj. zbavuje potřeby znát jak property příslušné události, tak hodnoty elementu).

Př. obousměrného mapování s využitím direktivy ngModel:

<input [(ngModel)]="hero.name">

je totéž jako  

<input [ngModel]="hero.name" (ngModel)="hero.name = $event.target.value">

je totéž jako  

<input [value]="hero.name" (input)="hero.name = $event.target.value">

  • Použití NgModul direktivy vyžaduje import modulu FormsModule. Direktivu lze použít pro elementy podporující ControlValueAccessor – Angular již poskytuje podporu pro většinu základních formulářových elementů, existují další implementace třetích stran nebo lze vytvářet vlastní (případně místo NgModulu implementovat jiné obousměrné mapování).
  • [(ngModel)] umožňuje pouze mapovat hodnotu v obou směrech bez dalšího specifického chování, pokud je například potřeba modifikovat hodnotu, pak je potřeba použít (ngModelChange) a [ngModel].

Př. změny vstupní hodnoty do majuskule: 

<input

  [ngModel]="currentHero.name"

  (ngModelChange)="setUppercaseName($event)">

Template expression

Př.: <li *ngFor="let customer of customers">{{customer.name}}</li>

Př.:  <input #customerInput>{{customerInput.value}

Výraz může obsahovat: aritmetické operace (+, -,...); přiřazení (=, +=,..), zřetězení výrazů (; a ,), de/inkrementování (++ a --), operátory jako new, typeof, instanceof  z JS, ale nikoli logické a bitové operátory.

Kontext pro vyhodnocení výrazu je typicky dán příslušnou třídou komponenty (některou její proměnnou) nebo je omezen na direktivu uvnitř šablony (např. proměnná customer v ngFor cyklu) nebo proměnnou uvnitř šablony (pomocí # syntaxe, např. customerInput) .

Při kolizi má přednost proměnná šablony > kontext direktivy > proměnná komponenty.

Z výrazů v šabloně nelze odkazovat globální proměnné, a to ani window, document nebo console.log().

Je dobré pamatovat na:

  • Angular sám je takto navržen. Záměrnou výjimkou, kterou Angular sám porušuje. je *ngFor with trackBy, která umožňuje přidávat/odebírat prvky v kolekce pomocí unikátního indexu a nikoli reference na objekt. Důvodem je drahá operace přidání a odebrání prvku v kolekci, protože vyvolá manipulaci s DOM.
  • Quick execution (popř. drahé výpočtu cachovat)
  • Simplicity (spíš než v šabloně vyhodnocovat výrazy v komponentě, kde se mj. lépe testuje)
  • Provádíme-li přiřazení, pak je potřeba dodržet typ (řetězec, číslo, objekt apod.)
  • Nevynechat závorky nebo prefix cílové proměnné. Angularu říkají, že se výraz má vyhodnotit,  jinak je výraz považován rovnou za textový řetězec.
  • Naopak nepoužít závorky nebo prefix, pokud chceme cílové proměnné předat neměnný řetězec, aby se řetězec opakovaně neinicializoval, když není potřeba.
Template statement

Př.: <button (click)="deleteHero()">Delete hero</button>

Příkaz (pravá strana za rovnítkem v event binding), který reaguje na událost. Oproti template expression je často jejich účelem modifikace dat. Povoleno je pouze základní přiřazení (=) a řetězení (; a ,). Další operace, které lze použít pro template expresion (vč. přiřazení s inkrementací/dekrementací) použít nelze. V obou případech nelze použít bitové operace.

Výraz je typicky přímo metodou v rámci komponenty. Kontext může kromě komponenty odkazovat na proměnné v šabloně jako je sama událost ($event), vstupní proměnná šablony (hero) nebo reference proměnné šablony (#heroForm). Při kolizi má přednost šablona před komponentou. Opět nelze odkazovat globální proměnné. Př.:

<button (click)="onSave($event)">Save</button>

<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>

<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>

Je dobré zachovat jednoduchost - nejlépe volat jednu metodu nebo přiřazení.

Directives

  • Jde o třídy s označením decorater @Directive
  • Umožňují dynamičnost šablon
  • Lze pomocí nich vytvořit Angular element s požadovaným chováním
  • Komponenta je vždy direktivou (@Component je potomkem @Directive)        
  • Angular poskytuje nejrůznější vestavěné direktivy pro snadnější práci.
  • Dělí se na:
  • komponenty
  • atributové direktivy (Attribute Directives)
  •  strukturální direktivy (Structural Directives)
  • Pozn.: Název třídy direktivy začíná velkým písmenem. Při použití v šabloně (jakožto atributu v elementu nebo komponentě) začíná malým.
Atributové Direktivy

které modifikují podobu nebo chování elementu (resp. jeho atributu)

  • Mezi důležité zabudované atributové direktivy patří:
  • NgClass – umožňuje přidání nebo odebrání CSS tříd; oproti mapování přes [class.nazev_třídy] umožňuje snadnější práci s více třídami najednou
  • NgStyle – umožňuje přidání nebo odebrání inline CSS stylů; oproti mapování přes [style.nazev_stylu] umožňuje snadnější práci s více styly najednou
  • NgModel – slouží k obousměrnému mapování dat ve formulářových HTML elementech.
  • Použití viz mapování dat výše.
  • Definování vlastních directives nám umožňuje ovlivnit chování elementů vč. již existujících (jako je HTML nebo komponenty třetí strany).

V kombinaci se vstupní proměnnou @Input můžeme direktivě předat hodnotu a snadno dynamicky upravit chování při použití v šabloně: https://angular.io/guide/attribute-directives#pass-values-into-the-directive-with-an-input-data-binding   

  • Ovlivnit chování komponenty (viz OnInit vs. OnDestroy).
  • Je vhodné dodržovat jmennou konvenci (typicky prefix app), aby nedocházelo ke konfliktu s atributy standardního HTML.
Strukturální Direktivy

které modifikují DOM, typicky přidáním/změnou/odebráním elementu

  • Mění DOM tak, že element i s potomky zcela odebere, komponenta a uzel v DOM může být uvolněn z GC vč. souvisejících zdrojů.
  • Alternativně lze element lze skrýt pomocí [class.hidden] nebo [style.display]. V tomto případě zůstává v DOM a v paměti Angularu. Takový element je dál udržován a sledován (vč. update mapovaných vlastností), což má u velkých komponent v DOM negativní vliv na performance. Na druhou stranu zobrazení dříve skryté komponenty je rychlé.
  • Začínají vždy prefixem * (jde o zkrácení syntaxe bez nutnosti obalovat elementy)
  • V uvozovkách za rovnítkem je spíš než pouhý výraz template expression tzv. microsyntax
  • Na jeden element lze uplatnit jen jednu direktivu
  • Pokud není k dispozici vhodný hostující element, můžeme pro direktivu použít <ng-container>, který negeneruje žádný HTML element v DOM. Například nelze přidat uvnitř dvojicí elementů select-option nebo kvůli existujícímu CSS selektoru.
  • Mezi důležité zabudované strukturální direktivy patří:
  • NgIf – podmíněné přidání nebo odebrání elementu z DOM (vč. všech vnořených komponent). Odebere element, pokud je výraz roven false nebo null(?).

*ngIf else 

  • V else větvi lze přiřadit template reference variable, kterou lze použít kdekoli v šabloně. Př.: 

<div *ngIf="isLogged; else loggedOut"> Welcome </div>

<ng-template #loggedOut> Please login </ng-template>

*ngIf then else 

  • Pomocí then else lze přiřadit template reference variable oběma (ne nezbytně logickým) hodnotám a použít v šabloně. Př.: 

<ng-template *ngIf="isLogged;then isLogged; else loggedOut"></ng-template>

<ng-template #isLogged> Hello </ng-template>

<ng-template #notShowBlock> Please login </ng-template>

ngIf as (else)

  • Alternativa pro then else k uložení hodnoty do lokální proměnné
  • Jde o často používaný konstrukt s AsyncPipe při asynchronním načítání data
  • Př. ngIfAs a async:

<div class="myDetail" *ngIf="myDetailObservable | async as detail; else loading">

            <div>{{detail.name}}</div>

            <div>{{detail.shortDescription}}</div>

            <div>{{detail.longDescription}}</div>

</div>

<ng-template #loading> <div>Loading ...</div> </ng-template>

export class AppComponent implements OnInit {

  myDetailObservable: Observable<Course>;

  ...

  ngOnInit() {

      this.myDetailObservable = this.myService.loadDetail(1);

  }

}

Bloku je registrována jedna asyncPipe (registrovat ji každé vlastnosti zvlášť by bylo drahé a nepřehledné), která je přihlášena ke změnám na myDetailObservable. Pomocí as je hodnota přiřazena do lokální proměnné detail

  • NgSwitch - umožňuje pomocí kombinace direktiv přepínat mezi různými elementy. Skládá se z:
  • atributové direktivy (bez hvězdičky!) NgSwitch, na kterou mapujeme výraz (libovolného typu) 
  • *NgSwitchCase, pokud se jeho hodnota rovná mapovanému výrazu, pak zobrazí element
  • *NgSwitchDefault, pokud se žádná hodnota z NgSwitchCase nerovná mapovanému výrazu, pak zobrazí element
  • Př.: 

<div [ngSwitch]="currentHero.emotion">

  <app-happy-hero    *ngSwitchCase="'happy'"   [hero]="currentHero"></app-happy-hero>

  <app-sad-hero      *ngSwitchCase="'sad'"     [hero]="currentHero"></app-sad-hero>

  <app-confused-hero *ngSwitchCase="'confused'"[hero]="currentHero"></app-confused-hero>

  <app-unknown-hero  *ngSwitchDefault          [hero]="currentHero"></app-unknown-hero>

</div>

  • NgForOf – opakuje šablonu pro každou položku kolekce
  • For each: *ngFor="let variable of collection
  • Př.: <app-hero-detail *ngFor="let hero of heroes" [hero]="hero"></app-hero-detail>
  • *ngFor poskytuje řadu lokálních proměnných: index, first, last, even a odd
  • Příklad použití indexu: <div *ngFor="let hero of heroes; let i=index">{{i + 1}} {{hero.name}}</div>
  • A ngForOf pro iterování.
  • Při změně / odebrání / vložení prvku z mapované kolekce dojde k drahému přepočítávání DOM všech komponent s prvky této kolekce. Tomu lze zabránit pomocí trackBy. 

Template reference variable

  • V šabloně lze vytvářet proměnné, kterým lze přiřadit DOM element (například vstup inputu), ale i web/Angular komponentu nebo direktivu.
  • Tato proměnná má scope v celé šabloně a nejen ve vnitřním bloku (oproti například proměnné z direktivy *ngFor)!
  • Deklaraci provedeme umístěním mřížky # nebo prefixu ref- před název proměnné.
  • Př.

<input #phone placeholder="phone number">

...

<!-- phone refers to the input element; pass its `value` to an event handler -->

<button (click)="callPhone(phone.value)">Call</button>

  • Umožňuje snadnou práci s požadovanou hodnotou v šabloně aniž bychom museli $event mapovat a předávat z šablony přes komponentu tam a zpět. Tím se zbavíme zbytečného provázání šablony a komponenty.

Např.:

template: `

  <input (keyup)="onKey($event)">

  <p>{{values}}</p>

`

export class KeyUpComponent_v1 {

  values = '';

  onKey(event: any) { // without type info

    this.values += event.target.value + ' | ';

  }

}

lze pomocí Template reference variable zapsat bez potřeby předávání přes komponentu:

template: `

  <input #box (keyup)="0"> //keyup zde nedělá nic smysluplného pouze vyvolá událost a tím update hodnoty v {{}}

  <p>{{box.value}}</p>

`

export class LoopbackComponent { }

hodnotu typicky v komponentě potřebujeme, ale i přes to je lépe použít template reference variable než $event, protože nevyžaduje v komponentě znalost struktury události z šablony: 

template: `

  <input #box (keyup)="onKey(box.value)">

  <p>{{values}}</p>

`

export class KeyUpComponent_v2 {

  values = '';

  onKey(value: string) {

    this.values += value + ' | ';

  }

}

  • Pro práci s formuláři je velmi užitečná direktiva ngForm, kterou obvykle přiřadíme referenční proměnné formuláře, což nám umožní sledovat a validovat veškeré ovládací prvky formuláře Př.:

<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">

  <div class="form-group">

    <label for="name">Name

      <input class="form-control" name="name" required [(ngModel)]="hero.name">

    </label>

  </div>

  <button type="submit" [disabled]="!heroForm.form.valid">Submit</button>

</form>

<div [hidden]="!heroForm.form.valid">

  {{submitMessage}}

</div>

Input and Output properties

  • input property
  • označena decorator @Input() nebo přes metadata inputs komponenty
  • vstupní stavová proměnná v komponentě, kterou lze mapovat pomocí property binding v šabloně
  • Output property
  • označena decorator @Output()nebo přes metadata outputs komponenty
  • emitovaná proměnná v komponentě, kterou lze mapovat pomocí event binding a vrací EventEmitter (dává na výstup pro rodičovskou komponentu)
  • V případě, že chceme použít (mapovat) vlastnost komponenty uvnitř její šablony není potřeba ja označovat ani jako Input ani jako Output. Ve vlastní komponentě používáme vlastnost jako template statement/expression (vpravo od rovnítka).

<img [src]="iconUrl"/>

<button (click)="onSave()">Save</button>

  • V případě, že chceme použít (mapovat) vlastnost dceřiné šablony (direktivy) v nadřazené šabloně (vlevo od rovnítka) je nutno označit ji jako veřejné API komponenty přes decorator nebo metadata (jinak mapování zamítne kompilátor).
  • Př. použití v rodičovské komponentě

<app-hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)"></app-hero-detail>

Zveřejnění API:

@Input()  hero: Hero;

@Output() deleteRequest = new EventEmitter<Hero>();

alternativně:

@Component({

  inputs: ['hero'],

  outputs: ['deleteRequest'],

})

  • Názvy vlastností veřejného API komponent třetí strany nemusí vyhovovat (např. konvencím), a tak je lze aliasovat.

Speciální operátory pro expresion languege

Pipes
  • {{interpolated_value | pipe_name}}

Př.: <p>The date is {{today | date:'fullDate'}}</p>

  • Slouží k transformaci zobrazovaných hodnot, např. formátování data nebo měny.
  • Lze použít k transformaci dat, se kterými se dál pracuje ve výrazu nebo microsyntaxi - například filtrovat kolekci ve výrazu pro *ngFor.
  • Př.: *ngFor="let hero of (days | filterWorkingDays)"
  • Nesmí mít side effect na původní dat - pouze vrací nová transformovaná data.
  • Pipes lze je řetězit (vždy vezme výsledek výraz vlevo jako vstup funkce vpravo)

Př.: {{ birthday | date | uppercase}}

  • Lze jim předávat parametry ovlivňující chování (fullDate v příkladu).
  • Parametr je oddělen od názvu pipe funkce dvojtečkou :, stejně jako jednotlivé parametry od sebe.
  • Parametr může být výrazem template expression (např. proměnná)
  • Praktickou funkcí pro debug může být json pipe, která vypíše objekt v JSON formátu, např.:

<div>{{currentHero | json}}</div>

  • K dispozici jsou již zabudované pipe funkce (Pipe API) nebo lze psát vlastní funkce.
  • Je potřeba myslet na to, že z performance důvodů se pipe automaticky uplatní jen při změně proměnné primitivního typu nebo změně reference, ale nikoli při změně “složené” hodnoty uvnitř referenční proměnné (například když se změní hodnota objektu nebo je přidán prvek do kolekce). Podobně jako se neprovádí událost OnChange.
  • Dokumentace doporučuje tam, kde je to možné, provést update a nahradit původní proměnnou (např. přidat prvek a nahradit kolekci).
  • Tam, kde nemáme dostatečnou kontrolu a potřebujeme provádět update výsledku pipe, lze použít tzv. impure pipe.
  • Provádí se nastavením vlastnosti pure na false, výchozí hodnota pipe je true. Zabudované Angular funkce lze získat jako impure, jednoduše vytvořením potomka.
  • Ta se ovšem provádí při každé  detekci změny komponenty, například při pohybu myši, což je velmi drahé především u složité pure funkce.
  • Není vhodné provádět pomocí nich filtrování nebo řazení kolekcí. To je lépe provádět v komponentě (například přes injektovanou sdílenou službu). Původní AngularJS měl pipe funkce filter and orderBy, které se často stávaly úzkým hrdlem, a proto již k dispozici nejsou.
  • Příkladem je AsyncPipe, která čeká na data (pomocí Promise) nebo reaguje na nová data (pomocí Observable).

safe navigation operator - null property paths

  • Běžný přístup k vlastnosti (přes tečkovou notací), která je null skončí chybou. Angular proto nabízí safe navigation operator ?.
  • ?. vrátí v případě, že je proměnná null, prázdný řetězec a neskončí chybou

non-null assertion operator

  • V TypeScript 2.0 lze zapnout striktní kontrolu na to, že hodnota není null nebo nedefinována. Pokud je kontrola zapnutá a chceme ji v Angularu potlačit, pak lze místo běžné tečkové notace použít non-null assertion operator !.

Přetypovací funkce $any()

  • Pokud je neznámý nebo těžko určitelný typ vlastnosti lze použít funkci $any()

Zachycení životního cyklu komponent - Lifecycle Component hooks

Angular dělá viditelnými události životního cyklu instancí komponent a direktiv (vytvoření, vyrendrování, zpracování potomků, změna hodnot, zničení před odstraněním z DOM).

Vývojář může reagovat na tyto události díky rozhraní pro lifecycle hooks, např. naše komponenta může implementovat rozhraní OnInit s metodou ngOnInit(). 

Události životního cyklu v pořadí volání:

  1. ngOnChanges() - je volána vždy před ngOnInit a vždu při změně hodnoty
  2. ngOnInit()
  3. ngDoCheck()
  4. ngAfterContentInit() 
  5. ngAfterContentChecked()
  6. ngAfterViewInit() 
  7. ngAfterViewChecked()
  8. ngOnDestroy()

Jejich přehledný popis viz dokumentace.

Při každé změně hodnoty se dvakrát provede trojce DoCheck, AfterContentChecked and AfterViewChecked, proto je potřeba je nezatěžovat složitou logikou.

OnInit vs. OnDestroy

Je doporučeno nedávat složitou logiku do konstruktorů. Místo toho v konstruktou provést pouze základní inicializaci proměnných a složitější logiku pro inicializaci umisťovat do metody ngOnInit(), protože se provede až je skutečně potřeba (nezpomalí například testy), provede se právě jednou a má již k dispozici hodnoty v případě mapovaných vlastností.

Naopak pro úklid zdrojů, o které se nestará GC, by měla sloužit ngOnDestroy().  

Pomocí direktiv a události onInti a onDestroy můžeme mj. “vložit” chování do komponent třetí strany nebo HTML.  

Příklad direktivy naslouchající vytvoření a zničení elementu div pro každý objekt listu:

// Spy on any element to which it is applied.

@Directive({selector: '[mySpy]'})

export class SpyDirective implements OnInit, OnDestroy {

  constructor(private logger: LoggerService) { }

  ngOnInit()    { this.logIt(`onInit`); }

  ngOnDestroy() { this.logIt(`onDestroy`); }

  private logIt(msg: string) {

     this.logger.log(`Spy #${nextId++} ${msg}`);

  }

}

<div *ngFor="let hero of heroes" mySpy class="heroes">

  {{hero}}

</div>

OnChange vs. DoCheck

Změna inputu vyvolá ngOnChanges() pouze pokud je mapována na primitivní typ. Pokud budeme mapovat input na vlastnost objektu, pak se změna hodnoty v inputu neprojeví, protože se nezmění reference.

Oproti tomu ngDoCheck() umožňuje reagovat i na změny, které Angular přehlíží, ale volá se velmi často (při sebemenší změně), a proto musí být implementace velmi jednoduchá.

AfterViewInit, AfterViewChecked

Angular volá po vytvoření vnořených view komponenty a umožňuje sledovat změny vlastností vnořených komponent. K těm lze přistupovat pouze dotazováním přes @ViewChild.

export class AfterViewComponent implements  AfterViewChecked, AfterViewInit {

  private prevHero = '';

  // Query for a VIEW child of type `ChildViewComponent`

  @ViewChild(ChildViewComponent, {static: false}) viewChild: ChildViewComponent;

  ngAfterViewInit() {

  let c = this.viewChild.hero.length > 10 ? `That's a long name` : '';

     if (c !== this.comment) {

        // Musi počkat na další cyklus, protože je vyrendrován, více viz níže

        this.logger.tick_then(() => this.comment = c);

     }

  }

  ngAfterViewChecked() {

     // viewChild is updated after the view has been checked

     if (this.prevHero === this.viewChild.hero) {

        this.logIt('AfterViewChecked (no change)');

     }

  }

  // ...

}

Z performance důvodů je proud dat jednosměrný (z předka do potomka), a tak je při těchto událostech view předka již sestaveno a, nelze z této události změnit proměnnou předka. Musíme v takovém případě počkat jeden JavaScript cyklus. Například použitím LoggerService.tick_then() ve výše uvedeném příkladu.

AfterContentInit, AfterContentChecked

Nastává při projekci externího obsahu do komponenty (Content projection), která je určena k importu HTML z vnějšku do komponenty. Jde o opačný přístup než je uveden výše. Pro přístup k vlastnosti vnořené komponenty je potřeba použít @ContentChild. Nastává před AfterView událostmi a oproti nim není potřeba čekat a proměnné předka lze měnit ihned.

Možnosti interakce komponent

https://angular.io/guide/component-interaction 

  1. Předání z rodiče do potomka přes input binding pomocí @Input decorations přímo na vlastnosti potomka.
  • Typicky slouží k tomu, abychom bylo možné nastavit vlastnost potomka při jeho použití v šabloně rodiče (např. v cyklu rodičovské šablony předávám prvek kolekce do komponenty znázorňující detail).
  • Použití viz event binding a Input and Output properties
  1. Předání z rodiče do potomka přes setter vstupní vlastnosti pomocí @Input decorations na tomto setteru.
  1. Předání přes událost životního cyklu ngOnChanges().
  • Hodí se především v případě, že více vstupních vlastnosti mezi sebou interaguje.
  • Viz Lifecycle Component hooks 
  • Příklad použití s proměnnou viz https://angular.io/guide/component-interaction#intercept-input-property-changes-with-ngonchanges
  1. Rodič naslouchající (mapuje) dceřiné události, která ji “vysílá” pomocí své  EventEmitter a @Output decoration.
  • Použití viz event binding a Input and Output properties
  • Obslužné metodě můžeme předat parametr s vlastnostmi události $event
  1. Rodič komunikující s potomkem přes lokální proměnné šablony (v rodiči označíme potomka pomocí template reference variable, se kterou pak můžeme pracovat vč. přístupu k jejím vlastnostem v šabloně rodiče).
  • Umožňuje přístup pouze z rodičovské šablony, nikoli z komponenty
  • Viz event binding a template reference variable
  1. Přístup z rodičovské komponenty k injektovanému potomkovi pomocí @viewChild a událostí AfterView
  1. Komunikace přes službu 
  • Scope instance služby je rodičovské a jeho dceřiná komponenta. Komponenty mimo podstrom nemají přístup.
  • Vytvoříme službu s proměnnými, které uděláme Observable. Rodičovská komponenta službu bude injektovat (přes konstruktor) a poskytovat (uvedením do metadat providers) svým potomkům. Příslušný potomek také injektuje službu (v konstruktoru) a tím získá přístup k instanci rodiče.
  • Využívá Subscription, od kterého je potřeba se odhlašovat (v ngOnDestroy() potomka)
  • Příklad a použití: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service 

Angular Elementy

  • Angular elementy mohou být převedeny na HTML Custom elements (nativní podpora existuje již u většiny prohlížečů, které slouží ke snadnějšímu vytváření znovupoužitelných komponent v HTML).
  • Angular elementy umožňují použití komponent napsaných v Angular v neAngular aplikaci.
  • Angular elementy umožňuje za běhu dynamické přidávání komponent (totéž co starší dynamické komponenty, ale snadnější).

Dynamické komponenty

  • Dynamické komponenty slouží k dynamickému přidávání komponent v šabloně, když například chceme často měnit část obsahu šablony (například banner nebo widget napsaný jako komponenta v Angularu) nebo pracovat nezávisle ve více týmech.

Formuláře

Dva přístupy řízení dat na formulářích:

  • Vhodné pro aplikace, kde jsou klíčové formuláře nebo aplikace založené na návrhovém vzoru reactive (aplikace řízené zprávami). 
  • Lépe škálovatelné, testovatelné a udržovatelné.
  • Pro snadnější a rychlejší přidání jednoduchého formuláře s logikou řízenou pouze v šabloně. Obdobný princip jako v AngularJS. Více toho za nás Angular explicitně udělá, ale máme menší kontrolu. Je více orientovaný na šablonu než komponentu.

Základní bloky formuláře

V obou přístupech se ke sledování hodnot a jejich mapování mezi Angularem a vstupním formulářovým elementem provádí přes model formuláře (form model) pomocí některého z následujících stavebních bloků:

  • FormControl tracks the value and validation status of an individual form control.
  • FormGroup tracks the same values and status for a collection of form controls.
  • FormArray tracks the same values and status for an array of form controls.
  • ControlValueAccessor creates a bridge between Angular FormControl instances and native DOM elements.

U template-driven forms využíváme rovnou v šabloně direktivu NgModel, která se postará o vytvoření a správu formControl za nás. Nemáme ale přímou kontrolu nad modelem.

U reactive forms musíme ručně vytvořit formControl v komponentě, se kterou pracujeme v šabloně.

Zásadní rozdíl je také v toku dat při zacházení se vstupními daty z formuláře. Template-driven data jsou měněna zprostředkovaně přes direktivu. Provádí se asynchronně (interně se použije událost ngModelChange). V případě reactive dat se mění hodnota synchronně bez “zprostředkovatele”, díky čemuž je jednodušší í testování.

V případě template-driven form se data model mění (je mutable). V případě template-driven form se FormControl vrací při změně vždy nový data model - původní je neměnný (immutable), lze tak snadněji sledovat unikátní změny (stavy) modelu s využitím principu Observable.

Také validace jsou u template-driven form orientované na šablonu pomocí direktiv. V případě reactive form jde funkce.

Reactive forms

https://angular.io/guide/reactive-forms 

  • Pro použití musíme mezi moduly přidat import ReactiveFormsModule
  • V komponentě dále importujeme a používáme některý ze stavebních prvků pro kontrolu formuláře.

Řízení jednotlivých vstupů formuláře - FormControl

  • Nejjednodušším stavebním prvkem je FormControl, který je určen ke správě a validování jednoho vstupu. V šabloně registrujeme model pomocí mapování direktivy formControl na požadované vstupní pole.
  • K práci s více vstupy současně jsou určeny FormGroup a FormArray (viz později).
  • Př. použití FormControl:

src/app/app.module.ts

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({

  imports: [

    // ...

    ReactiveFormsModule

  ],

})

export class AppModule { }

src/app/my-form.component.ts

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

import { FormControl } from '@angular/forms';

@Component({

  selector: 'app-name-editor',

  templateUrl: './name-editor.component.html',

  styleUrls: ['./name-editor.component.css']

})

export class NameEditorComponent {

  myInput = new FormControl('');

}

src/app/name-editor/name-editor.component.html   

<!-- Obousm2rn0 mapování mezi formControl a DOM elementem -->

<input type="text" [formControl]="myInput">

Přístup k hodnotě vstupního pole:

  1. Mapováním přes vlastnost value, například při zobrazení v šabloně:

<p>Zadal jste: {{ myInput.value }}</p>

  1. Posloucháním změn vstupu přes observable valueChanges, a to v šabloně pomocí AsyncPipe nebo v komponentě pomocí metody subscribe().
  • viz ngIf a

Změna hodnoty ve FormControl:

  • Lze provést pomocí metody setValue(). Například příchozí hodnoty z backendu by se měli nastavit přes setValue. Tato změna v komponentě se promítne rovnou do šablony.
  • Př.: src/app/my-form.component.ts

updateName() {  this.myInput.setValue('xyz');  }

Seskupování řízení formuláře - FormGroup

setValue pole / skupina

Services and DI

  • Slouží k oddělení servisní logiky od komponent pro lepší modularitu a znovupoužitelnost. Komponenta by měla pouze prezentovat data a funkce (zprostředkovat UX) a nic víc. Logiku by měla komponenta delegovat na servisní třídy, které tak zajistí například načtení hodnot ze serveru, validace nebo logování.
  • Pro snadné zpřístupnění servisní třídy nebo naopak nahrazení implementace podporuje Angular dependency injection (DI). Servisní třídy injektujeme podle potřeb do jiných tříd, např. komponent.
  • Jde o best practice. Angular tyto principy nijak nevynucuje.

Dependency injection v Angularu

Princip DI

  • Princip je DI je podobný jako ve Springu.
  • O vytváření dependency (instancí injektovaných tříd), správu kontejneru s nimi a jejich znovupoužití se stará Angular (konkrétně tzv. injector).
  • Dependency nemusí být pouze (servisní) třída, ale také funkce nebo hodnota.
  • Při startu (resp. během fáze bootstrap) je vytvořen “hlavní” injector (tzv. application-wide injector nebo také root injector) a další podle potřeby (např. má-li komponenta nebo lazy modul vlastního providera - viz níže).
  • Způsob, jakým má injector získat nebo vytvořit dependency specifikuje provider.
  • Pro každou dependency musí být definován provider. Registruje se pomocí metadat v komponentě, modulu a nebo v samotné servisní třídě (servisních třídy mohou být svým vlastním providerem).

Použití DI

  • Servisní třídy označujeme pomocí decorater @Injectable, čímž ji označíme jako dependency, definujeme potřebná metadata a umožníme ji injektovat do dalších tříd.
  • Potřebné dependencies injektujeme pomocí konstruktoru.
  • Angular při vytváření nové instance (např. komponenty) rozhodne jaké jsou potřeba dependencies, a to podle typu parametrů v konstruktoru.

Př.: constructor(private service: HeroService) { }

  • Nejdříve zjistí, zda má injector nějakou existující instanci dependency požadovaného typu. Pokud nikoliv injector ji vytvoří pomocí registrovaného provideru.
  • Samotný konstruktor je proveden až jakmile jsou vyřešeny a vráceny všechny injektované třídy z jeho parametru.
  • Pro každou dependency musí být definován provider, který zajistí vytvoření správné instance (což mj. umožňuje měnit implementaci dané depencency) a specifikuje “scope” dané dependency podle toho jak registrován:
  • a) metadaty přímo v samotné servisní třídě @Injectable(providedIn: 'root')
  • Provider na úrovni kořenové komponenty s root injector poskytne vždy jednu a tutéž instanci v celé aplikaci (singelton)
  • Umožňuje Angularu optimalizovat zkompilovanou aplikaci odebráním nepoužitých depencencies.
  • CLI příkaz ng generate service vygeneruje servisní třídu s registrací tohoto provideru
  • b) metadaty modulu @NgModule(providers) 
  • Provider poskytne stejnou instanci servisní třídy všem komponentám daného modulu
  • Použití stejné servisní třídy ve více providerech různých modulů nebo v modulu a kořenové komponentě může způsobit neočekávané chování.
  • Pokud je provider registrován do kořenové komponenty (pomocí @Injectable(providedIn: 'root')), pak se použije stejná instance také pro eager moduly
  • Při konfliktu více modulů, vyhrává poslední přidaný modul a jeho instanci použijí ostatní eager moduly
  • Pokud je konflikt s lazy modulem, pak si tento vytvoří vlastní “lokální” instanci (protože se vytvoří až po root injectoru). Pokud lazy komponenta neregistruje vlastní provider, pak instanci z root injectoru použije.
  • c) metadaty komponenty @Component(providers) 
  • Provider poskytne novou instanci servisní třídy každé instanci této komponenty bez ohledu na existenci
  • Více viz

Animace

Přímo Angular nabízí animace založené na CSS transformacích, změně barev apod.: https://angular.io/guide/animations 

Umožňuje například zvýraznění nebo plynulý přechod mezi styly elementu.


[1] Angular pracuje s proměnnými DOM stromu (DOM property) nikoliv s atributy HTML! Oproti proměnným v DOM je atribut HTML statický a nikdy se nemění. HTML atributy (spolu s dalším) slouží pouze jako výchozí hodnoty pro sestavení DOM. Prohlížeč si udržuje DOM, který se může dynamicky měnit.

Např. když uživatel vyplní hodnotu inputu tak tím změní proměnnou v DOM, ale atribut value v HTML zůstane nezměněn (s výchozí hodnotou). Property DOM a atributy se mohou, ale nemusí mapovat na sebe a názvy se mohou shodovat.

[a]tzv. microsyntaxe