Anche se AngularJS è dotato di routing integrato, a volte si può trovare limitante; il framework Angular UI Router può aiutare ad alleviare il dolore. L’implementazione nativa del routing di AngularJS è responsabile dell’inizializzazione dei controllori che corrispondono alle rotte dell’applicazione. Anche se questa funzionalità funziona bene negli scenari di base, in situazioni avanzate, si scopre rapidamente che il routing nativo di Angular:
- Richiede di cambiare manualmente le stringhe di URL in tutto il codice base quando un URL cambia.
- Richiede di ricordare la sintassi del percorso alla lettera per navigare in una pagina.
- Non offre viste annidate.
- Non offre viste con nome.
- Non permette di passare dati durante la navigazione.
Come alternativa, il framework Angular UI Router è un livello di astrazione per il routing che presenta un approccio più dichiarativo alla navigazione. Il framework UI Router colma anche alcune delle lacune dell’implementazione nativa fornendo viste annidate e nominate, permette di passare dati tra le viste e molto altro.
Il framework Angular UI Router è un livello di astrazione per il routing che presenta un approccio dichiarativo alla navigazione
In questo articolo, imparerai a costruire una semplice applicazione che utilizza il framework UI Router. Lungo la strada, diventerai familiare con gli stati, come risolvere le dipendenze, e imparerai vari metodi per la navigazione.
- Capire gli stati
- Introduzione ad Angular UI Router
- Viaggio tra gli stati
- Scarico e installazione
- Usare Angular UI Router
- Configurazione
- Definizione degli stati
- Risolvere i dati con il Servizio Articoli
- Usare i dati risolti in ArticlesController
- Rendering della lista degli articoli
- Più navigazione astratta
- Conclusione
Capire gli stati
Forse il modo migliore per apprezzare il problema che il framework UI Router spera di risolvere è considerare la natura del Web. Nella maggior parte delle applicazioni, quando si crea un link ad una pagina, si definisce un percorso URL esplicito. Per esempio, se si volesse navigare alla pagina Products del proprio sito, si potrebbe avere un URL come:
Code Sample 1: Version 2 Flexible Container
http://www.example.com/products
Ci sono due problemi con questa situazione. Il primo è che devi ricordare il percorso letterale esatto della pagina Products ogni volta che stabilisci un collegamento. Anche se l’esempio dato qui può essere facile da ricordare, molti URL del mondo reale non sono così facilmente memorizzabili. Si incontra il prossimo problema quando accade l’inevitabile e qualcuno decide di cambiare il percorso in qualcos’altro. Quando un URL cambia, bisogna assicurarsi che tutti i link esistenti siano aggiornati per puntare alla nuova posizione.
Invece di dover tenere traccia dei percorsi URL alla lettera, non sarebbe meglio dire semplicemente all’applicazione di “andare alla pagina dei prodotti”? Permettendo all’applicazione di preoccuparsi della navigazione, si è sollevati dal dover conoscere il percorso letterale e si è protetti dai collegamenti interrotti causati dall’inevitabilità del cambiamento. L’uso degli stati vi dà questo livello di flessibilità. Uno stato incapsula una posizione URL, il nome dello stato, dati specializzati per la vista, identifica un modo per localizzare o generare la vista, e può persino esporre eventi personalizzati.
Introduzione ad Angular UI Router
Angular UI Router è un framework che sostituisce completamente il routing nativo disponibile in AngularJS. UI Router è molto simile al routing nativo di AngularJS in quanto le applicazioni sono composte da una shell che contiene un segnaposto per il contenuto dinamico. La figura 1 dimostra come la shell dell’applicazione ospita un elemento che usa la direttiva ui-view. Man mano che le regole di stato vengono valutate nel framework, il contenuto HTML viene reso all’interno del segnaposto.
Angular UI Router è un framework che sostituisce completamente il routing nativo disponibile in AngularJS.
Oltre al rendering del contenuto HTML, il framework UI Router supporta il routing degli URL, la capacità di risolvere le dipendenze prima che i controllori siano inizializzati, le viste con nome e annidate, i filtri di utilità, gli eventi di cambio di stato e le transizioni dichiarative tra gli stati.
Viaggio tra gli stati
Ci sono alcuni modi diversi che si possono usare per muoversi tra diversi stati. Il primo modo è la direttiva ui-sref. Probabilmente hai familiarità con l’attributo href del tag HTML anchor (che rappresenta un riferimento ipertestuale); allo stesso modo, la direttiva ui-sref si riferisce ad un riferimento di stato. Si usa la direttiva dichiarando un nome di stato con la direttiva ui-sref applicata ad un’ancora. Per esempio:
<a ui-sref="about">About Us</a>
Quando il framework UI Router valuta questa direttiva, il riferimento viene trasformato per avere il valore URL appropriato. Per esempio:
<a ui-sref="about" href="#about">About Us</a>
Nota che l’elemento è aggiornato per includere un attributo href con un valore corrispondente a come l’URL deve essere aggiornato per navigare alla pagina About Us. La direttiva ui-sref è abbastanza flessibile. Supporta scenari semplici così come modi per trattare con stati annidati e anche valori parametrizzati.
Il prossimo approccio per navigare tra gli stati è quello di utilizzare un metodo fuori dall’oggetto $state che è disponibile per un controller Angular. In questo prossimo snippet, si può vedere come il metodo navigate è implementato per chiamare $state.go e passare l’applicazione allo stato about.
angular.module('app') .controller('PageController', );
L’oggetto $state è iniettato dal framework UI Router e include una serie di metodi per aiutare a gestire e manipolare lo stato nell’applicazione. Il valore qui è che si dice all’applicazione di “andare” allo stato circa e si è liberati dal conoscere il percorso letterale dell’URL della pagina.
Scarico e installazione
Ci sono diversi modi per accedere al framework UI Router. Puoi scaricare l’ultima versione direttamente dal repository GitHub a https://github.com/angular-ui/ui-router. In alternativa, puoi installare il framework tramite Bower o NuGet o anche includere link CDN nelle tue pagine; entrambi sono disponibili a http://cdnjs.com/libraries/angular-ui-router.
Usare Angular UI Router
Quello che segue è un tutorial che dimostra come costruire una semplice applicazione statica basata sui contenuti usando il framework UI Router. La figura 2 mostra l’applicazione di esempio della home page che imparerete a costruire durante la lettura di questo articolo. Da questa schermata, si può vedere la shell dell’applicazione e come il contenuto della home page viene iniettato nel segnaposto utilizzando la direttiva ui-view.
Puoi cambiare stato per navigare verso la pagina dei contatti, come mostrato nella Figura 3. Il meccanismo della pagina dei contatti usa il metodo go dell’oggetto $state passando un nome di stato al metodo.
Lo stato successivo è associato alla pagina della lista degli articoli, come si vede nella Figura 4. Qui, un array di dati dell’articolo è messo a disposizione della vista dopo che i valori grezzi sono stati iniettati nel controller dall’UI Framework. La navigazione in questa pagina è facilitata dalla direttiva ui-sref che permette di esprimere dichiarativamente lo stato dell’applicazione verso cui si vuole navigare.
La pagina finale, illustrata nella Figura 5, mostra come uno stato annidato viene utilizzato nell’applicazione.
Configurazione
Per iniziare a lavorare con il framework UI Router, bisogna configurare la pagina. Il primo passo è aggiungere il nome dell’applicazione nell’attributo ng-app dell’elemento HTML della pagina. Qui, il nome dell’applicazione è semplicemente app.
< html ng-app="app">
In seguito, è necessario aggiungere la direttiva ui-view a un elemento della pagina per agire come segnaposto per il contenuto iniettato dal framework. In questo caso, la direttiva viene aggiunta a un elemento div.
< div ui-view></div>
Infine, è necessario fare riferimento sia ad Angular che ad Angular UI Router nella pagina.
<script src="scripts/lib/angular.min.js"></script><script src="scripts/lib/angular-ui-router.min.js"></script><script src="scripts/app/app.js"></script>
Questo frammento di codice include anche un riferimento allo script app.js nella cartella script/app che contiene il codice per inizializzare l’applicazione Angular. Il processo di inizializzazione è dove viene implementata la maggior parte della configurazione e dell’interfaccia con il framework UI Router.
Definizione degli stati
Come detto in precedenza, la base del framework UI Router è l’uso di diversi stati in un’applicazione. Accedendo a ciascuno di questi stati, l’applicazione può navigare o ricostituirsi in circostanze all’interno del ciclo di vita dell’applicazione. La seguente sezione dimostra come definire gli stati per l’applicazione; tutto il codice è implementato nel file app.js. Ogni sezione viene esaminata isolatamente, ma se volete vedere lo script di inizializzazione completo, fate riferimento al listato 1.
Il primo passo è quello di configurare UI Router nella vostra applicazione AngularJS. Dopo aver nominato il modulo, avete l’opportunità di registrare il framework UI Router come dipendenza dell’applicazione aggiungendo il letterale ui.router nell’array delle dipendenze. (Si noti come il commento denoti un segnaposto per il codice in uno snippet successivo.)
angular.module('app', ) .config(/* add configuration here */);
Una volta definito il modulo e registrate le dipendenze, l’applicazione è impostata per eseguire una funzione anonima che viene eseguita durante la fase di configurazione dell’applicazione. Qui, ci sono alcune risorse iniettate nella funzione che sono rilevanti per il framework UI Router.
L’oggetto $stateProvider presenta il metodo state che permette di definire stati granulari dell’applicazione che possono coincidere o meno con i cambiamenti dell’URL. L’oggetto $urlRouterProvider è un oggetto che dà il controllo su come viene gestita e osservata la posizione del browser. Nel contesto dell’UI Router, $urlRouterProvider è usato per aiutare a definire uno scenario di navigazione catch-all. Ognuno di questi oggetti è discusso più in dettaglio nei prossimi frammenti di codice. (Di nuovo, si noti che i successivi frammenti di codice sono collocati nella posizione del commento segnaposto nel frammento precedente).
Ogni stato dell’applicazione è definito fornendo un nome e dicendo al framework dove trovare il markup per la vista. Qui, lo stato home è definito fornendo la posizione root per l’url e un valore per la proprietà templateUrl.
$stateProvider .state('home', { url: '/', templateUrl: '/partials/home.html' })
Questo dice all’applicazione di caricare il contenuto del file home.html nel segnaposto ui-view quando l’utente naviga verso la root dell’applicazione. Qui si comincia a vedere uno dei vantaggi di avere un routing centrato sullo stato. Se, per qualche ragione, si volesse che l’URL per lo stato home puntasse a /home invece che alla nuda posizione root (/), questo cambiamento dovrebbe avvenire solo qui nella configurazione. Questo stato rinuncia a qualsiasi configurazione avanzata e carica una pagina statica nel browser. Ci possono essere altre volte in cui si vuole associare un controller specifico allo stato.
Lo stato di contatto è impostato per caricare il markup della pagina contact.html nel segnaposto ui-view. Oltre a fare un’operazione di sostituzione di base, il ContactsController è anche associato alla vista scoped al livello dell’elemento DOM che ospita la direttiva ui-view.
.state('contact', { url: '/contact', templateUrl: '/partials/contact.html', controller: 'ContactController', })
Come mostrato nella Figura 3, la pagina Contact include un pulsante per navigare alla pagina Articles. La navigazione avviene nel ContactsController e dimostra come cablare un controller a una vista caricata su richiesta dal framework UI Router.
Lo stato della pagina degli articoli fa un ulteriore passo avanti nella configurazione aggiungendo valori nell’oggetto che risolvono qualsiasi valore configurato definito nell’oggetto. Lo scopo di questo stato è quello di rendere un elenco degli articoli disponibili sul sito. Questo stato è impostato per avere le informazioni sugli articoli disponibili al controller prima che sia istanziato. Nel seguente frammento, lo stato definisce un valore nell’oggetto resolve.
.state('articles', { url: '/articles', templateUrl: '/partials/articles.html', resolve: { articles: 'ArticlesService' }, controller: 'ArticlesController' })
In questo caso, la proprietà articles punta alla stringa ArticlesService. Quando si passa una stringa come valore alla proprietà resolve, il framework contatta un servizio registrato con lo stesso nome e risolve il servizio fino al suo valore finale. In questo caso, l’ArticlesService restituisce una promessa, quindi il controller associato non viene istanziato fino a quando la promessa del servizio non viene risolta e l’oggetto finale è disponibile come valore iniettabile per il controller. L’implementazione per l’ArticlesService è disponibile nel listato 3.
Dopo che l’elenco degli articoli è reso all’utente come illustrato nella figura 4, l’utente può selezionare un articolo e approfondire il contenuto del sito. Questa azione è rappresentata da uno stato annidato. Notate come il nome dello stato include un punto (.) tra articles e article per denotare una relazione genitore e figlio tra gli stati.
.state('articles.article', { url: '/:pageName', templateUrl: function ($stateParams) { return '/partials/articles/' + $stateParams.pageName + '.html'; } });
Qui, c’è una regola speciale applicata a come viene valutata la proprietà url. Poiché questa è una vista annidata (come indicato dal punto nel nome dello stato) il valore della proprietà url sarà concatenato con il valore url dello stato padre. Questo significa che ogni stato corrispondente avrà un URL che inizia con /articles e poi include il nome della pagina dell’articolo.
La presenza dei due punti (:) è indicativa di un parametro URL. Introducendo un parametro nell’URL, la definizione dello stato diventa abbastanza flessibile da gestire qualsiasi stato che corrisponde alla relazione che ha con il suo stato padre. Questo stato presenta anche una funzione che viene eseguita per restituire il valore di templateUrl. L’uso di una funzione qui dà l’opportunità di utilizzare i parametri definiti nell’url dello stato. Qualsiasi nome si dia al parametro nella proprietà url corrisponde al nome della proprietà dell’oggetto $stateParams. Pertanto, questo stato prende il pageName passato nell’URL da usare nella funzione templateUrl per accedere ai singoli file di contenuto che vengono eventualmente iniettati nell’elemento che ospita la direttiva ui-view.
Questo è l’ultimo stato definito nell’applicazione. Per vedere come tutti gli stati sono implementati nell’attuale script di inizializzazione, fare riferimento al listato 1.
L’ultimo comando richiesto all’applicazione è cosa fare se l’utente cerca di accedere ad un URL che non è definito nel metodo configure. Usando il metodo otherwise dall’oggetto $urlRouterProvider, qualsiasi URL non riconosciuto viene scartato e l’applicazione viene reindirizzata a una posizione predefinita. In questo caso, l’applicazione è configurata per reindirizzare all’URL principale se l’URL dato non corrisponde a uno stato definito.
$urlRouterProvider.otherwise('/');
Ora, con ogni stato dell’applicazione definito, puoi iniziare a rivolgere la tua attenzione alla costruzione dell’ArticlesService.
Risolvere i dati con il Servizio Articoli
La configurazione dello stato dell’articolo include un valore per l’opzione resolve. Questo oggetto è configurato per avere un valore stringa di ArticlesService impostato alla proprietà articles (vedi Listato 1 per il contesto). Fornire una stringa all’oggetto resolve dice al framework di individuare un servizio registrato nell’applicazione e di risolvere il servizio fino al suo valore finale. Il servizio ArticlesService è implementato per restituire una promessa.
angular.module('app').factory('ArticlesService', ); return deferred.promise; }]);
Qui, il servizio sta usando il servizio $q per creare una promessa per restituire un array. In questo caso, i valori sono hard-coded, ma in un contesto reale, potrebbe essere necessario accedere a un server remoto per fornire i dati. In ogni caso, il servizio deve essere completamente risolto prima che il framework del router passi l’esecuzione al controller associato. Pertanto, quando lo stato dell’articolo viene invocato, alla fine al controller viene passato un array di oggetti articolo come dipendenza.
Usare i dati risolti in ArticlesController
Uno dei vantaggi dell’utilizzo del framework UI Router è la capacità di imporre la separazione delle preoccupazioni. Poiché lo stato degli articoli implementa un oggetto resolve, l’array grezzo di articoli è iniettato nel controller.
angular.module('app') .controller('ArticlesController', );
Questo approccio è superiore al richiedere che l’ArticlesController “conosca” l’ArticlesService perché è molto più facile prendere in giro un array grezzo di oggetti per scopi di test piuttosto che occuparsi di prendere in giro il servizio stesso. L’implementazione completa per i controllori dell’applicazione si trova nel listato 2.
Rendering della lista degli articoli
Ora che l’applicazione ha navigato verso lo stato dell’articolo e il controllore ha l’array degli articoli risolto, la vista è pronta per essere resa. La vista Articles è composta da due parti. La prima è un altro segnaposto div che usa la direttiva ui-view per creare una vista annidata. La seconda è un elenco non ordinato dei diversi articoli disponibili sul sito. Costruire la vista in questo modo permette di cliccare su diversi titoli di articoli mentre l’elenco degli articoli rimane nella pagina. (Potete vedere un esempio di questo nella Figura 5.) Questo è possibile perché il contenuto della pagina è caricato nella ui-view a livello di articolo mentre la pagina nel suo complesso è resa nella ui-view nella shell dell’applicazione. L’implementazione completa della shell dell’applicazione è disponibile nel listato 4.
Il seguente frammento di codice dimostra come la vista articoli implementa una vista annidata.
<div ui-view> <!-- default content goes here --></div>...<ul class="list-group"> <li class="list-group-item" c> <a ui-sref="articles.article({pageName: '{{article.pageName}}'})"> {{article.title}}</a> </li></ul>
Ci sono tre modi in cui questo markup usa il framework UI Router. In primo luogo, l’elemento div usa la direttiva ui-view come segnaposto e, come dice il commento, si può passare un contenuto predefinito da rendere nel segnaposto prima che qualsiasi contenuto sia reso dal framework. Il listato 5 dimostra come un messaggio statico sia usato come contenuto segnaposto nella pagina prima che qualsiasi contenuto sia caricato nella vista.
In secondo luogo, l’elemento anchor ha la direttiva ui-sref applicata. Questo segnala al framework UI Router di elaborare questo collegamento nel contesto del framework e alla fine rende un valore href standard che corrisponde all’URL per lo stato dichiarato in base alle impostazioni definite nella configurazione dell’applicazione (vedi Listato 1).
Il terzo modo in cui il framework viene usato è che il valore della direttiva ui-sref accetta un’espressione per generare il corretto valore dell’URL per uno stato annidato. Qui, un hash viene passato nella gerarchia dello stato annidato (in questo caso articles.article) dove il valore per pageName è legato al pageName dell’articolo in arrivo. Quando il framework UI Router valuta questa espressione, viene generato un valore URL corrispondente per ogni articolo che corrisponde alle regole di stato definite.
L’ultimo controller da implementare è il ContactController, che usa il metodo go del parametro state per navigare l’applicazione in un nuovo stato.
app.controller('ContactController', );
Qui, chiamando semplicemente go con un nome di stato, il controller si preoccupa solo di dichiarare lo stato a cui si vuole passare piuttosto che cercare di tenere traccia dello schema di routing concreto nell’applicazione.
Conclusione
Anche se AngularJS viene fornito con un’implementazione di routing funzionale, si possono realizzare rapidamente i vantaggi di usare un framework di routing basato sullo stato per applicazioni non banali. Il framework UI Router fornisce modi semplici per definire gli stati, risolvere le dipendenze e fare uso di viste annidate. Per ulteriori informazioni su ciò che il framework può fare, assicuratevi di visitare la home del progetto su GitHub a https://github.com/angular-ui/ui-router/.