Un tutorial passo dopo passo per la tua prima app AngularJS

Pubblicato: 2022-03-11
AngularJS si è evoluto ed è diventato ancora migliore. Ora chiamato Angular, è stato interamente riscritto per un nuovo flusso di lavoro di sviluppo. Dai un'occhiata al nostro nuovo tutorial di Angular 5 e anche al più recente tutorial full-stack di Angular 6, con Material e Firebase.

Cos'è AngularJS?

AngularJS è un framework MVC JavaScript sviluppato da Google che ti consente di creare applicazioni front-end ben strutturate, facilmente testabili e gestibili.

E perché dovrei usarlo?

Se non hai ancora provato AngularJS, ti stai perdendo. Il framework è costituito da un set di strumenti strettamente integrato che ti aiuterà a creare applicazioni lato client ben strutturate e ricche in modo modulare, con meno codice e maggiore flessibilità.

AngularJS estende l'HTML fornendo direttive che aggiungono funzionalità al tuo markup e ti consentono di creare potenti modelli dinamici. Puoi anche creare le tue direttive, creando componenti riutilizzabili che soddisfino le tue esigenze e astraendo tutta la logica di manipolazione del DOM.

Implementa anche il data binding a due vie, collegando il tuo HTML (viste) ai tuoi oggetti JavaScript (modelli) senza problemi. In parole povere, questo significa che qualsiasi aggiornamento sul tuo modello si rifletterà immediatamente nella tua vista senza la necessità di alcuna manipolazione DOM o gestione di eventi (ad esempio, con jQuery).

Angular fornisce servizi oltre a XHR che semplificano notevolmente il codice e consentono di astrarre le chiamate API in servizi riutilizzabili. Con ciò, puoi spostare il tuo modello e la tua logica aziendale nel front-end e creare app Web indipendenti dal back-end.

Infine, adoro Angular per la sua flessibilità per quanto riguarda la comunicazione con il server. Come la maggior parte dei framework MVC JavaScript, ti consente di lavorare con qualsiasi tecnologia lato server purché possa servire la tua app tramite un'API Web RESTful. Ma Angular fornisce anche servizi oltre a XHR che semplificano notevolmente il codice e consentono di astrarre le chiamate API in servizi riutilizzabili. Di conseguenza, puoi spostare il tuo modello e la tua logica aziendale nel front-end e creare app Web indipendenti dal back-end. In questo post, faremo proprio questo, un passo alla volta.

Allora, da dove comincio?

Innanzitutto, decidiamo la natura dell'app che vogliamo creare. In questa guida, preferiremmo non dedicare troppo tempo al back-end, quindi scriveremo qualcosa sulla base di dati facilmente raggiungibili su Internet, come un'app di feed sportivi!

Dato che sono un grande fan delle corse automobilistiche e della Formula 1, userò un servizio API di autosport per fungere da back-end. Fortunatamente, i ragazzi di Ergast sono così gentili da fornire un'API gratuita per gli sport motoristici che sarà perfetta per noi.

Per un'anteprima di ciò che costruiremo, dai un'occhiata alla demo dal vivo. Per abbellire la demo e mostrare alcuni modelli Angular, ho applicato un tema Bootstrap da WrapBootstrap, ma visto che questo articolo non riguarda i CSS, lo astrarrò dagli esempi e lo tralascerò.

Esercitazione introduttiva

Diamo il via alla nostra app di esempio con alcuni standard. Raccomando il progetto angular-seed in quanto non solo fornisce un ottimo scheletro per il bootstrap, ma prepara anche il terreno per i test unitari con Karma e Jasmine (non faremo alcun test in questa demo, quindi faremo solo lascia queste cose da parte per ora; vedere la parte 2 di questo tutorial per ulteriori informazioni sull'impostazione del progetto per i test unitari e end-to-end).

EDIT (maggio 2014): da quando ho scritto questo tutorial, il progetto angular-seed ha subito alcune pesanti modifiche (inclusa l'aggiunta di Bower come gestore di pacchetti). In caso di dubbi su come implementare il progetto, dai una rapida occhiata alla prima sezione della loro guida di riferimento. Nella parte 2 di questo tutorial, Bower, tra gli altri strumenti, è trattato in modo più dettagliato.

OK, ora che abbiamo clonato il repository e installato le dipendenze, lo scheletro della nostra app sarà simile a questo:

angularjs tutorial: inizia con lo scheletro

Ora possiamo iniziare a codificare. Mentre stiamo cercando di creare un feed sportivo per un campionato di corse, iniziamo con la vista più rilevante: la classifica del campionato .

la classifica del campionato

Dato che abbiamo già un elenco di driver definito nel nostro ambito (aspetta con me - ci arriveremo) e ignorando qualsiasi CSS (per leggibilità), il nostro HTML potrebbe assomigliare a:

 <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>

La prima cosa che noterai in questo modello è l'uso di espressioni ("{{" e "}}") per restituire i valori delle variabili. Nello sviluppo di AngularJS, le espressioni consentono di eseguire alcuni calcoli per restituire un valore desiderato. Alcune espressioni valide sarebbero:

  • {{ 1 + 1 }}
  • {{ 946757880 | date }}
  • {{ user.name }}

In effetti, le espressioni sono snippet simili a JavaScript. Ma nonostante sia molto potente, non dovresti usare espressioni per implementare una logica di livello superiore. Per questo, utilizziamo le direttive.

Comprensione delle Direttive di Base

La seconda cosa che noterai è la presenza di ng-attributes , che non vedresti nel tipico markup. Quelle sono direttive.

Ad alto livello, le direttive sono indicatori (come attributi, tag e nomi di classi) che dicono ad AngularJS di allegare un determinato comportamento a un elemento DOM (o trasformarlo, sostituirlo, ecc.). Diamo un'occhiata a quelli che abbiamo già visto:

  • La direttiva ng-app è responsabile del bootstrap dell'app definendone l'ambito. In AngularJS, puoi avere più app all'interno della stessa pagina, quindi questa direttiva definisce dove inizia e finisce ogni app distinta.

  • La direttiva ng-controller definisce quale controller sarà responsabile della tua vista. In questo caso, indichiamo driversController , che fornirà il nostro elenco di driver ( driversList ).

  • La direttiva ng-repeat è una delle più comunemente utilizzate e serve a definire l'ambito del modello durante il ciclo delle raccolte. Nell'esempio precedente, replica una riga nella tabella per ogni driver in driversList .

Aggiunta di controller

Naturalmente, non serve la nostra vista senza un controller. Aggiungiamo driversController al nostro controllers.js:

 angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });

Potresti aver notato la variabile $scope che stiamo passando come parametro al controller. La variabile $scope dovrebbe collegare il controller e le viste. In particolare, contiene tutti i dati che verranno utilizzati all'interno del tuo modello. Tutto ciò che aggiungi ad esso (come l'elenco dei driversList nell'esempio sopra) sarà direttamente accessibile nelle tue viste. Per ora, lavoriamo solo con un array di dati fittizio (statico), che sostituiremo in seguito con il nostro servizio API.

Ora aggiungi questo ad app.js:

 angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);

Con questa riga di codice, in realtà inizializziamo la nostra app e registriamo i moduli da cui dipende. Torneremo su quel file ( app.js ) in seguito.

Ora mettiamo tutto insieme in index.html :

 <!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>

Modulo errori minori, ora puoi avviare la tua app e controllare il tuo elenco (statico) di driver.

Nota: se hai bisogno di aiuto per eseguire il debug della tua app e visualizzare i tuoi modelli e l'ambito all'interno del browser, ti consiglio di dare un'occhiata al fantastico plug-in Batarang per Chrome.

Caricamento di dati dal server

Poiché sappiamo già come visualizzare i dati del nostro controller nella nostra vista, è ora di recuperare effettivamente i dati in tempo reale da un server RESTful.

Per facilitare la comunicazione con i server HTTP, AngularJS fornisce i servizi $http e $resource . Il primo non è che un livello sopra XMLHttpRequest o JSONP, mentre il secondo fornisce un livello di astrazione più elevato. Useremo $http .

Per astrarre le nostre chiamate API del server dal controller, creiamo il nostro servizio personalizzato che recupererà i nostri dati e fungerà da wrapper attorno $http aggiungendo questo a our services.js :

 angular.module('F1FeederApp.services', []). factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } return ergastAPI; });

Con le prime due righe creiamo un nuovo modulo ( F1FeederApp.services ) e registriamo un servizio all'interno di quel modulo ( ergastAPIservice ). Si noti che si passa $http come parametro a quel servizio. Questo dice al motore di iniezione delle dipendenze di Angular che il nostro nuovo servizio richiede (o dipende da ) il servizio $http .

In modo simile, dobbiamo dire ad Angular di includere il nostro nuovo modulo nella nostra app. Registriamolo con app.js , sostituendo il nostro codice esistente con:

 angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);

Ora, tutto ciò che dobbiamo fare è modificare un po' il nostro controller.js , includere ergastAPIservice come dipendenza e saremo a posto:

 angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; ergastAPIservice.getDrivers().success(function (response) { //Dig into the responde to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); });

Ora ricarica l'app e controlla il risultato. Si noti che non abbiamo apportato modifiche al nostro modello, ma abbiamo aggiunto una variabile nameFilter al nostro ambito. Mettiamo quella variabile da usare.

Filtri

Grande! Abbiamo un controller funzionale. Ma mostra solo un elenco di driver. Aggiungiamo alcune funzionalità implementando un semplice input di ricerca di testo che filtrerà il nostro elenco. Aggiungiamo la seguente riga al nostro index.html , proprio sotto il tag <body> :

 <input type="text" ng-model="nameFilter" placeholder="Search..."/>

Ora stiamo facendo uso della direttiva ng-model . Questa direttiva associa il nostro campo di testo alla variabile $scope.nameFilter e fa in modo che il suo valore sia sempre aggiornato con il valore di input. Ora visitiamo index.html ancora una volta e apportiamo una piccola modifica alla riga che contiene la direttiva ng-repeat :

 <tr ng-repeat="driver in driversList | filter: nameFilter">

Questa riga dice ng-repeat che, prima di emettere i dati, l'array driversList deve essere filtrato dal valore memorizzato in nameFilter .

A questo punto, entra in gioco il data binding bidirezionale: ogni volta che viene inserito un valore nel campo di ricerca, Angular assicura immediatamente che il $scope.nameFilter che abbiamo associato ad esso venga aggiornato con il nuovo valore. Poiché l'associazione funziona in entrambi i modi, nel momento in cui il valore nameFilter viene aggiornato, anche la seconda direttiva ad esso associata (cioè, ng-repeat ) ottiene il nuovo valore e la vista viene aggiornata immediatamente.

Ricarica l'app e controlla la barra di ricerca.

barra di ricerca dell'app

Si noti che questo filtro cercherà la parola chiave su tutti gli attributi del modello, inclusi quelli non utilizzati. Diciamo che vogliamo filtrare solo per Driver.givenName e Driver.familyName : Innanzitutto, aggiungiamo driversController , proprio sotto $scope.driversList = []; linea:

 $scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };

Ora, tornando a index.html , aggiorniamo la riga che contiene la direttiva ng-repeat :

 <tr ng-repeat="driver in driversList | filter: searchFilter">

Ricarica l'app ancora una volta e ora abbiamo una ricerca per nome.

Itinerari

Il nostro prossimo obiettivo è creare una pagina dei dettagli del pilota che ci consentirà di fare clic su ciascun pilota e vedere i dettagli della sua carriera.

Innanzitutto, includiamo il servizio $routeProvider (in app.js ) che ci aiuterà a gestire questi vari percorsi applicativi . Quindi, aggiungeremo due di questi percorsi: uno per la classifica del campionato e un altro per i dettagli del pilota. Ecco la nostra nuova app.js :

 angular.module('F1FeederApp', [ 'F1FeederApp.services', 'F1FeederApp.controllers', 'ngRoute' ]). config(['$routeProvider', function($routeProvider) { $routeProvider. when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}). when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}). otherwise({redirectTo: '/drivers'}); }]);

Con tale modifica, la navigazione su http://domain/#/drivers caricherà il driversController e cercherà la vista parziale da renderizzare in partials/drivers.html . Ma aspetta! Non abbiamo ancora visioni parziali, giusto? Dovremo creare anche quelli.

Viste parziali

AngularJS ti consentirà di associare i tuoi percorsi a controller e viste specifici.

Ma prima, dobbiamo dire ad Angular dove rendere queste viste parziali. Per questo, useremo la direttiva ng-view , modificando il nostro index.html per rispecchiare quanto segue:

 <!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>

Ora, ogni volta che navighiamo attraverso i percorsi delle nostre app, Angular caricherà la vista associata e la renderà al posto del <ng-view> . Tutto quello che dobbiamo fare è creare un file chiamato partials/drivers.html e mettere lì la nostra tabella del campionato HTML. Utilizzeremo questa possibilità anche per collegare il nome del conducente al nostro percorso dei dettagli del conducente:

 <input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>

Infine, decidiamo cosa vogliamo mostrare nella pagina dei dettagli. Che ne dici di un riepilogo di tutti i fatti rilevanti sul conducente (ad esempio, nascita, nazionalità) insieme a una tabella contenente i suoi risultati recenti? Per fare ciò, aggiungiamo a services.js :

 angular.module('F1FeederApp.services', []) .factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });

Questa volta, forniamo l'ID del conducente al servizio in modo da recuperare le informazioni relative esclusivamente a un conducente specifico. Ora modifichiamo controllers.js :

 angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });

La cosa importante da notare qui è che abbiamo appena iniettato il servizio $routeParams nel controller del driver. Questo servizio ci consentirà di accedere ai nostri parametri URL (per :id , in questo caso) utilizzando $routeParams.id .

Ora che abbiamo i nostri dati nell'ambito, abbiamo solo bisogno della restante vista parziale. Creiamo un file chiamato partials/driver.html e aggiungiamo:

 <section> <a href="./#/drivers"><- Back to drivers list</a> <nav class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>

Si noti che ora stiamo facendo buon uso della direttiva ng-show . Questa direttiva mostrerà l'elemento HTML solo se l'espressione fornita è true (cioè né false , né null ). In questo caso, l'avatar apparirà solo dopo che l'oggetto driver è stato caricato nell'ambito dal controller.

Finiture

Aggiungi un sacco di CSS e visualizza la tua pagina. Dovresti finire con qualcosa del genere:

pagina renderizzata con CSS

Ora sei pronto per avviare la tua app e assicurarti che entrambi i percorsi funzionino come desiderato. Puoi anche aggiungere un menu statico a index.html per migliorare le capacità di navigazione dell'utente. Le possibilità sono infinite.

EDIT (maggio 2014): ho ricevuto molte richieste per una versione scaricabile del codice che creiamo in questo tutorial. Ho quindi deciso di pubblicarlo qui (privato di qualsiasi CSS). Tuttavia, sconsiglio vivamente di scaricarlo, poiché questa guida contiene ogni singolo passaggio necessario per creare la stessa applicazione con le tue mani, che sarà un esercizio di apprendimento molto più utile ed efficace.

Conclusione

A questo punto del tutorial, abbiamo coperto tutto ciò di cui avresti bisogno per scrivere una semplice app (come un feeder di Formula 1). Ognuna delle restanti pagine della demo dal vivo (ad es. classifica del campionato costruttori, dettagli della squadra, calendario) condivide la stessa struttura di base e gli stessi concetti che abbiamo esaminato qui.

Infine, tieni presente che Angular è un framework molto potente e abbiamo a malapena scalfito la superficie in termini di tutto ciò che ha da offrire. Nella parte 2 di questo tutorial, forniremo esempi del motivo per cui Angular si distingue tra i suoi framework MVC front-end peer: testabilità. Esamineremo il processo di scrittura ed esecuzione di unit test con Karma, ottenendo un'integrazione continua con Yeomen, Grunt e Bower e altri punti di forza di questo fantastico framework front-end.