Supponiamo che io abbia familiarità con lo sviluppo di applicazioni lato client in jQuery, ma ora vorrei iniziare a usare AngularJS. Puoi descrivere il cambio di paradigma che è necessario? Ecco alcune domande che potrebbero aiutarti a inquadrare una risposta:
Non sto cercando un confronto dettagliato tra jQuery
e AngularJS
.
In jQuery, si progetta una pagina, e poi la si rende dinamica. Questo perché jQuery è stato progettato per l'ampliamento ed è cresciuto incredibilmente da questa semplice premessa. Ma in AngularJS, dovete iniziare da zero con la vostra architettura in mente. Invece di iniziare pensando "Ho questo pezzo del DOM e voglio fargli fare X", devi iniziare con quello che vuoi realizzare, poi progettare la tua applicazione, e infine progettare la tua vista.
Allo stesso modo, non iniziare con l'idea che jQuery fa X, Y, e Z, quindi aggiungerò solo AngularJS per modelli e controller. Questo è vero allettante quando si inizia, ed è per questo che raccomando sempre ai nuovi sviluppatori di AngularJS di non usare affatto jQuery, almeno fino a quando non si abituano a fare le cose in modo angolare.
Ho visto molti sviluppatori qui e sulla mailing list creare queste elaborate soluzioni con plugin jQuery di 150 o 200 linee di codice che poi incollano in AngularJS con un insieme di callback e $apply
che sono confusi e contorti; ma alla fine riescono a farlo funzionare! Il problema è che nella maggior parte dei casi quel plugin jQuery potrebbe essere riscritto in AngularJS in una frazione del codice, dove improvvisamente tutto diventa comprensibile e semplice.
La linea di fondo è questa: quando si trova una soluzione, prima "pensa in AngularJS"; se non riesci a pensare ad una soluzione, chiedi alla comunità; se dopo tutto questo non c'è una soluzione facile, allora sentiti libero di raggiungere la jQuery. Ma non lasciare che jQuery diventi una stampella o non padroneggerai mai AngularJS.
Per prima cosa sappiate che le applicazioni a pagina singola sono applicazioni. Non sono non pagine web. Quindi dobbiamo pensare come uno sviluppatore lato server oltre a pensare come uno sviluppatore lato client. Dobbiamo pensare a come dividere la nostra applicazione in componenti individuali, estensibili e testabili. Allora come lo fate? Come si "pensa in AngularJS"? Ecco alcuni principi generali, in contrasto con jQuery.
In jQuery, cambiamo programmaticamente la vista. Potremmo avere un menu a tendina definito come un ul
come questo:
<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, nella nostra logica applicativa, lo attiveremmo con qualcosa come:
$('.main-menu').dropdownMenu();
Quando guardiamo solo la vista, non è immediatamente ovvio che ci sia qualche funzionalità qui. Per piccole applicazioni, questo va bene. Ma per applicazioni non banali, le cose diventano rapidamente confuse e difficili da mantenere.
In AngularJS, però, la vista è il record ufficiale della funzionalità basata sulla vista. La nostra dichiarazione ul
sarebbe invece come questa:
<ul class="main-menu" dropdown-menu>
...
</ul>
Questi due fanno la stessa cosa, ma nella versione AngularJS chiunque guardi il template sa cosa dovrebbe succedere. Ogni volta che un nuovo membro del team di sviluppo sale a bordo, può guardare questo e quindi sapere che c'è una direttiva chiamata dropdownMenu
che opera su di esso; non ha bisogno di intuire la risposta giusta o setacciare il codice. La vista ci ha detto cosa doveva succedere. Molto più pulito.
Gli sviluppatori nuovi di AngularJS spesso fanno una domanda come: come faccio a trovare tutti i link di un tipo specifico e ad aggiungere una direttiva su di essi. Lo sviluppatore è sempre sbalordito quando gli rispondiamo: non lo fai. Ma la ragione per cui non lo fai è che questo è come metà jQuery, metà AngularJS, e non va bene. Il problema qui è che lo sviluppatore sta cercando di "fare jQuery" nel contesto di AngularJS. Questo non funzionerà mai bene. La vista è il record ufficiale. Al di fuori di una direttiva (più avanti), non si cambia mai, mai, mai il DOM. E le direttive sono applicate nella vista, quindi l'intento è chiaro.
Ricorda: non progettare e poi registrare. Devi architettare, e poi progettare.
Questa è di gran lunga una delle caratteristiche più impressionanti di AngularJS e taglia fuori un sacco di necessità di fare il tipo di manipolazioni DOM che ho menzionato nella sezione precedente. AngularJS aggiornerà automaticamente la tua vista in modo che tu non debba farlo! In jQuery, rispondiamo agli eventi e poi aggiorniamo il contenuto. Qualcosa come:
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
Per una vista che assomiglia a questa:
<ul class="messages" id="log">
</ul>
Oltre a mescolare le preoccupazioni, abbiamo anche gli stessi problemi di significare l'intento che ho menzionato prima. Ma ancora più importante, abbiamo dovuto fare riferimento e aggiornare manualmente un nodo DOM. E se vogliamo cancellare una voce del registro, dobbiamo codificare contro il DOM anche per questo. Come testiamo la logica oltre al DOM? E se vogliamo cambiare la presentazione? Questo è un po' disordinato e un po' fragile. Ma in AngularJS, possiamo fare questo:
$http( '/myEndpoint.json' ).then( function ( response ) {
$scope.log.push( { msg: 'Data Received!' } );
});
E la nostra vista può apparire così:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
Ma per quello che conta, la nostra vista potrebbe apparire così:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
E ora invece di usare una lista non ordinata, stiamo usando le caselle di avviso di Bootstrap. E non abbiamo mai dovuto cambiare il codice del controller! Ma ancora più importante, non importa dove o come il registro venga aggiornato, anche la vista cambierà. Automaticamente. Bello!
Anche se non l'ho mostrato qui, il data binding è bidirezionale. Quindi quei messaggi di log potrebbero anche essere modificabili nella vista semplicemente facendo così: <input ng-model="entry.msg" />
. E ci fu un gran tripudio.
In jQuery, il DOM è un po' come il modello. Ma in AngularJS, abbiamo un livello di modello separato che possiamo gestire in qualsiasi modo vogliamo, completamente indipendente dalla vista. Questo aiuta per il data binding di cui sopra, mantiene la separazione delle preoccupazioni, e introduce una testabilità molto maggiore. Altre risposte hanno menzionato questo punto, quindi mi limiterò a questo.
E tutto quanto detto sopra si lega a questo tema generale: tenete separate le vostre preoccupazioni. La tua vista agisce come la registrazione ufficiale di ciò che dovrebbe accadere (per la maggior parte); il tuo modello rappresenta i tuoi dati; hai un livello di servizio per eseguire compiti riutilizzabili; fai la manipolazione del DOM e aumenta la tua vista con le direttive; e incolli tutto insieme con i controller. Questo è stato menzionato anche in altre risposte, e l'unica cosa che vorrei aggiungere riguarda la testabilità, che discuto in un'altra sezione più avanti.
Per aiutarci con la separazione delle preoccupazioni c'è la dependency injection (DI). Se vieni da un linguaggio lato server (da Java a PHP) probabilmente hai già familiarità con questo concetto, ma se sei un ragazzo lato client che viene da jQuery, questo concetto può sembrare da stupido a superfluo a hipster. Ma non lo è. :-) Da una prospettiva ampia, DI significa che si possono dichiarare componenti molto liberamente e poi da qualsiasi altro componente, basta chiedere un'istanza di esso e sarà concesso. Non c'è bisogno di conoscere l'ordine di caricamento, o la posizione dei file, o qualcosa del genere. Il potere potrebbe non essere immediatamente visibile, ma fornirò solo un esempio (comune): i test. Diciamo che nella nostra applicazione, abbiamo bisogno di un servizio che implementa l'archiviazione lato server attraverso un'API REST e, a seconda dello stato dell'applicazione, anche l'archiviazione locale. Quando eseguiamo i test sui nostri controller, non vogliamo dover comunicare con il server - stiamo testando il controller, dopo tutto. Possiamo semplicemente aggiungere un finto servizio con lo stesso nome del nostro componente originale, e l'iniettore farà in modo che il nostro controller riceva automaticamente quello finto - il nostro controller non sa e non deve sapere la differenza. Parlando di test...
Questo è in realtà parte della sezione 3 sull'architettura, ma è così importante che la sto mettendo come propria sezione di primo livello. Di tutti i molti plugin jQuery che hai visto, usato o scritto, quanti di loro hanno una suite di test di accompagnamento? Non molti, perché jQuery non è molto adatta a questo. Ma AngularJS lo è. In jQuery, l'unico modo per testare è spesso quello di creare il componente in modo indipendente con una pagina campione/demo contro la quale i nostri test possono eseguire la manipolazione del DOM. Quindi dobbiamo sviluppare un componente separatamente e poi integrarlo nella nostra applicazione. Che scomodità! Così il più delle volte, quando sviluppiamo con jQuery, optiamo per uno sviluppo iterativo invece che test-driven. E chi potrebbe biasimarci? Ma poiché abbiamo la separazione delle preoccupazioni, possiamo fare uno sviluppo test-driven iterativo in AngularJS! Per esempio, diciamo che vogliamo una direttiva super-semplice per indicare nel nostro menu qual è il nostro percorso attuale. Possiamo dichiarare ciò che vogliamo nella vista della nostra applicazione:
<a href="/hello" when-active>Hello</a>
Ok, ora possiamo scrivere un test per la direttiva inesistente 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();
}));
E quando eseguiamo il nostro test, possiamo confermare che fallisce. Solo ora dovremmo creare la nostra direttiva:
.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' );
}
});
}
};
});
Il nostro test ora passa e il nostro menu funziona come richiesto. Il nostro sviluppo è tutto iterativo e guidato dai test. Fantastico.
Si sente spesso dire "fare solo manipolazioni DOM in una direttiva". Questa è una necessità. Trattatela con la dovuta deferenza!
Ma tuffiamoci un po' più a fondo...
Alcune direttive si limitano a decorare ciò che c'è già nella vista (si pensi a ngClass
) e quindi a volte fanno subito una manipolazione DOM e poi sono praticamente fatte. Ma se una direttiva è come un "widget" e ha un template, dovrebbe anche rispettare la separazione delle preoccupazioni. Cioè, il template anche dovrebbe rimanere largamente indipendente dalla sua implementazione nelle funzioni link e controller.
AngularJS viene fornito con un intero set di strumenti per rendere questo molto facile; con ngClass
possiamo aggiornare dinamicamente la classe; ngModel
permette il binding dei dati a due vie; ngShow
e ngHide
mostrano o nascondono programmaticamente un elemento; e molti altri - inclusi quelli che scriviamo noi stessi. In altre parole, possiamo fare ogni tipo di meraviglia senza manipolazione del DOM. Meno manipolazione del DOM, più le direttive sono facili da testare, più sono facili da stilizzare, più sono facili da cambiare in futuro, e più sono riutilizzabili e distribuibili.
Vedo molti sviluppatori alle prime armi con AngularJS che usano le direttive come un posto dove lanciare un mucchio di jQuery. In altre parole, pensano "visto che non posso fare la manipolazione del DOM nel controller, prendo quel codice e lo metto in una direttiva". Mentre questo è certamente molto meglio, è spesso ancora sbagliato.
Pensate al logger che abbiamo programmato nella sezione 3. Anche se lo mettiamo in una direttiva, vogliamo ancora farlo nel modo "Angular Way". Non richiede alcuna manipolazione del DOM! Ci sono molte volte in cui la manipolazione del DOM è necessaria, ma è molto più rara di quanto si pensi! Prima di fare manipolazioni DOM ovunque nella tua applicazione, chiediti se ne hai davvero bisogno. Potrebbe esserci un modo migliore.
Ecco un rapido esempio che mostra il modello che vedo più frequentemente. Vogliamo un pulsante alternabile. (Nota: questo esempio è un po' artificioso e un po' prolisso per rappresentare casi più complicati che sono risolti esattamente nello stesso modo).
.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);
});
}
};
});
Ci sono alcune cose sbagliate in questo:
angular.element
e il nostro componente funzionerà ancora quando verrà lasciato in un progetto che non ha jQuery.angular.element
) userà sempre jQuery se è stata caricata! Quindi non abbiamo bisogno di usare il $
- possiamo semplicemente usare angular.element
.$
- l' elemento
che viene passato alla funzione link
sarebbe già un elemento 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;
};
}
};
});
Di nuovo, la roba del template è nel template, quindi voi (o i vostri utenti) potete facilmente sostituirla con una che soddisfi qualsiasi stile necessario, e la logica non ha mai dovuto essere toccata. Riusabilità - boom!
E ci sono ancora tutti gli altri vantaggi, come i test - è facile! Non importa cosa ci sia nel template, l'API interna della direttiva non viene mai toccata, quindi il refactoring è facile. Puoi cambiare il template quanto vuoi senza toccare la direttiva. E non importa cosa cambiate, i vostri test continuano a passare.
w00t!
Quindi, se le direttive non sono solo collezioni di funzioni simili a jQuery, cosa sono? Le direttive sono in realtà estensioni dell'HTML. Se l'HTML non fa qualcosa che ti serve, scrivi una direttiva che lo faccia per te, e poi la usi proprio come se fosse parte dell'HTML.
Detto in un altro modo, se AngularJS non fa qualcosa fuori dalla scatola, pensa a come il team lo realizzerebbe per adattarlo con ngClick
, ngClass
, et al.
Non usare nemmeno jQuery. Non includerla nemmeno. Vi frenerà. E quando ti imbatti in un problema che pensi di sapere già come risolvere con jQuery, prima di prendere il $
, prova a pensare a come farlo entro i confini di AngularJS. Se non lo sai, chiedi! 19 volte su 20, il modo migliore per farlo non ha bisogno di jQuery e cercare di risolverlo con jQuery si traduce in più lavoro per voi.
In jQuery, i selettori sono usati per trovare elementi del DOM e poi legare/registrare gestori di eventi ad essi. Quando un evento si innesca, quel codice (imperativo) viene eseguito per aggiornare/cambiare il DOM.
In AngularJS, si vuole pensare alle views piuttosto che agli elementi DOM. Le viste sono HTML (dichiarativo) che contengono le direttive di AngularJS. Le direttive impostano i gestori di eventi dietro le quinte per noi e ci danno un databinding dinamico. I selettori sono usati raramente, quindi la necessità degli ID (e di alcuni tipi di classi) è molto diminuita. Le viste sono legate ai modelli (tramite gli ambiti). Le viste sono una proiezione del modello. Gli eventi cambiano i modelli (cioè i dati, le proprietà dell'ambito), e le viste che proiettano quei modelli si aggiornano "automaticamente."
In AngularJS, pensate ai modelli, piuttosto che agli elementi DOM selezionati da jQuery che contengono i vostri dati. Pensate alle viste come proiezioni di quei modelli, piuttosto che registrare callback per manipolare ciò che l'utente vede.
jQuery impiega unobtrusive JavaScript - il comportamento (JavaScript) è separato dalla struttura (HTML).
AngularJS usa controllori e direttive (ognuno dei quali può avere un proprio controllore, e/o funzioni di compilazione e collegamento) per rimuovere il comportamento dalla vista/struttura (HTML). Angular ha anche servizi e filtri per aiutare a separare/organizzare la vostra applicazione.
Vedi anche https://stackoverflow.com/a/14346528/215945
Un approccio alla progettazione di un'applicazione AngularJS:
Si può fare molto con jQuery senza sapere come funziona l'ereditarietà prototipale di JavaScript. Quando si sviluppano applicazioni AngularJS, si evitano alcune insidie comuni se si ha una buona comprensione dell'ereditarietà JavaScript. Lettura consigliata: https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs
Può descrivere il cambiamento di paradigma che è necessario?
Imperativo vs Dichiarativo
Con jQuery dici al DOM cosa deve succedere, passo dopo passo. Con AngularJS descrivi quali risultati vuoi ma non come farlo. Più su questo qui. Inoltre, controlla la risposta di Mark Rajcok.
Come posso architettare e progettare diversamente le app web lato client?
AngularJS è un intero framework lato client che utilizza il pattern MVC (controlla la loro rappresentazione grafica). Si concentra molto sulla separazione delle preoccupazioni.
Qual è la più grande differenza? Cosa dovrei smettere di fare/usare; cosa dovrei invece iniziare a fare/usare?
jQuery è una libreria
AngularJS è un bellissimo framework lato client, altamente testabile, che combina tonnellate di cose interessanti come MVC, dependency injection, data binding e molto altro.
Si concentra sulla separazione delle preoccupazioni e sui test (unit testing e test end-to-end), il che facilita lo sviluppo test-driven.
Il modo migliore per iniziare è passare attraverso il loro fantastico tutorial. Potete passare attraverso i passi in un paio d'ore; tuttavia, nel caso vogliate padroneggiare i concetti dietro le quinte, includono una miriade di riferimenti per ulteriori letture.
Ci sono considerazioni/restrizioni lato server?
Si può usare su applicazioni esistenti dove si sta già usando jQuery puro. Tuttavia, se vuoi sfruttare appieno le caratteristiche di AngularJS puoi considerare di codificare il lato server usando un approccio RESTful.
In questo modo potrete sfruttare il loro resource factory, che crea un'astrazione del vostro lato server RESTful API e rende le chiamate lato server (get, save, delete, ecc.) incredibilmente facili.