Да предположим, че съм запознат с разработването на клиентски приложения в jQuery, но сега искам да започна да използвам AngularJS. Можете ли да опишете необходимата промяна на парадигмата? Ето няколко въпроса, които могат да ви помогнат да формулирате отговора:
Не търся подробно сравнение между jQuery
и AngularJS
.
В jQuery проектирате страницата, а след това я правите динамична. Това е така, защото jQuery е проектиран за допълване и се е разраснал неимоверно от тази проста предпоставка. Но в AngularJS трябва да започнете от самото начало с оглед на вашата архитектура. Вместо да започвате с мисълта "Имам тази част от DOM и искам да я накарам да направи X", трябва да започнете с това, което искате да постигнете, след това да проектирате приложението си и накрая да проектирате изгледа си.
По същия начин не започвайте с идеята, че jQuery прави X, Y и Z, така че просто ще добавя AngularJS отгоре за модели и контролери. Това е много изкушаващо, когато започвате, и затова винаги препоръчвам на новите разработчици на AngularJS да не използват jQuery, поне докато не свикнат да правят нещата по "Angular Way".
Виждал съм много разработчици тук и в пощенския списък да създават сложни решения с плъгини на jQuery от 150 или 200 реда код, които след това вграждат в AngularJS с набор от обратни повиквания и $apply
, които са объркващи и сложни; но в крайна сметка им се получава! Проблемът е, че в повечето случаи този плъгин на jQuery може да бъде пренаписан в AngularJS с малка част от кода, при което всичко изведнъж става разбираемо и просто.
Изводът е следният: когато търсите решение, първо "мислете на AngularJS";; ако не можете да измислите решение, попитайте общността; ако след всичко това няма лесно решение, тогава не се колебайте да посегнете към jQuery. Но не позволявайте jQuery да се превърне в патерица, защото иначе никога няма да овладеете AngularJS.
Първо знайте, че приложенията от една страница са приложения. Те не са уебстраници. Затова трябва да мислим като разработчик от страна на сървъра в допълнение към мисленето като разработчик от страна на клиента. Трябва да помислим как да разделим нашето приложение на отделни, разширяеми и тестваеми компоненти. И тогава как да направите това? Как да мислите в AngularJS"? Ето някои общи принципи, съпоставени с jQuery.
В jQuery променяме изгледа програмно. Можем да имаме падащо меню, дефинирано като ul
по следния начин:
<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>
В jQuery, в нашата логика на приложение, ще го активираме с нещо подобно:
$('.main-menu').dropdownMenu();
Когато просто погледнем изгледа, не е веднага очевидно, че тук има някаква функционалност. За малки приложения това е добре. Но за нетривиални приложения нещата бързо стават объркващи и трудни за поддържане.
В AngularJS обаче изгледът е официалният запис на функционалността, базирана на изгледа. Вместо това нашата декларация ul
би изглеждала така:
<ul class="main-menu" dropdown-menu>
...
</ul>
Тези две декларации правят едно и също нещо, но във версията на AngularJS всеки, който погледне шаблона, знае какво'се очаква да се случи. Всеки път, когато дойде нов член на екипа разработчици, той може да погледне това и след това да знае, че върху него работи директива, наречена dropdownMenu
; не е нужно да интуитивно да разбира правилния отговор или да пресява код. Изгледът ни казва какво трябва да се случи. Много по-чисто.
Разработчиците, които са нови в AngularJS, често задават въпрос от типа: как да намеря всички връзки от определен вид и да добавя директива върху тях. Разработчикът винаги е смаян, когато му отговорим: не трябва. Но причината, поради която не'правите това, е, че това е като half-jQuery, half-AngularJS и не е добре. Проблемът тук е, че разработчикът се опитва да "направи jQuery" в контекста на AngularJS. Това никога няма да се получи добре. Изгледът е официалният запис. Извън директива (повече за това по-долу), никога, никога, никога не променяте DOM. А директивите се прилагат в изгледа, така че намерението е ясно.
Запомнете: не проектирайте, а след това маркирайте. Трябва да създавате архитектура, а след това да проектирате.
Това определено е една от най-страхотните функции на AngularJS и премахва много от необходимостта да се правят манипулации с DOM, които споменах в предишния раздел. AngularJS автоматично ще актуализира изгледа ви, за да не се налага вие да го правите! В jQuery реагираме на събития и след това актуализираме съдържанието. Нещо като:
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
За изглед, който изглежда по следния начин:
<ul class="messages" id="log">
</ul>
Освен смесването на проблемите, имаме и същите проблеми със означаването на намеренията, които споменах преди. Но по-важното е, че трябваше ръчно да направим препратка и да актуализираме DOM възел. А ако искаме да изтрием запис в дневника, трябва да кодираме и това срещу DOM. Как да тестваме логиката отделно от DOM? А ако искаме да променим представянето? Това е малко объркано и малко несигурно. Но в AngularJS можем да направим това:
$http( '/myEndpoint.json' ).then( function ( response ) {
$scope.log.push( { msg: 'Data Received!' } );
});
И нашият изглед може да изглежда така:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
Но в този смисъл нашият изглед може да изглежда така:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
И сега, вместо да използваме неподреден списък, използваме предупредителни полета Bootstrap. И не се наложи да променяме кода на контролера! Но по-важното е, че независимо от това къде или как се актуализира дневникът, изгледът също ще се промени. Автоматично. Чисто!
Въпреки че не го показах тук, свързването на данните е двупосочно. Така че тези съобщения в дневника могат да се редактират и в изгледа само по този начин: <input ng-model="entry.msg" />
. И имаше много радост.
В jQuery DOM е нещо като модел. Но в AngularJS имаме отделен моделен слой, който можем да управляваме по какъвто си искаме начин, напълно независимо от изгледа. Това помага за горното свързване на данни, поддържа separation of concerns и въвежда много по-голяма възможност за тестване. Други отговори споменаха тази точка, така че ще я оставя само на това.
Всички гореизброени отговори се свързват с тази всеобхватна тема: дръжте грижите си отделно. Вашият изглед действа като официален запис на това, което трябва да се случи (в по-голямата си част); вашият модел представя данните ви; имате слой от услуги за изпълнение на задачи за многократна употреба; извършвате манипулации с DOM и допълвате изгледа си с директиви; и слепвате всичко това с контролери. Това беше споменато и в други отговори, а единственото, което бих добавил, е свързано с възможността за тестване, която обсъждам в друг раздел по-долу.
За да ни помогне с разделянето на проблемите, е dependency injection (DI). Ако идвате от сървърен език (от Java до PHP), вероятно вече сте запознати с тази концепция, но ако сте човек от клиентската страна, идващ от jQuery, тази концепция може да изглежда от глупава, през излишна до хипарска. Но това не е така :-) От по-широка гледна точка DI означава, че можете да декларирате компоненти много свободно и след това от всеки друг компонент просто да поискате негова инстанция и тя ще ви бъде предоставена. Не е нужно да знаете за реда на зареждане, за местоположението на файловете или нещо подобно. Възможно е силата да не се вижда веднага, но аз'ще дам само един (често срещан) пример: тестването. Нека'да кажем, че в нашето приложение се нуждаем от услуга, която реализира съхранение от страна на сървъра чрез REST API и, в зависимост от състоянието на приложението, също и локално съхранение. Когато изпълняваме тестове на нашите контролери, не искаме да се налага да комуникираме със сървъра - все пак тестваме контролера. Можем просто да добавим имитационна услуга със същото име като оригиналния ни компонент и инжекторът ще се погрижи нашият контролер да получи автоматично фалшивата - нашият контролер не знае и не е нужно да знае разликата. Говорейки за тестване...
Това всъщност е част от раздел 3 за архитектурата, но е толкова важно, че го поставям като отделен раздел от най-високо ниво. От всички многобройни плъгини jQuery, които сте виждали, използвали или писали, колко от тях са имали придружаващ набор от тестове? Не са много, защото jQuery не е много подходящ за това. Но AngularJS е. В jQuery единственият начин за тестване често е да създадем компонента самостоятелно с примерна/демонстрационна страница, спрямо която нашите тестове могат да извършват манипулации с DOM. Така че тогава трябва да разработим компонента отделно и после да го интегрираме в нашето приложение. Колко неудобно! Затова в повечето случаи, когато разработваме с jQuery, избираме итеративна, а не тестова разработка. И кой би могъл да ни вини? Но тъй като имаме разделение на грижите, можем да извършваме итеративно разработване, базирано на тестове, в AngularJS! Например, да кажем, че искаме суперпроста директива, която да показва в менюто ни какъв е текущият маршрут. Можем да декларираме това, което искаме, в изгледа на нашето приложение:
<a href="/hello" when-active>Hello</a>
Добре, сега можем да напишем тест за несъществуващата директива 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();
}));
И когато стартираме нашия тест, можем да потвърдим, че той се проваля. Едва сега трябва да създадем нашата директива:
.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' );
}
});
}
};
});
Сега тестът ни преминава успешно и менюто ни се изпълнява според изискванията. Разработката ни е итеративна и базирана на тестове. Страхотно готино.
Често ще чуете "да правите манипулации с DOM само в директива". Това е необходимост. Отнасяйте се към нея с нужното уважение!
Но нека се потопим малко по-надълбоко...
Някои директиви само украсяват това, което вече е в изгледа (спомнете си за ngClass
), и затова понякога правят манипулации с DOM направо и след това практически са готови. Но ако дадена директива е като "джаджа" и има шаблон, тя трябва също да спазва разделението на грижите. Това означава, че шаблонът също трябва да остане до голяма степен независим от реализацията му във функциите за връзка и контролер.
AngularJS идва с цял набор от инструменти, които правят това много лесно; с помощта на ngClass
можем динамично да актуализираме класа; ngModel
позволява двупосочно свързване на данни; ngShow
и ngHide
програмно показват или скриват елемент; и много други - включително тези, които сами пишем. С други думи, можем да правим всякакви страхотни неща без манипулация на DOM. Колкото по-малко са манипулациите с DOM, толкова по-лесно е да се тестват директивите, толкова по-лесно е да се стилизират, толкова по-лесно е да се променят в бъдеще и толкова по-удобни са за повторно използване и разпространение.
Виждам много нови разработчици на AngularJS, които използват директивите като място, където да хвърлят куп jQuery. С други думи, те си мислят, че след като не мога да манипулирам DOM в контролера, ще взема този код и ще го сложа в директива. Макар че това със сигурност е много по-добре, често все още е погрешно.
Спомнете си за логера, който програмирахме в раздел 3. Дори и да го поставим в директива, ние все още искаме да го направим по "Angular Way". Това все още не изисква никаква манипулация с DOM! Има много моменти, когато е необходима манипулация на DOM, но те са много по-редки, отколкото си мислите! Преди да извършвате манипулация на DOM всякъде в приложението си, се запитайте дали наистина е необходимо. Може да има по-добър начин.
Ето един кратък пример, който показва модела, който виждам най-често. Искаме бутон с възможност за превключване. (Бележка: този пример е малко измислен и малко по-сложен, за да представи по-сложни случаи, които се решават по абсолютно същия начин.)
.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);
});
}
};
});
Има няколко неща, които не са наред с това:
angular.element
и нашият компонент пак ще работи, когато бъде пуснат в проект, който няма jQuery.angular.element
) винаги ще използва jQuery, ако то е било заредено! Така че не е необходимо да използваме $
- можем просто да използваме angular.element
.$
- елементът
, който се подава на функцията link
, вече ще бъде елемент на 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;
};
}
};
});
Отново, нещата от шаблона са в шаблона, така че вие (или вашите потребители) можете лесно да го смените с такъв, който отговаря на всеки необходим стил, а логиката никога не е трябвало да бъде докосвана. Многократна употреба - бум!
И все пак има и всички останали предимства, като например тестването - лесно е! Без значение какво'е в шаблона, вътрешният API на директивата'никога не е докосван, така че рефакторингът е лесен. Можете да променяте шаблона, колкото искате, без да докосвате директивата. И без значение какво променяте, тестовете ви все още преминават успешно.
w00t!
И така, ако директивите не са просто колекции от функции, подобни на jQuery, какво са те? Директивите всъщност са разширения на HTML. Ако HTML не прави нещо, от което се нуждаете, пишете директива, която да го направи вместо вас, и след това я използвате така, сякаш е част от HTML.
Казано по друг начин, ако AngularJS не прави нещо от кутията, помислете как екипът ще го постигне, за да се впише точно в ngClick
, ngClass
и др.
Дори не използвайте jQuery. Дори не го включвайте. То ще ви задържи назад. И когато стигнете до проблем, който смятате, че вече знаете как да решите с jQuery, преди да посегнете към $
, опитайте се да помислите как да го направите в рамките на AngularJS. Ако не знаете, попитайте! В 19 от 20 случая най-добрият начин да го направите не се нуждае от jQuery и опитът да го решите с jQuery води до повече работа за вас.
В jQuery селекторите се използват за намиране на DOM елементи и след това за свързване/регистриране на обработчици на събития към тях. Когато се задейства събитие, този (императивен) код се изпълнява, за да актуализира/промени DOM.
В AngularJS трябва да мислите за прегледи, а не за DOM елементи. Изгледите са (декларативни) HTML, които съдържат директиви на AngularJS. Директивите настройват обработката на събитията зад кулисите и ни дават възможност за динамично свързване на данни. Селекторите се използват рядко, така че нуждата от идентификатори (и някои видове класове) е значително намалена. Изгледите са свързани с моделите (чрез обхвата). Изгледите са проекция на модела. Събитията променят моделите (т.е. данните, свойствата на обхвата), а изгледите, които проектират тези модели, се актуализират "автоматично."
В AngularJS мислете за модели, а не за избрани от jQuery DOM елементи, които съхраняват вашите данни. Мислете за изгледите като за проекции на тези модели, а не като за регистриране на обратни повиквания за манипулиране на това, което вижда потребителят.
jQuery използва ненатрапчив JavaScript - поведението (JavaScript) е отделено от структурата (HTML).
AngularJS използва контролери и директиви (всяка от които може да има собствен контролер и/или функции за компилиране и свързване), за да отдели поведението от изгледа/структурата (HTML). Angular също така разполага с услуги и филтри, които помагат за разделянето/организирането на вашето приложение.
Вижте също https://stackoverflow.com/a/14346528/215945
Един от подходите за проектиране на AngularJS приложение:
Можете да направите много с jQuery, без да знаете как работи прототипното наследяване на JavaScript. Когато разработвате AngularJS приложения, ще избегнете някои често срещани капани, ако имате добри познания за наследяването на JavaScript. Препоръчително четиво: https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs
Можете ли да опишете промяната на парадигмата, която е необходима?
Императивна срещу декларативна
С jQuery казвате на DOM какво трябва да се случи, стъпка по стъпка. При AngularJS описвате какви резултати искате, но не и как да ги постигнете. Повече за това тук. Също така, вижте отговора на Mark Rajcok'.
Как да проектирам по различен начин уеб приложения от страна на клиента?
AngularJS е цяла клиентска рамка, която използва модела MVC (разгледайте тяхното графично представяне). Тя се фокусира в голяма степен върху разделянето на проблемите.
Каква е най-голямата разлика? Какво трябва да спра да правя/използвам; какво трябва да започна да правя/използвам вместо това?
jQuery е библиотека
AngularJS е красива фреймуърк система от страна на клиента, която може да се тества и съчетава в себе си много интересни неща, като MVC, dependency injection, свързване на данни и много други.
Тя се фокусира върху разделянето на проблемите и тестването (тестване на единици и тестване от край до край), което улеснява разработката, ръководена от тестове.
Най-добрият начин да започнете е да преминете през техния страхотен урок. Можете да преминете през стъпките за няколко часа; ако обаче искате да овладеете концепциите зад кулисите, те включват безброй препратки за по-нататъшно четене.
Има ли някакви съображения/ограничения от страна на сървъра?
Можете да го използвате в съществуващи приложения, в които вече използвате чисто jQuery. Въпреки това, ако искате да се възползвате напълно от функциите на AngularJS, може да обмислите кодиране на сървърната страна, като използвате RESTful подход.
По този начин ще можете да използвате тяхната фабрика за ресурси, която създава абстракция на вашия RESTful API от страна на сървъра и прави повикванията от страна на сървъра (получаване, запазване, изтриване и т.н.) изключително лесни.