Javascript

Kurs Angular 2 – Szablony, binding

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 gdy isUnderlined = 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 zmiennej role):

<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ść zmiennej numList = [ 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 (tag p jest widoczny wtedy gdy isVisible = 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:

  1. Jednokierunkowy, model danych do widoku,
  2. Jednokierunkowy, widok do modelu danych,
  3. 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ść.