Pierwsze kroki z AngularJS: dyrektywy i dwukierunkowe wiązanie
AngularJS sprawia, że DOM reaguje na twoje dane automatycznie. Praktyczne wprowadzenie do dwukierunkowego wiązania, dyrektyw, kontrolerów i wstrzykiwania zależności — plus uczciwe spojrzenie na cykl digest i miejsca, gdzie magia kosztuje.
Przez lata frontowy JavaScript oznaczał jQuery: wybierz element, odczytaj lub zapisz jego zawartość, podepnij zdarzenie, powtórz. Działa, ale przy czymkolwiek o realnej interaktywności staje się plątaniną ręcznych aktualizacji DOM, gdzie strona i twoje dane są zawsze o jedną pominiętą linię od niezgody. AngularJS proponuje coś fundamentalnie innego — opisz relację między danymi a DOM raz, a framework niech trzyma je w synchronizacji. Po kilku projektach z nim oto praktyczne wprowadzenie, od którego chcielibyśmy zacząć — z magią i kosztami włącznie.
Dwukierunkowe wiązanie: sztandarowa sztuczka
Funkcją, która sprawia, że ludzie się prostują, jest dwukierunkowe wiązanie danych. Deklarujesz wiązanie w markupie i od tej chwili model i widok pozostają zsynchronizowane automatycznie — w obu kierunkach, bez kodu klejącego:
<div ng-app ng-controller="GreetController">
<input ng-model="name" placeholder="twoje imię">
<p>Cześć, {{ name }}!</p>
</div>
Wpisuj do inputa, a akapit aktualizuje się w trakcie pisania. Zmień name w
JavaScripcie, a input się aktualizuje. Nie ma $('#name').on('keyup', ...), nie ma
wywołania .text(...) — ng-model i interpolacja {{ name }} deklarują, że te dwie
rzeczy są tą samą wartością, a Angular robi resztę. Dla formularzy, filtrów,
podglądów na żywo i dashboardów usuwa to ogromną ilość harówki, z której zbudowany
jest kod jQuery.
Kontrolery i scope
Dane stojące za kawałkiem UI żyją na $scope, podpiętym przez kontroler:
angular.module("app", [])
.controller("TodoController", function ($scope) {
$scope.todos = [
{ title: "Napisz wpis na bloga", done: false },
{ title: "Wyślij go", done: false },
];
$scope.remaining = function () {
return $scope.todos.filter(function (t) { return !t.done; }).length;
};
$scope.add = function (title) {
$scope.todos.push({ title: title, done: false });
};
});
<div ng-controller="TodoController">
<p>pozostało: {{ remaining() }}</p>
<ul>
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span>{{ todo.title }}</span>
</li>
</ul>
<input ng-model="newTitle">
<button ng-click="add(newTitle)">Dodaj</button>
</div>
$scope to most między kontrolerem a widokiem: właściwości i funkcje na nim są
wprost adresowalne w szablonie. ng-repeat renderuje listę i trzyma ją w
synchronizacji, gdy todos się zmienia; zaznacz checkbox, a remaining()
aktualizuje się natychmiast, bo checkbox jest związany z todo.done, a widok
przelicza się ponownie. Zauważ, że nigdzie nie ma manipulacji DOM — opisujesz, czym
UI jest jako funkcja danych, a nie kroki, by je zmienić.
Dyrektywy: uczenie HTML-a nowych sztuczek
Dyrektywy to najpotężniejsza i najbardziej wyróżniająca idea Angulara. ng-model,
ng-repeat i ng-click to wszystko dyrektywy — znaczniki na elementach DOM
doczepiające zachowanie. Prawdziwą mocą jest to, że możesz napisać własne, pakując
kawałek UI w reużywalny element niestandardowy lub atrybut:
angular.module("app").directive("starRating", function () {
return {
restrict: "E", // użyj jako elementu: <star-rating>
scope: { rating: "=" }, // dwukierunkowo wiąż atrybut 'rating'
template:
'<span ng-repeat="n in [1,2,3,4,5]" ng-click="rating = n">' +
' {{ n <= rating ? "★" : "☆" }}' +
'</span>',
};
});
<star-rating rating="review.score"></star-rating>
Teraz <star-rating> to komponent, który możesz upuścić gdziekolwiek, z własnym
szablonem i rating związanym dwukierunkowo z tym, co przekażesz. To ten sam
instynkt, który później napędzi web components i każdy framework komponentowy, jaki
nastąpi: enkapsuluj markup, style i zachowanie w nazwaną, reużywalną jednostkę.
Angular dotarł tam wcześnie, a dyrektywy są miejscem, gdzie ambicja frameworka
naprawdę widać.
Wstrzykiwanie zależności i serwisy
Angular ma wbudowane wstrzykiwanie zależności, co jest niezwykłe dla frontowego JavaScriptu i naprawdę użyteczne. Prosisz o to, czego potrzebujesz, po nazwie w argumentach funkcji, a Angular to dostarcza:
angular.module("app")
.factory("TodoApi", function ($http) {
return {
all: function () { return $http.get("/todos"); },
create: function (t) { return $http.post("/todos", t); },
};
})
.controller("TodoController", function ($scope, TodoApi) {
TodoApi.all().then(function (res) { $scope.todos = res.data; });
});
Kontroler deklaruje, że potrzebuje $http (klienta HTTP Angulara) i twojego serwisu
TodoApi, i dostaje je wstrzyknięte. Wepchnięcie komunikacji z serwerem do serwisu
trzyma kontrolery cienkimi i czyni warstwę danych testowalną w izolacji — ta sama
dyscyplina rozdzielenia odpowiedzialności, którą cenimy po stronie Railsów,
zastosowana w przeglądarce. Dla aplikacji Rails TodoApi rozmawia z twoimi
endpointami JSON, a Angular renderuje wynik — czysty podział między serwerem
serwującym dane a klientem je prezentującym.
Cykl digest: skąd bierze się magia i co kosztuje
Nieodpowiedzialne byłoby uczyć Angulara bez wyjaśnienia, jak wiązanie naprawdę działa, bo tam mieszkają jego granice. Nie ma prawdziwej „magii” — jest cykl digest. Angular trzyma listę obserwatorów (jeden na wiązanie) i po każdym zdarzeniu, o którym wie (kliknięcie, odpowiedź HTTP, timeout), uruchamia pętlę, która sprawdza każdą obserwowaną wartość wobec poprzedniej i aktualizuje DOM tam, gdzie się różnią. Powtarza, aż nic się nie zmieni — to „dirty checking”.
Ma to dwie praktyczne konsekwencje, które musisz przyswoić:
- Wydajność skaluje się z liczbą wiązań. Każde wiązanie to obserwator
sprawdzany przy każdym digeście. Strona z kilkuset jest w porządku; siatka z
tysiącami żywych wiązań uczyni digest wolnym, a UI szarpane. Poprawki to realne
techniki, po które sięgniesz — wiązania jednorazowe (
{{ ::value }}w 1.3),track bywng-repeati niewiązanie tego, co nie musi się zmieniać. - Angular zna tylko zmiany, które sam spowodował. Jeśli zmienisz dane poza
światem Angulara — surowy
setTimeout, callback od strony trzeciej, zdarzenie jQuery — digest się nie uruchomi, a widok zwietrzeje. Musisz powiedzieć Angularowi przez$scope.$apply(...). Pytanie „czemu mój widok się nie aktualizuje?” niemal zawsze kończy się tutaj.
Czy używać?
Dla prawdziwej aplikacji jednostronicowej — bogatej, ze stanem, z dużą ilością interaktywnego UI nad API JSON — AngularJS to duży krok naprzód względem ręcznie klepanego jQuery. Dwukierunkowe wiązanie i dyrektywy usuwają kategorię błędów i sporo kodu, struktura (kontrolery, serwisy, DI) skaluje się do prawdziwej aplikacji, a myślenie komponentowe to właściwy kierunek dla całej branży. Koszty są równie realne: cykl digest to model wydajności, który musisz szanować, framework jest opiniujący i duży, a posypanie odrobiną Angulara strony renderowanej po stronie serwera jest niezgrabne — chce posiadać swój zakątek DOM.
Nasza zasada w 2014: sięgaj po Angulara, gdy ekran jest naprawdę aplikacjopodobny, i zostań przy HTML renderowanym po stronie serwera (plus mały, celowany JavaScript), gdy to głównie treść. Użyty do tego, do czego jest zbudowany, zmienia, ile możesz komfortowo zbudować w przeglądarce — tylko wejdź w to, rozumiejąc cykl digest, bo to zrozumienie jest różnicą między Angularem czującym się jak magia a Angularem czującym się jak zagadka.