Template syntax, który oferuje nam Angular 2 pozwala w niezwykle prosty i przejrzysty sposób na rozwinięcie skrzydeł standardowej składni HTML, dzięki czemu w widoku jesteśmy w stanie umieścić inline-owy JS, podpiąć event handlery czy m.in z łatwością rotować klasami elementów.
Przegląd możliwości
- Interpolacja:
<div class="container">
{{ 2 + 2 }} // 4
{{ 'To jest' + ' tekst' }} // To jest tekst
{{ 'Zly przyklad'.slice(4) }} // przyklad
</div>
- Podpięcie zdarzenia:
<div class="container">
<button type="button" (click)="clickListener($event)">Button</button>
</div>
Naciśnięcie przycisku spowoduje wywołanie metody clickListener($event)
i przekazanie parametru $event
jako argument funkcji. Podpiąć możemy wszystkie dostępne standardowo zdarzenia drzewa DOM (spis).
- Rotacja klasy elementu (klasa
underline
jest dodana tylko wtedy gdyisUnderlined = true
):
<div class="container">
<p [class.underline]="isUnderlined"></p>
</div>
- Zarządzanie wieloma klasami naraz:
<div class="container">
<p [ngClass]="{ 'underline': isUnderlined, 'active': isActive }"></p>
</div>
Nie jest to zbyt estetyczne rozwiązanie – przede wszystkim bardzo łatwo o pomyłkę. Widzimy, że [ngClass]
jako wartość przyjmuje obiekt w którym klucze oznaczają nazwy klas. Znacznie lepiej byłoby wyciągnąć kod z szablonu do klasy komponentu – w postaci właściwości lub metody. Przykład:
public getClassNames() {
return {
'underline': this.isUnderlined,
'active': this.isActive
}
}
Natomiast w szablonie wystarczy:
<div class="container">
<p [ngClass]="getClassNames()"></p>
</div>
- Styl elementu (szerokość elementu jest równa wartości zmiennej
containerWidth
):
<div class="container" [style.width.px]="containerWidth">
<p>Treść</p>
</div>
- Zarządzanie wieloma stylami naraz:
<div class="container">
<p [ngStyle]="{ 'text-decoration': isUnderlined ? 'underline' : 'none', 'font-size.px': fontSize }"></p>
</div>
Tak samo jak w przypadku klas, lepszym rozwiązaniem byłoby wykorzystanie metody zwracającej obiekt.
- Atrybuty (wartość atrybutu
role
jest równa wartości zmiennejrole
):
<div class="container" [attr.role]="role">
<p></p>
</div>
- Wyświetlanie HTML-a
Interpolacja nie pozwala na wyrenderowanie kodu HTML znajdującego się w zmiennej w postaci string
. W tym wypadku musimy zastosować dyrektywę innerHTML
:
<div class="container">
<p [innerHTML]="htmlContent"></p>
</div>
- Pętla
for
, przy założeniu, że zawartość zmiennejnumList = [ 1, 2, 3 ]
:
<ul class="list">
<li *ngFor="let element of numList">{{ element }}</li>
</ul>
W rezultacie na ekranie zostaną wyświetlone kolejno liczby od 1 do 3.
Pętla for
może zostać dodatkowo wyposażona w index
, który oznacza indeks aktualnie przetwarzanego elementu kolekcji:
<ul class="list">
<li *ngFor="let element of numList; let i = index">
numList[{{ i }}] = {{ element }}
</li>
</ul>
Rezultat:
- numList[0] = 1
- numList[1] = 2
- numList[2] = 3
Istnieje jeszcze jeden parametr o który wzbogacona może zostać dyrektywa *ngFor
, mianowicie – trackBy
, aczkolwiek o nim nieco więcej w artykule dot. optymalizacji aplikacji.
- Instrukcja warunkowa
if
(tagp
jest widoczny wtedy gdyisVisible = true
):
<div class="container">
<p *ngIf="isVisible">Tekst</p>
</div>
- Instrukcja warunkowa
switch
:
<div [ngSwitch]="variable">
<p>
Wartość zmiennej 'variable' ==
<template [ngSwitchCase]="case">jeden</template>
<template ngSwitchCase="2">dwa</template>
<template ngSwitchDefault>domyślna</template>
</p>
</div>
Jeżeli wartość zmiennej variable
wynosi 2, na ekranie wyświetli się:
Wartość zmiennej 'variable' == dwa
Jeżeli wartość zmiennej variable
i zmiennej case
wynosi 1, na ekranie wyświetli się:
Wartość zmiennej 'variable' == jeden
W każdym innym wypadku, wartość zmiennej jest domyślna. W przypadku [ngSwitchCase]
, jako wartość przyjmowane jest wyrażenie w języku JavaScript (w tym wypadku zmienna case
). Jeżeli opuścimy nawiasy kwadratowe i wykorzystamy ngSwitchCase
, interpretowane jest to jako literał. Różnica ta spowodowana jest bindingiem, który omówiony został poniżej.
Binding
Binding – z ang. powiązanie, to koncepcja znana przede wszystkim z AngularJS. W przypadku ng2, mamy trzy rodzaje bindingu:
- Jednokierunkowy, model danych do widoku,
- Jednokierunkowy, widok do modelu danych,
- Two-way data binding, czyli wiązanie dwukierunkowe.
Model danych do widoku
W przypadku szablonów, naszym źródłem danych jest komponent, tak więc pierwszy typ wiązania polega na wszczepianiu w kod HTML składowych klasy. Powyżej mogliśmy zauważyć, że aby wyświetlić / wykorzystać zawartość konkretnej zmiennej, musieliśmy zastosować interpolację {{ expression }}
lub notację nawiasów kwadratowych [właściwość]="expression"
.
Wszystkie poniższe zapisy są sobie równoważne (zakładamy, że zmienna URLTitle
zawiera tytuł odnośnika):
<a href="" title="{{ URLTitle }}" class="url">Link</a>
<a href="" [title]="URLTitle" class="url">Link</a>
<a href="" bind-title="URLTitle" class="url">Link</a>
Najmniej czytelny wydaje się być przykład numer 1.
Widok do modelu danych
Z poziomu widoku generowane mogą być zdarzenia (z ang. events). Poniższe zapisy są sobie równoważne (naciśnięcie na przycisk spowoduje wywołanie funkcji clickListener($event)
):
<button type="button" (click)="clickListener($event)">Przycisk 1</button>
<button type="button" on-click="clickListener($event)">Przycisk 2</button>
Two-way data binding
Wiązanie dwukierunkowe najlepiej sprawdza się w przypadku formularzy (dane uzupełniane z poziomu widoku są automatycznie aktualizowane w modelu danych). Składnia bindingu dwukierunkowego opiera się na umieszczeniu słowa kluczowego w kombinację nawiasów okrągłych i kwadratowych – [()]
. Aby zapamiętać tę regułę, Angular 2 radzi:
Visualize a banana in a box to remember that the parentheses go inside the brackets. – angular.io
Poniższe przykłady są równoważne:
<input type="text" [(ngModel)]="form.name" name="name" />
<input type="text" bindon-ngModel="form.name" />
Nasze pole name
obiektu form
będzie automatycznie aktualizowane w modelu w reakcji na zmiany z poziomu widoku. Oczywiście jest to zwyczajny syntatix sugar. Powyższy zapis jest równoważny z tym:
<input type="text" [ngModel]="form.name" (ngModelChange)="form.name = $event" name="name" />
<input type="text" bind-ngModel="form.name" on-ngModel="form.name = $event" >
Czyli z poziomu modelu bindujemy wartość form.name
do elementu input
, a następnie w reakcji na zdarzenia nadpisujemy jego wartość.