Załóżmy, że jestem zaznajomiony z tworzeniem aplikacji po stronie klienta w jQuery, ale teraz chciałbym zacząć używać AngularJS. Czy możesz opisać zmianę paradygmatu, która jest konieczna? Oto kilka pytań, które mogą pomóc Ci w sformułowaniu odpowiedzi:
I'm nie szuka szczegółowego porównania jQuery
i AngularJS
.
W jQuery projektujesz stronę, a następnie sprawiasz, że jest ona dynamiczna. Dzieje się tak dlatego, że jQuery został zaprojektowany do rozszerzenia i niesamowicie rozwinął się z tego prostego założenia. Ale w AngularJS musisz zacząć od podstaw, mając na uwadze swoją architekturę. Zamiast zaczynać od myślenia "Mam ten kawałek DOM i chcę, aby zrobił X", musisz zacząć od tego, co chcesz osiągnąć, a następnie przejść do projektowania aplikacji, a następnie ostatecznie przejść do projektowania widoku.
Podobnie, nie zaczynaj od pomysłu, że jQuery robi X, Y, i Z, więc po prostu dodam AngularJS na wierzchu dla modeli i kontrolerów. Jest to bardzo kuszące, kiedy dopiero zaczynasz, dlatego zawsze zalecam, aby nowi programiści AngularJS nie używali jQuery, przynajmniej dopóki nie przyzwyczają się do robienia rzeczy na sposób Angulara.
Widziałem, jak wielu programistów tutaj i na liście mailingowej tworzy te skomplikowane rozwiązania z wtyczkami jQuery o 150 lub 200 liniach kodu, które następnie wklejają do AngularJS z kolekcją wywołań zwrotnych i $apply
, które są mylące i zagmatwane; ale w końcu udaje im się to zrobić! Problem w tym, że w większości przypadków ten plugin jQuery mógłby być przepisany w AngularJS w ułamku kodu, gdzie nagle wszystko staje się zrozumiałe i proste.
Wniosek jest taki: przy rozwiązywaniu problemu, najpierw "myśl w AngularJS";, jeśli nie możesz'wymyślić rozwiązania, zapytaj społeczności; jeśli po tym wszystkim nie ma łatwego rozwiązania, wtedy nie krępuj się sięgnąć po jQuery. Ale nie pozwól, aby jQuery stało się kulą u nogi, bo nigdy nie opanujesz AngularJS.
Po pierwsze wiedz, że aplikacje jednostronicowe są aplikacjami. Nie są nie stronami internetowymi. Musimy więc myśleć jak programista po stronie serwera w dodatku do myślenia jak programista po stronie klienta. Musimy myśleć o tym, jak podzielić naszą aplikację na pojedyncze, rozszerzalne, testowalne komponenty. A więc jak to zrobić? Jak "myśleć w AngularJS"? Oto kilka ogólnych zasad, w przeciwieństwie do jQuery.
W jQuery, programowo zmieniamy widok. Moglibyśmy mieć menu rozwijane zdefiniowane jako ul
w taki sposób:
<ul class="main-menu">
<li class="active">
<a href="#/home">Home</a>
</li>
<li>
<a href="#/menu1">Menu 1</a>
<ul>
<li><a href="#/sm1">Submenu 1</a></li>
<li><a href="#/sm2">Submenu 2</a></li>
<li><a href="#/sm3">Submenu 3</a></li>
</ul>
</li>
<li>
<a href="#/home">Menu 2</a>
</li>
</ul>
W jQuery, w naszej logice aplikacji, aktywowalibyśmy je za pomocą czegoś takiego:
$('.main-menu').dropdownMenu();
Kiedy patrzymy na widok, nie od razu widać, że jest tu jakaś funkcjonalność. Dla małych aplikacji jest to'w porządku. Ale dla nietrywialnych aplikacji, rzeczy szybko stają się zagmatwane i trudne do utrzymania.
W AngularJS jednak, widok jest oficjalnym zapisem funkcjonalności opartej na widoku. Nasza deklaracja ul
wyglądałaby zamiast tego tak:
<ul class="main-menu" dropdown-menu>
...
</ul>
Te dwa elementy robią to samo, ale w wersji AngularJS każdy, kto spojrzy na szablon, wie, co'ma się wydarzyć. Za każdym razem, gdy nowy członek zespołu deweloperów wchodzi na pokład, może spojrzeć na to i wiedzieć, że działa na tym dyrektywa dropdownMenu
; nie musi intuicyjnie rozumieć właściwej odpowiedzi ani przekopywać się przez żaden kod. Widok powiedział nam, co ma się stać. Dużo czyściej.
Programiści początkujący w AngularJS często zadają pytanie typu: jak znaleźć wszystkie linki określonego rodzaju i dodać do nich dyrektywę. Deweloper jest zawsze zdumiony, gdy odpowiadamy: nie da się. Ale powodem, dla którego tego nie zrobisz jest to, że jest to jak pół-jQuery, pół-AngularJS, i nie jest to dobre. Problem polega na tym, że programista próbuje "zrobić jQuery" w kontekście AngularJS. To'nigdy nie będzie działać dobrze. Widok jest oficjalnym zapisem. Poza dyrektywą (więcej na ten temat poniżej), nigdy, przenigdy, nigdy nie zmieniasz DOM. A dyrektywy są stosowane w widoku, więc intencja jest jasna.
Pamiętaj: nie projektuj, a potem oznaczaj. Musisz najpierw zaprojektować, a potem zaprojektować.
Jest to zdecydowanie jedna z najbardziej niesamowitych cech AngularJS i eliminuje wiele z konieczności wykonywania tego rodzaju manipulacji DOM, o których wspomniałem w poprzedniej sekcji. AngularJS będzie automatycznie aktualizował twój widok, więc ty nie musisz tego robić! W jQuery, odpowiadamy na zdarzenia, a następnie aktualizujemy zawartość. Coś w stylu:
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
Dla widoku, który wygląda tak:
<ul class="messages" id="log">
</ul>
Oprócz mieszania obaw, mamy również te same problemy z oznaczaniem intencji, o których wspomniałem wcześniej. Ale co ważniejsze, musieliśmy ręcznie odwoływać się do węzła DOM i aktualizować go. A jeśli chcemy usunąć wpis w dzienniku, musimy również zakodować go w DOM. Jak przetestować logikę poza DOM? A co jeśli chcemy zmienić prezentację? To jest trochę niechlujne i trochę kruche. Ale w AngularJS, możemy to zrobić:
$http( '/myEndpoint.json' ).then( function ( response ) {
$scope.log.push( { msg: 'Data Received!' } );
});
A nasz widok może wyglądać tak:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
Ale dla tej sprawy, nasz widok może wyglądać tak:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
A teraz zamiast listy nieuporządkowanej, używamy alert boxów Bootstrapa. I nigdy nie musieliśmy zmieniać kodu kontrolera! Ale co ważniejsze, niezależnie od tego, gdzie lub jak log zostanie zaktualizowany, widok również się zmieni. Automatycznie. Fajnie!
Choć nie pokazałem tego tutaj, wiązanie danych jest dwukierunkowe. Więc te komunikaty dziennika mogą być również edytowalne w widoku, po prostu robiąc to: <input ng-model="entry.msg" />
. I było wiele radości.
W jQuery, DOM jest czymś w rodzaju modelu. Ale w AngularJS mamy oddzielną warstwę modelu, którą możemy zarządzać w dowolny sposób, zupełnie niezależnie od widoku. Pomaga to w przypadku powyższego wiązania danych, utrzymuje separację obaw i wprowadza znacznie większą testowalność. Inne odpowiedzi wspomniały o tym punkcie, więc'po prostu zostawię to na tym.
Wszystkie powyższe punkty wiążą się z nadrzędnym tematem: zachowaj oddzielenie swoich obaw. Twój widok działa jako oficjalny zapis tego, co ma się wydarzyć (w przeważającej części); twój model reprezentuje twoje dane; masz warstwę usług do wykonywania zadań wielokrotnego użytku; robisz manipulację DOM i rozszerzasz swój widok za pomocą dyrektyw; i sklejasz to wszystko razem z kontrolerami. Zostało to również wspomniane w innych odpowiedziach, a jedyna rzecz, którą dodałbym, dotyczy testowalności, którą omawiam w innej sekcji poniżej.
Aby pomóc nam w separacji obaw jest dependency injection (DI). Jeśli pochodzisz z języka server-side (od Java do PHP), prawdopodobnie znasz już tę koncepcję, ale jeśli jesteś gościem client-side, pochodzącym z jQuery, koncepcja ta może wydawać się głupia, zbędna lub hipsterska. Ale tak nie jest :-) Z szerokiej perspektywy, DI oznacza, że możesz deklarować komponenty bardzo swobodnie, a następnie z dowolnego innego komponentu, po prostu poprosić o jego instancję i zostanie ona przyznana. Nie musisz wiedzieć o kolejności ładowania, lokalizacji plików ani o niczym takim. Siła tego rozwiązania może nie być widoczna od razu, ale podam tylko jeden (powszechny) przykład: testowanie. Powiedzmy, że w naszej aplikacji potrzebujemy usługi, która implementuje przechowywanie danych po stronie serwera poprzez REST API oraz, w zależności od stanu aplikacji, również lokalne przechowywanie danych. Podczas przeprowadzania testów na naszych kontrolerach, nie chcemy mieć potrzeby komunikowania się z serwerem - w końcu testujemy kontroler. Możemy po prostu dodać mock service o tej samej nazwie, co nasz oryginalny komponent, a injector sprawi, że nasz kontroler automatycznie dostanie ten fałszywy - nasz kontroler nie wie i nie musi wiedzieć o różnicy. Skoro już mowa o testowaniu...
To jest tak naprawdę część sekcji 3 o architekturze, ale jest tak ważne, że umieszczam to jako własną sekcję najwyższego poziomu. Spośród wszystkich wielu wtyczek jQuery, które widziałeś, używałeś lub napisałeś, ile z nich miało dołączony zestaw testów? Niewiele, ponieważ jQuery nie jest na to zbyt podatny. Ale AngularJS już tak. W jQuery, jedynym sposobem na testowanie jest często stworzenie komponentu niezależnie, z przykładową stroną demo, na której nasze testy mogą wykonywać manipulacje DOM. Musimy więc stworzyć komponent osobno, a następnie zintegrować go z naszą aplikacją. Jakie to niewygodne! Tak więc, przez większość czasu, podczas tworzenia aplikacji z jQuery, wybieramy iteracyjny, a nie sterowany testami rozwój. I kto mógłby nas za to winić? Ale ponieważ mamy separację obaw, możemy w AngularJS wykonywać iteracyjny rozwój sterowany testami! Na przykład, powiedzmy, że chcemy super prostą dyrektywę, która wskaże nam w naszym menu, jaka jest nasza aktualna trasa. Możemy zadeklarować to, co chcemy w widoku naszej aplikacji:
<a href="/hello" when-active>Hello</a>
Ok, teraz możemy napisać test dla nieistniejącej dyrektywy when-active
:
it( 'should add "active" when the route changes', inject(function() {
var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );
$location.path('/not-matching');
expect( elm.hasClass('active') ).toBeFalsey();
$location.path( '/hello' );
expect( elm.hasClass('active') ).toBeTruthy();
}));
I kiedy uruchomimy nasz test, możemy potwierdzić, że się nie powiódł. Dopiero teraz powinniśmy stworzyć naszą dyrektywę:
.directive( 'whenActive', function ( $location ) {
return {
scope: true,
link: function ( scope, element, attrs ) {
scope.$on( '$routeChangeSuccess', function () {
if ( $location.path() == element.attr( 'href' ) ) {
element.addClass( 'active' );
}
else {
element.removeClass( 'active' );
}
});
}
};
});
Nasz test został zaliczony, a nasze menu działa zgodnie z wymaganiami. Nasz rozwój jest zarówno iteracyjny jak i sterowany testami. Wspaniałe.
Często słyszy się, że "tylko manipulacje DOM w dyrektywie". Jest to konieczność. Traktuj to z należytym szacunkiem!
Ale zanurzmy się trochę głębiej...
Niektóre dyrektywy po prostu dekorują to, co'jest już w widoku (pomyśl o ngClass
) i dlatego czasami od razu wykonują manipulację DOM i wtedy są w zasadzie gotowe. Ale jeśli dyrektywa jest jak "widget" i ma szablon, powinna również respektować separację obaw. Oznacza to, że szablon to powinien pozostać w dużej mierze niezależny od jego implementacji w funkcjach odnośnika i kontrolera.
AngularJS posiada cały zestaw narzędzi, które to bardzo ułatwiają; dzięki ngClass
możemy dynamicznie aktualizować klasę; ngModel
pozwala na dwukierunkowe wiązanie danych; ngShow
i ngHide
programowo pokazują lub ukrywają element; i wiele innych - w tym te, które sami piszemy. Innymi słowy, możemy robić wszystkie rodzaje niesamowitości bez manipulacji DOM. Im mniej manipulacji DOM-em, tym łatwiej jest testować dyrektywy, tym łatwiej jest je stylizować, tym łatwiej jest je zmieniać w przyszłości i tym bardziej nadają się one do ponownego użycia i dystrybucji.
Widzę wielu programistów nowych w AngularJS, którzy używają dyrektyw jako miejsca do rzucania garści jQuery. Innymi słowy, myślą oni "skoro nie mogę'robić manipulacji DOM w kontrolerze, wezmę ten kod i umieszczę go w dyrektywie". Podczas gdy to z pewnością jest o wiele lepsze, to często wciąż jest złe.
Pomyśl o loggerze, który zaprogramowaliśmy w rozdziale 3. Nawet jeśli umieścimy go w dyrektywie, to i tak chcemy to zrobić w "Angular Way". To wciąż nie wymaga żadnej manipulacji DOM! Jest wiele przypadków, kiedy manipulacja DOM jest konieczna, ale jest to o wiele rzadsze niż myślisz! Przed wykonaniem manipulacji DOM wszędzie w swojej aplikacji, zadaj sobie pytanie, czy naprawdę musisz. Być może istnieje lepszy sposób.
Oto szybki przykład, który pokazuje wzorzec, z którym spotykam się najczęściej. Chcemy mieć przycisk z możliwością przełączania. (Uwaga: ten przykład jest trochę wymyślony i nieco oklepany, aby przedstawić bardziej skomplikowane przypadki, które są rozwiązywane w dokładnie taki sam sposób).
.directive( 'myDirective', function () {
return {
template: '<a class="btn">Toggle me!</a>',
link: function ( scope, element, attrs ) {
var on = false;
$(element).click( function () {
on = !on;
$(element).toggleClass('active', on);
});
}
};
});
Jest kilka rzeczy, które są z tym nie tak:
angular.element
i nasz komponent będzie nadal działał, jeśli zostanie wrzucony do projektu, który nie ma jQuery.angular.element
) będzie zawsze używał jQuery, jeśli zostało ono załadowane! Więc nie musimy używać $
- możemy po prostu użyć angular.element
.$
- element
, który jest przekazywany do funkcji link
będzie już elementem jQuery!.directive( 'myDirective', function () {
return {
scope: true,
template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
link: function ( scope, element, attrs ) {
scope.on = false;
scope.toggle = function () {
scope.on = !scope.on;
};
}
};
});
Ponownie, rzeczy związane z szablonami są w szablonie, więc Ty (lub Twoi użytkownicy) możecie je łatwo podmienić na takie, które spełniają dowolny wymagany styl, a logika nigdy nie musiała być dotykana. Możliwość ponownego użycia - bum!
I są jeszcze wszystkie inne korzyści, takie jak testowanie - to jest proste! Bez względu na to, co jest w szablonie, wewnętrzne API dyrektywy nigdy nie jest dotykane, więc refaktoryzacja jest łatwa. Możesz zmienić szablon tak bardzo, jak chcesz, bez dotykania dyrektywy. I bez względu na to, co zmienisz, twoje testy nadal przejdą.
w00t!
Jeśli więc dyrektywy nie są'tylko kolekcjami funkcji podobnych do jQuery, to czym są? Dyrektywy są tak naprawdę rozszerzeniami HTML. Jeśli HTML nie robi czegoś, czego potrzebujesz, piszesz dyrektywę, która robi to za Ciebie, a następnie używasz jej tak, jakby była częścią HTML.
Innymi słowy, jeśli AngularJS nie robi czegoś po wyjęciu z pudełka, pomyśl, jak zespół mógłby to osiągnąć, aby pasowało do ngClick
, ngClass
, i tak dalej.
Nawet nie używaj jQuery. Nawet nie włączaj go. To będzie cię powstrzymywać. A kiedy natrafisz na problem, który wydaje Ci się, że wiesz już jak rozwiązać w jQuery, zanim sięgniesz po $
, zastanów się, jak to zrobić w AngularJS. Jeśli nie wiesz'pytaj! 19 razy na 20, najlepszy sposób na zrobienie tego nie wymaga jQuery i próba rozwiązania go za pomocą jQuery powoduje więcej pracy dla ciebie.
W jQuery, selektory są używane do znajdowania elementów DOM, a następnie wiązania/rejestrowania obsługi zdarzeń do nich. Kiedy zdarzenie wyzwala się, ten (imperatywny) kod wykonuje się, aby zaktualizować/zmienić DOM.
W AngularJS, chcesz myśleć raczej o widokach niż o elementach DOM. Widoki to (deklaratywny) HTML, który zawiera dyrektywy AngularJS. Dyrektywy ustawiają za nas obsługę zdarzeń za kulisami i dają nam dynamiczne wiązanie danych. Selektory są rzadko używane, więc zapotrzebowanie na ID (i niektóre typy klas) jest znacznie zmniejszone. Widoki są powiązane z modelami (poprzez zakresy). Widoki są projekcją modelu. Zdarzenia zmieniają modele (czyli dane, właściwości zakresów), a widoki, które rzutują te modele aktualizują się "automatycznie."
W AngularJS, myśl o modelach, a nie o wybranych przez jQuery elementach DOM, które przechowują dane. Pomyśl o widokach jako projekcji tych modeli, zamiast rejestrować wywołania zwrotne, aby manipulować tym, co widzi użytkownik.
jQuery stosuje nierzucający się w oczy JavaScript - zachowanie (JavaScript) jest oddzielone od struktury (HTML).
AngularJS używa kontrolerów i dyrektyw (z których każda może mieć swój własny kontroler, i/lub funkcje kompilacji i linkowania), aby usunąć zachowanie z widoku/struktury (HTML). Angular posiada również services i filters, które pomagają oddzielić/uporządkować aplikację.
Zobacz także https://stackoverflow.com/a/14346528/215945
Jedno z podejść do projektowania aplikacji AngularJS:
Możesz zrobić wiele z jQuery bez wiedzy o tym, jak działa prototypowe dziedziczenie w JavaScript. Podczas tworzenia aplikacji AngularJS, unikniesz niektórych typowych pułapek, jeśli będziesz miał dobre zrozumienie dziedziczenia w JavaScript. Zalecana lektura: https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs
Czy może Pan opisać zmianę paradygmatu, która jest konieczna?
Imperatywny vs Deklaratywny
Z jQuery mówisz DOM-owi, co ma się wydarzyć, krok po kroku. Z AngularJS opisujesz, jakie wyniki chcesz uzyskać, ale nie jak to zrobić. Więcej na ten temat tutaj. Sprawdź również odpowiedź Marka Rajcoka's.
Jak mogę architektować i projektować aplikacje internetowe po stronie klienta inaczej?
AngularJS to cały framework po stronie klienta, który używa wzorca MVC (sprawdź ich graficzną reprezentację). Skupia się on w dużej mierze na separacji problemów.
Jaka jest największa różnica? Co powinienem przestać robić/używać; co powinienem zacząć robić/używać zamiast tego?
jQuery jest biblioteką
AngularJS jest pięknym frameworkiem po stronie klienta, wysoce testowalnym, który łączy w sobie tony fajnych rzeczy, takich jak MVC, dependency injection, wiązanie danych i wiele innych.
Skupia się na separation of concerns i testowaniu (unit testing i end-to-end testing), co ułatwia rozwój oparty na testach.
Najlepszym sposobem na rozpoczęcie jest przejście przez ich niesamowity tutorial. Możesz przejść przez wszystkie kroki w kilka godzin; jednakże, w przypadku, gdy chcesz opanować koncepcje za kulisami, zawierają one niezliczoną ilość odnośników do dalszego czytania.
Czy istnieją jakieś względy/ograniczenia po stronie serwera?
Możesz go używać w istniejących aplikacjach, w których już używasz czystego jQuery. Jeśli jednak chcesz w pełni wykorzystać funkcje AngularJS, możesz rozważyć kodowanie po stronie serwera za pomocą podejścia RESTful.
Pozwoli Ci to wykorzystać ich fabrykę zasobów, która tworzy abstrakcję Twojego RESTful API po stronie serwera i sprawia, że wywołania po stronie serwera (get, save, delete, itp.) są niewiarygodnie łatwe.