Stel dat ik'bekend ben met het ontwikkelen van client-side applicaties in jQuery, maar nu'zou ik graag AngularJS willen gaan gebruiken. Kunt u de paradigmaverschuiving beschrijven die nodig is? Hier zijn een paar vragen die u kunnen helpen een antwoord te formuleren:
Ik'ben niet op zoek naar een gedetailleerde vergelijking tussen jQuery
en AngularJS
.
In jQuery, ontwerp je een pagina, en dan maak je hem dynamisch. Dit komt omdat jQuery is ontworpen voor augmentatie en ongelooflijk is gegroeid vanuit die eenvoudige premisse. Maar in AngularJS, moet je vanaf de grond beginnen met je architectuur in gedachten. In plaats van te beginnen met te denken "Ik heb dit stukje van het DOM en ik wil het X laten doen", moet je beginnen met wat je wil bereiken, dan je applicatie gaan ontwerpen, en dan tenslotte je view gaan ontwerpen.
Op dezelfde manier, begin niet met het idee dat jQuery X, Y, en Z doet, dus ik'zal gewoon AngularJS daarbovenop toevoegen voor modellen en controllers. Dit is echt verleidelijk als je'net begint, en daarom raad ik nieuwe AngularJS ontwikkelaars altijd aan om helemaal geen jQuery te gebruiken, tenminste totdat ze gewend zijn om dingen op de "Angular Way" te doen.
Ik'heb veel ontwikkelaars hier en op de mailing list deze uitgebreide oplossingen zien maken met jQuery plugins van 150 of 200 regels code die ze dan in AngularJS lijmen met een verzameling callbacks en $apply
s die verwarrend en ingewikkeld zijn; maar uiteindelijk krijgen ze het aan de praat! Het probleem is dat in meestal gevallen die jQuery plugin in AngularJS herschreven zou kunnen worden in een fractie van de code, waarbij alles ineens begrijpelijk en rechttoe rechtaan wordt.
De bottom line is deze: bij het oplossen, eerst "denken in AngularJS"; als je'geen oplossing kunt bedenken, vraag het dan aan de community; als er na dat alles nog geen makkelijke oplossing is, dan grijp gerust naar de jQuery. Maar laat jQuery geen kruk worden of je zult AngularJS nooit onder de knie krijgen.
Weet eerst dat single-page applicaties applicaties zijn. Ze'zijn geen webpagina's. Dus we moeten denken als een server-side ontwikkelaar naast het denken als een client-side ontwikkelaar. We moeten nadenken over hoe we onze applicatie kunnen verdelen in individuele, uitbreidbare, testbare componenten. Maar hoe doe je dat dan? Hoe "denk je in AngularJS"? Hier zijn wat algemene principes, afgezet tegen jQuery.
In jQuery, we programmatisch wijzigen van de weergave. We zouden een dropdown menu kunnen definiëren als een ul
zoals dit:
<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>
In jQuery, in onze applicatie logica, zouden we het activeren met iets als:
$('.main-menu').dropdownMenu();
Als we alleen maar naar de view kijken, is het's niet meteen duidelijk dat hier enige functionaliteit is. Voor kleine toepassingen, is dat's prima. Maar voor niet-triviale toepassingen worden de dingen snel verwarrend en moeilijk te onderhouden.
In AngularJS, echter, is de view de officiële registratie van view-gebaseerde functionaliteit. Onze ul
declaratie zou er dan als volgt uitzien:
<ul class="main-menu" dropdown-menu>
...
</ul>
Deze twee doen hetzelfde, maar in de AngularJS versie weet iedereen die naar de template kijkt wat's verondersteld wordt te gebeuren. Wanneer een nieuw lid van het ontwikkelingsteam aan boord komt, kan ze hier naar kijken en dan weten dat er een directive genaamd dropdownMenu
op werkt; ze hoeft niet het juiste antwoord te intuïtueren of door enige code te ziften. De view vertelde ons wat er moest gebeuren. Veel schoner.
Ontwikkelaars die nieuw zijn met AngularJS stellen vaak een vraag als: hoe vind ik alle links van een bepaald soort en voeg daar een directive aan toe. De ontwikkelaar is altijd verbijsterd als wij antwoorden: dat doe je niet. Maar de reden dat je dat niet doet is dat dit half-jQuery, half-AngularJS is, en niet goed. Het probleem hier is dat de ontwikkelaar probeert "jQuery" te doen in de context van AngularJS. Dat's nooit goed gaan werken. De view is het officiële record. Buiten een directive (meer hierover hieronder), verander je nooit, nooit, nooit de DOM. En directives worden in de view toegepast, dus de bedoeling is duidelijk.
Onthoud: don't ontwerp, en dan mark up. Je moet architect, en dan ontwerpen.
Dit is veruit een van de meest geweldige functies van AngularJS en snijdt veel van de noodzaak weg om het soort DOM manipulaties te doen die ik in de vorige sectie noemde. AngularJS zal automatisch je view updaten zodat jij dat niet hoeft te doen! In jQuery reageren we op gebeurtenissen en werken dan de inhoud bij. Zoiets als:
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
Voor een view die er zo uitziet:
<ul class="messages" id="log">
</ul>
Afgezien van het mengen van zorgen, hebben we ook dezelfde problemen met het aangeven van de bedoeling die ik eerder noemde. Maar wat belangrijker is, we moesten handmatig verwijzen naar een DOM node en deze bijwerken. En als we een logboek item willen verwijderen, moeten we daarvoor ook coderen tegen het DOM. Hoe testen we de logica los van de DOM? En wat als we de presentatie willen veranderen? Dit is een beetje rommelig en een beetje broos. Maar in AngularJS kunnen we dit doen:
$http( '/myEndpoint.json' ).then( function ( response ) {
$scope.log.push( { msg: 'Data Received!' } );
});
En onze view kan er zo uitzien:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
Maar wat dat betreft, kan onze view er ook zo uitzien:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
En nu gebruiken we in plaats van een ongeordende lijst, Bootstrap waarschuwingsvakken. En we hoefden nooit de controller code te veranderen! Maar nog belangrijker, ongeacht waar of hoe het logboek wordt bijgewerkt, het uitzicht zal ook veranderen. Automatisch. Gaaf!
Hoewel ik het hier niet heb laten zien, is de data binding twee kanten op. Dus die logberichten zouden ook in de view kunnen worden aangepast door gewoon dit te doen: <input ng-model="entry.msg" />
. En er was veel vreugde.
In jQuery, is het DOM een soort van model. Maar in AngularJS, hebben we een aparte model laag die we kunnen beheren op elke manier die we willen, volledig onafhankelijk van de view. Dit helpt voor de bovengenoemde gegevensbinding, onderhoudt scheiding van belangen, en introduceert veel grotere testbaarheid. Andere antwoorden noemden dit punt al, dus ik'laat het hier maar bij.
En al het bovenstaande sluit aan bij dit overkoepelende thema: houd je zorgen gescheiden. Je view fungeert als het officiële verslag van wat er zou moeten gebeuren (voor het grootste deel); je model vertegenwoordigt je gegevens; je hebt een service laag om herbruikbare taken uit te voeren; je doet DOM manipulatie en vergroot je view met directives; en je lijmt het allemaal aan elkaar met controllers. Dit werd ook vermeld in andere antwoorden, en het enige wat ik zou willen toevoegen heeft te maken met testbaarheid, wat ik bespreek in een andere sectie hieronder.
Om ons te helpen met het scheiden van zorgen is dependency injection (DI). Als je uit een server-side taal komt (van Java tot PHP) ben je'waarschijnlijk al bekend met dit concept, maar als je'een client-side kerel bent die uit jQuery komt, kan dit concept alles lijken van dom tot overbodig tot hipster. Maar dat is het niet. :-) Vanuit een breed perspectief betekent DI dat je componenten heel vrij kunt declareren en dan vanuit elk ander component gewoon om een instantie ervan kunt vragen en die zal worden toegekend. Je hoeft niets te weten over laadvolgorde, of bestandslocaties, of iets dergelijks. De kracht is misschien niet meteen zichtbaar, maar ik'zal maar een (veelvoorkomend) voorbeeld geven: testen. Laten's zeggen dat we in onze applicatie een service nodig hebben die server-side opslag implementeert via een REST API en, afhankelijk van de applicatiestatus, ook lokale opslag. Bij het uitvoeren van tests op onze controllers willen we niet met de server hoeven te communiceren - we testen tenslotte de controller. We kunnen gewoon een mock service toevoegen met dezelfde naam als onze originele component, en de injector zal ervoor zorgen dat onze controller de neppe automatisch krijgt - onze controller weet'niet en hoeft'niet het verschil te weten. Over testen gesproken...
Dit is eigenlijk een deel van sectie 3 over architectuur, maar het'is zo belangrijk dat ik het als een eigen top-level sectie zet. Van alle vele jQuery plugins die je'hebt gezien, gebruikt of geschreven, hoeveel van hen hadden een bijbehorende testsuite? Niet veel, want jQuery is daar niet erg vatbaar voor. Maar AngularJS wel. In jQuery is de enige manier om te testen vaak om de component zelfstandig te maken met een voorbeeld/demo pagina waartegen onze tests DOM manipulatie kunnen uitvoeren. Dus dan moeten we een component apart ontwikkelen en dán integreren in onze applicatie. Hoe onhandig! Dus vaak kiezen we bij het ontwikkelen met jQuery voor iteratieve in plaats van test-gedreven ontwikkeling. En wie kan het ons kwalijk nemen? Maar omdat we separation of concerns hebben, kunnen we test-driven ontwikkeling iteratief doen in AngularJS! Laten we bijvoorbeeld zeggen dat we een super-simpele directive willen om in ons menu aan te geven wat onze huidige route is. We kunnen in de view van onze applicatie aangeven wat we willen:
<a href="/hello" when-active>Hello</a>
Okay, nu kunnen we een test schrijven voor de niet bestaande when-active
directive:
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();
}));
En als we onze test uitvoeren, kunnen we bevestigen dat hij faalt. Pas nu moeten we onze directive maken:
.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' );
}
});
}
};
});
Onze test slaagt nu en ons menu werkt zoals gevraagd. Onze ontwikkeling is beiden iteratief en test-gedreven. Wicked-cool.
Je'll hoort vaak "alleen DOM manipulatie doen in een directive". Dit is een noodzaak. Behandel het met gepaste eerbied!
Maar laten we een beetje dieper duiken...
Sommige directives decoreren alleen wat'al in de view zit (denk ngClass
) en doen daarom soms meteen DOM manipulatie en zijn dan in principe klaar. Maar als een directive als een "widget" is en een template heeft, dan moet deze ook de separation of concerns respecteren. Dat wil zeggen dat de template ook grotendeels onafhankelijk moet blijven van zijn implementatie in de link en controller functies.
AngularJS komt met een hele set tools om dit heel gemakkelijk te maken; met ngClass
kunnen we de class dynamisch updaten; ngModel
maakt twee-weg data binding mogelijk; ngShow
en ngHide
laten programmatisch een element zien of verbergen; en nog veel meer - inclusief de tools die we zelf schrijven. Met andere woorden, we kunnen alle soorten van geweldigheid doen zonder DOM manipulatie. Hoe minder DOM manipulatie, hoe makkelijker directives te testen zijn, hoe makkelijker ze te stijlen zijn, hoe makkelijker ze in de toekomst te veranderen zijn, en hoe meer herbruikbaar en distribueerbaar ze zijn.
Ik zie veel ontwikkelaars die nieuw zijn in AngularJS directives gebruiken als de plek om een hoop jQuery te gooien. Met andere woorden, ze denken "aangezien ik geen DOM manipulatie in de controller kan doen, zal ik'die code in een directive zetten". Hoewel dat zeker veel beter is, is het vaak nog steeds verkeerd.
Denk aan de logger die we in sectie 3 hebben geprogrammeerd. Zelfs als we dat in een directive zetten, willen we het nog steeds op de "Angular Way" doen. Er is nog geen DOM manipulatie voor nodig! Er zijn veel momenten waarop DOM manipulatie nodig is, maar het'is een veel zeldzamer dan je denkt! Voordat je overal in je applicatie DOM manipulatie uitvoert, vraag jezelf af of je het echt nodig hebt. Er is misschien een betere manier.
Hier's een snel voorbeeld dat het patroon laat zien dat ik het vaakst zie. We willen een schakelbare knop. (Let op: dit voorbeeld is een beetje gekunsteld en een beetje langdradig om meer ingewikkelde gevallen weer te geven die op precies dezelfde manier worden opgelost).
.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);
});
}
};
});
Hier zijn een paar dingen mis mee:
angular.element
gebruiken en onze component zal nog steeds werken wanneer het in een project wordt gedropt dat's geen jQuery heeft.angular.element
) altijd jQuery gebruiken als het geladen was! Dus we hoeven de $
niet te gebruiken - we kunnen gewoon angular.element
gebruiken.$
- het element
dat wordt doorgegeven aan de link
functie zou altijd een jQuery element zijn!.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;
};
}
};
});
Nogmaals, het template spul zit in het template, dus jij (of je gebruikers) kunnen het gemakkelijk verwisselen voor een die voldoet aan elke stijl die nodig is, en de logica hoefde nooit aangeraakt te worden. Herbruikbaarheid - boem!
En er zijn nog al die andere voordelen, zoals testen - het'is gemakkelijk! Het maakt niet uit wat's in het sjabloon, de richtlijn's interne API wordt nooit aangeraakt, dus refactoring is gemakkelijk. Je kunt het sjabloon zo veel veranderen als je wilt zonder de richtlijn aan te raken. En het maakt niet uit wat je verandert, je tests slagen nog steeds.
w00t!
Dus als directives niet alleen maar verzamelingen van jQuery-achtige functies zijn, wat zijn ze dan? Directives zijn eigenlijk uitbreidingen van HTML. Als HTML iets niet doet wat jij nodig hebt, dan schrijf je een directief om het voor jou te doen, en dan gebruik je het alsof het een deel van HTML is.
Anders gezegd, als AngularJS iets niet uit de doos doet, bedenk dan hoe het team het zou kunnen maken om het in te passen met ngClick
, ngClass
, enzovoort.
Gebruik niet eens jQuery. Gebruik het zelfs niet. Het zal je tegenhouden. En als je bij een probleem komt waarvan je denkt dat je al weet hoe het op te lossen in jQuery, probeer dan, voordat je naar de $
grijpt, te bedenken hoe je het binnen de grenzen van AngularJS kunt doen. Als je het niet weet, vraag het dan! 19 van de 20 keer heeft de beste manier om het te doen geen jQuery nodig en om te proberen het op te lossen met jQuery resulteert in meer werk voor jou.
In jQuery worden selectors gebruikt om DOM elementen te vinden en vervolgens event handlers aan ze te binden/registreren. Wanneer een gebeurtenis triggert, wordt die (imperatieve) code uitgevoerd om het DOM bij te werken/te wijzigen.
In AngularJS moet je denken aan views in plaats van DOM-elementen. Views zijn (declaratieve) HTML die AngularJS directives bevatten. Directives zetten de event handlers achter de schermen voor ons op en geven ons dynamische databinding. Selectors worden zelden gebruikt, dus de behoefte aan IDs (en sommige soorten classes) is sterk verminderd. Views zijn gebonden aan modellen (via scopes). Views zijn een projectie van het model. Gebeurtenissen veranderen modellen (dat wil zeggen, gegevens, scope-eigenschappen), en de views die deze modellen projecteren worden "automatisch." bijgewerkt;
In AngularJS, denk aan modellen, in plaats van jQuery-geselecteerde DOM-elementen die uw gegevens bevatten. Denk aan views als projecties van die modellen, eerder dan callbacks te registreren om te manipuleren wat de gebruiker ziet.
jQuery maakt gebruik van onopvallende JavaScript - gedrag (JavaScript) is gescheiden van de structuur (HTML).
AngularJS gebruikt controllers en directives (die elk hun eigen controller kunnen hebben, en/of compileer- en linking-functies) om gedrag te verwijderen van de view/structuur (HTML). Angular heeft ook services en filters om te helpen bij het scheiden/organiseren van uw applicatie.
Zie ook https://stackoverflow.com/a/14346528/215945
Eén benadering van het ontwerpen van een AngularJS applicatie:
Je kunt veel doen met jQuery zonder te weten hoe JavaScript prototypal inheritance werkt. Bij het ontwikkelen van AngularJS applicaties zul je een aantal veel voorkomende valkuilen vermijden als je een goed begrip hebt van JavaScript inheritance. Aanbevolen lectuur: https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs
Kunt u de paradigmaverschuiving beschrijven die nodig is?
*Imperatief vs Declaratief**
Met jQuery vertel je het DOM wat er moet gebeuren, stap voor stap. Met AngularJS beschrijf je welke resultaten je wilt, maar niet hoe je het moet doen. Meer hierover hier. Kijk ook eens naar Mark Rajcok's antwoord.
Hoe architect en ontwerp ik client-side web apps anders?
AngularJS is een volledig client-side framework dat gebruik maakt van het MVC patroon (bekijk hun grafische voorstelling). Het richt zich sterk op scheiding van belangen.
Wat is het grootste verschil? Wat moet ik stoppen met doen/gebruiken; wat moet ik in plaats daarvan gaan doen/gebruiken?
jQuery is een bibliotheek
AngularJS is een prachtig client-side framework, zeer testbaar, dat tonnen coole dingen combineert, zoals MVC, dependency injection, data binding en nog veel meer.
Het focust op separation of concerns en testen (unit testing en end-to-end testing), wat test-driven development vergemakkelijkt.
De beste manier om te beginnen is door hun geweldige tutorial te doorlopen. Je kunt de stappen in een paar uur doorlopen; maar voor het geval je de concepten achter de schermen onder de knie wilt krijgen, voegen ze een groot aantal referenties toe voor verder lezen.
Zijn er server-side overwegingen/beperkingen?
U kunt het gebruiken op bestaande toepassingen waar u al gebruik maken van pure jQuery. Echter, als u volledig wilt profiteren van de AngularJS-functies kunt u overwegen om de server-kant te coderen met behulp van een RESTful aanpak.
Door dit te doen kunt u gebruik maken van hun resource factory, die een abstractie van uw server side RESTful API creëert en server-side calls (get, save, delete, etc.) ongelooflijk eenvoudig maakt.