Dzisiaj chciałem opisać w kilku punktach najważniejsze cechy frameworka Angular dla użytkowników, którzy nie mieli z nim jeszcze styczności. Na wstępnie trzeba zaznaczyć, że twórcy odeszli od nazwy Angular 2 (głównie z powodu wporwadzenia Semantic Versioning). Od teraz wersje 1.x.x nazywamy AngularJS, a do wydań oznaczonych jako 2.x.x i dalej będziemy używali krótkiej nazwy Angular. Można o tym poczytać tutaj.
Czym jest Angular?
Angular to framework służący do rozwijania aplikacji webowych oraz na platformy mobilne. Nie jest to kolejna wersja swojego poprzednika, został napisany całkowicie od nowa. Wersje te nie są ze sobą kompatybilne. Na pierwszy rzut oka widać, że dwójka bardzo różni się od swojego pierwowzoru. Poniżej postaram się przedstawić te różnice i pokazać, że pomimo tego że są to całkiem inne narzędzia, developerzy korzystający z jedynki będą mogli wykorzystać część swojego doświadczenia podczas pracy z nowym wydaniem.
Wybór języka
Twórcy Angulara dają nam wybór w kwestii języka, którym będziemy się posługiwać podczas pisania naszych aplikacji. Do wyboru mamy następujące opcje:
- ES5, czyli po prostu JavaScript. Ten sam, który był najczęściej wykorzystywany podczas rozwoju aplikacji w starym AngularJS.
- ES6/ES2015, jest to rozszerzenie standardowego języka JavaScript, dodające nowe funkcje. Nie wszystkie przeglądarki go wspierają, dlatego podczas rozwoju aplikacji wykorzystujących ES6 należy korzystać z kompilatorów np. Babel.
- TypeScript wprowadza do JavaScript’u jeszcze więcej możliwośći np. typy, dzięki czemu łatwiej wyłapywać błędy podczas pisania aplikacji. Jest rozszerzeniem ES6, i tak samo jak on, wymaga kompilatora. Więcej na jego temat można poczytać w tym miejscu.
- Dart jest to język programowania stworzony przez firmę Google. Po więcej informacji na temat tego języka zapraszam tutaj.
W dalszej części tego artykuły będę posługiwał się przykładami napisanymi w TypeScripcie.
Moduły
Obie wersje Angulara przedstawiają koncepcję modułu, jako swego rodzaju punkt wejścia dla naszej aplikacji. W pierwszej wersji definiowaliśmy go w następujący sposób:
1 2 3 4 |
(function () { angular .module('app', []); })(); |
W nawiasach kwadratowych podawaliśmy zależności z których chcieliśmy korzystać w naszej aplikacji. Następnie musieliśmy oznaczyć w HTMLu gdzie nasza aplikacji ma się znajdować:
1 |
<html ng-app="app"> |
Teraz definiujemy go w następujący sposób:
1 2 3 4 5 6 7 8 9 10 11 |
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { MyComponent } from './my.component'; @NgModule({ imports: [BrowserModule], declarations: [...], bootstrap: [...], }) export class AppModule { } |
Moduł tym razem służy do oznaczenia z jakich elementów nasza aplikacja będzie się składała. Do tego celu służy dekorator @NgModule
, który używamy w połączeniu z klasą modułu. Wskazujemy, z jakich modułów dostarczanych z zewnątrz będziemy korzystać (imports
) np. RouterModule lub BrowserModule. Deklarujemy (declarations
) z jakich komponentów będziemy korzystać w naszej aplikacji. Może się ona składać z setek lub tysięcy małych komponentów. Definiujemy tutaj także komponent wejściowy przy użyciu słowa bootstrap
. Komponent ten zostaje uruchomiony jako pierwszy. W górnej części tego pliku znajdują się importy, czyli mechanizm TypeScriptu, który pozwala na użycie innych bibliotek w naszej aplikacji. Modułom dokładniej przyjrzymy się w kolejnych postach.
Kontrolery i Komponenty
Angular odszedł od koncepcji kontrolerów, używanych w pierwszej wersji. Kontrolery były definiowane w widoku:
1 2 3 |
<div ng-controller="MyController as ctrl"> <h3>{{ctrl.title}}</h3> </div> |
Następnie dodawany był kod, który przedstawiał jego logikę.
1 2 3 4 5 6 7 8 9 10 |
(function () { angular .module('app', []) .controller('MyController', MyController); function MyController() { var ctrl = this; ctrl.title = 'My title'; } })(); |
Obecnie w widoku używamy tylko nazwy naszego komponentu np.:
1 |
<my-component></my-component> |
Sam komponent zaś definiuje jego logikę oraz wygląd:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { Component } from '@angular/core'; @Component({ selector: 'my-component', template: ` <div> <h3>{{title}}</h3> <div> ` }) export class MyComponent { title = 'My title'; } |
Importujemy tutaj dekorator komponentu, który pozwala nam na jego definicje. Wskazujemy selector
, który będzie wskazywał na nasz komponent w HTMLu, jego wygląd poprzez template
(można oczywiście dodać ten szablon w osobnym pliku), oraz logikę komponentu w klasie.
Dyrektywy
Dyrektywy dostępne w Angularze uległy znacznym zmianom, ale dalej są obecne. Popularne w pierwszej wersji dyrektywy to na przykład ng-repeat
oraz ng-if
. W nowej wersji zostały one zastąpione przez *ngFor
oraz *ngIf
. Nazywamy je Structural directives, ponieważ wpływają na strukturę naszej strony (powielają elementy, usuwają etc). Są poprzedzane znakiem *
. Przykłady dla AngularJS:
1 2 3 4 5 6 7 |
<!-- AngularJS --> <div ng-if="cats.length"> <h1>List of cats</h1> <ul> <li ng-repeat="cat in cats">{{cat.name}}</li> </ul> </div> |
A teraz nowa wersja:
1 2 3 4 5 6 7 |
<!-- Angular --> <div *ngIf="cats.length"> <h1>List of cats</h1> <ul> <li *ngFor="let cat of cats">{{cat.name}}</li> </ul> </div> |
Różnice nie są wielkie, oprócz samej składni, dla *ngFor
dochodzi także słowo kluczowe let
, które definiuje zmienną lokalną.
Oprócz tego, w Angularze usunięte zostają inne dyrektywy, takie jak ng-src
, ng-style
, ng-href
. Podobnie jest z dyrektywami, które służyły do obsługi zdarzeń np. ng-click
czy ng-focus
. Zamiast tego, możemy podpiąć się bezpośrednio do atrybutów lub zdarzeń w HTML, na przykład:
1 2 3 4 5 6 7 |
<!-- Angular --> <div [style.visibility]="story ? 'visible' : 'hidden'"> <img [src]="imagePath"> <br/> <a [href]="link">{{story}}</a> <button (click)="doStuff()">Ok</button> </div> |
Do właściwości podpinamy się poprzez nawiasy kwadratowe []
, a do zdarzeń nawiasy okrągłe ()
. Daje nam to dużo możliwości, ponieważ nie musimy już polegać na tym, czy interesująca nas dyrektywa istnieje. Zamiast tego korzystamy po prostu z atrybutów oraz zdarzeń w HTMLu.
Data binding
Tutaj także jest wiele podobieństw, chociaż ponownie składnia trochę się różni. O ile dalej możemy korzystać z nawiasów klamrowych {{...}}
aby wyświetlić dane lub tworzyć wyrażenia, to two-way oraz one-way binding znany z poprzednika wygląda trochę inaczej. Działa tutaj ten sam mechanizm, który pokazałem w dyrektywach. One-way binding, w którym zmiany wychodzą z widoku do kodu, odbywa się poprzez użycie nawiasów okrągłych ()
. Tutaj przykładem mogą być eventy, gdzie zdarzenie wysyła nam informacje do naszej klasy. Wysyłanie danych z klasy do widoku odbywa się poprzez nawiasy kwadratowe []
(wykorzystując atrybuty, klasy, style, właściwości) lub klamrowe {}
(mechanizm ten nazywa się Interpolation). Two-way binding, czyli przesył danych w obie strony, odbywa się zarówno przy użyciu nawiasów kwadratowych i okrągłych [(...)]
. Składnia ta wydaje się dziwna, ale jest w 100% spójna z tym co powiedzieliśmy wcześniej: dane zarówno wysyłane są z widoku do kodu, oraz z kodu do widoku.
1 2 3 |
<div> <input [(ngModel)]="myObject.name"> </div> |
Zamiast nawiasów mamy możliwość korzystania z prefixów bind-
lub on-
dla komunikacji w jedną stronę, lub bindon-
w obie strony. Przykłady:
1 2 3 4 5 |
<div> <h3 bind-innerText="name"></h3> <input bindon-ngModel="name"> <button on-click="doStuff()">Ok</button> </div> |
Serwisy
W pierwszym Angularze mieliśmy do wyboru kilka metod, które pozwalały na dodawanie serwisów do aplikacji. Dzięki nim byliśmy w stanie wyciągnąć w jedno miejsce logikę, która powtarzała się w wielu fragmentach aplikacji lub pozwalały na komunikację pomiędzy różnymi jej elementami. Czasem pojawiał się problem: którą metodę wybrać? Do wyboru były popularne Factory
, Service
czy Provider
, a także Constants
oraz Values
. Ten element został całkowicie zmieniony w nowej wersji. Zamiast wybierać dostępne mechanizmy, po prostu dodajemy klasę, która ma w sobie określoną funkcjonalność, a następnie używamy jej w dowolnym miejscu.
1 2 3 4 5 6 7 8 9 |
import { Injectable } from '@angular/core'; @Injectable() export class CatsService { getCats = () => [ { id: 1, name: 'Garfield' }, { id: 2, name: 'Mruczek' } ]; } |
Należy jedynie dodać dekorator @Injectable
, który pozwoli na wstrzyknięcie naszego serwisu np. do komponentu. O tym opowiem w kolejnym akapicie.
Dependency Injection
Dependency Injection to wzorzec architektoniczny, polegający na wstrzykiwaniu zależności do naszego kodu. Jeżeli chcemy odseparować z komponentu logikę np. pobierania użytkowników z bazy danych, a w samym komponencie zająć się jedynie ich wyświetlaniem, możemy zrobić to w łatwy sposób. Pobieranie danych wyciągamy do Serwisu, czyli innej klasy, a w komponencie wskazujemy na to, że potrzebujemy dostać obiekty do wyświetlenia. W module dodajemy komponent i serwis:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { CatsComponent } from './cats.component'; import { CatsService } from './cats.service'; @NgModule({ imports: [BrowserModule], declarations: [CatsComponent], providers: [CatsService], bootstrap: [CatsComponent], }) export class AppModule { } |
W komponencie wskazujemy, że potrzebujemy serwisu (używamy tutaj CatsService
z poprzedniego akapitu), który zwróci nam nasze obiekty do wyświetlenia:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { Component } from '@angular/core'; import { CatsService } from './cats.service'; @Component({ selector: 'my-cats', templateUrl: './cats.component.html', }) export class CatsComponent { cats = this.catsService.getVehicles(); constructor(private catsService: CatsService) { } } |
W ten sposób nasz komponent będzie w stanie wyświetlić listę naszych kotów, bez wiedzy skąd trzeba je pobrać!
Podsumowanie
Oczywiście, to co tutaj przedstawiłem to tylko część dobrodziejstw które oferuje nam Angular. Mamy jeszcze Routing, który został znacznie uproszczony, filtry, które teraz nazywają się Pipes oraz wiele innych. Wydaje mi się, że pomimo wielu różnic które pojawiły się w nowej wersji (wiele z nich moim zdaniem dobrych), kilka konceptów znanych z pierwszego wydania tego frameworka można zastosować w nowych aplikacjach, które będą już korzystały z najnowszej edycji. Dzięki temu próg wejścia w tą technologię jest dla weteranów AngularJS znacznie mniejszy.
Na dzisiaj to tyle. Tematy, które nie zostały tutaj poruszone na pewno pojawią się w przyszłości. Postaram się także rozwinąć bardziej szczegółowo to, o czym już mówiłem. Zapraszam do komentowania!