Selv om AngularJS leveres med indbygget routing, kan du nogle gange finde det begrænsende; Angular UI Router-rammen kan hjælpe dig med at lette smerten. Den indbyggede AngularJS-routing-implementering er ansvarlig for at initialisere controllere, der matcher applikationsruter. Selv om denne funktionalitet fungerer godt i grundlæggende scenarier, vil du i avancerede situationer hurtigt opdage, at Angular’s native routing:
- Kræver, at du manuelt ændrer URL-strenge i hele din kodebase, når en URL ændres.
- Kræver, at du skal huske rutesyntaksen ordret for at navigere til en side.
- Byder ikke mulighed for indlejrede visninger.
- Byder ikke mulighed for navngivne visninger.
- Giver dig ikke mulighed for at videregive data under navigationen.
Som et alternativ er Angular UI Router-rammen et abstraktionslag for routing, der har en mere deklarativ tilgang til navigation. UI Router-rammen udfylder også nogle af hullerne i den native implementering ved at levere nested og navngivne visninger, giver dig mulighed for at videregive data mellem visninger og meget mere.
Angular UI Router-rammen er et abstraktionslag for routing, der har en mere deklarativ tilgang til navigation
I denne artikel lærer du at bygge en simpel applikation, der bruger UI Router-rammen. Undervejs bliver du fortrolig med tilstande, hvordan du løser afhængigheder, og du lærer forskellige metoder til navigation.
- Forståelse af tilstande
- Introduktion af Angular UI Router
- Navigation mellem tilstande
- Hentning og installation
- Anvendelse af Angular UI Router
- Konfiguration
- Definering af tilstande
- Resolving Data with the Articles Service
- Brug af opløste data i ArticlesController
- Rendering af artikellisten
- Mere abstrakt navigation
- Slutning
Forståelse af tilstande
Den bedste måde at forstå det problem, som UI Router-rammen håber at løse, er måske ved at overveje webets natur. Når man i de fleste programmer opretter et link til en side, definerer man i de fleste programmer en eksplicit URL-sti. Hvis du f.eks. ønsker at navigere til siden Produkter på dit websted, kan du have en URL som:
Codeeksempel 1: Version 2 Flexible Container
http://www.example.com/products
Der er to problemer med denne situation. Det første er, at du skal huske den nøjagtige bogstavelige sti til Products-siden, hver gang du opretter et link. Selv om det eksempel, der er givet her, er let at huske, er det ikke så let at huske mange URL’er i den virkelige verden. Det næste problem opstår, når det uundgåelige sker, og nogen beslutter sig for at ændre stien til noget andet. Når en URL ændres, skal man sørge for, at alle eksisterende links opdateres, så de peger på den nye placering.
I stedet for at skulle holde styr på URL-stierne ordret, ville man så ikke hellere bare sige til programmet, at det skal “gå til Products-siden”? Ved at lade programmet tage sig af navigationen, slipper du for at skulle kende den bogstavelige sti og er beskyttet mod ødelagte links, der skyldes uundgåelige ændringer. Ved at bruge states får du denne fleksibilitet. En tilstand indkapsler en URL-placering, tilstandens navn, specialiserede data for visningen, identificerer en måde at lokalisere eller generere visningen på og kan endda eksponere brugerdefinerede hændelser.
Introduktion af Angular UI Router
Angular UI Router er en ramme, der helt erstatter den native routing, der er tilgængelig i AngularJS. UI Router minder meget om den native AngularJS-routing, idet programmerne består af en shell, der indeholder en pladsholder for dynamisk indhold. Figur 1 viser, hvordan applikationsskallen er vært for et element, der bruger ui-view-direktivet. Efterhånden som tilstandsreglerne evalueres i rammen, gengives HTML-indhold inde i pladsholderen.
Angular UI Router er en ramme, der helt erstatter den native routing, der er tilgængelig i AngularJS.
Bortset fra rendering af HTML-indhold understøtter UI Router-rammen URL-routing, muligheden for at løse afhængigheder, før controllere initialiseres, navngivne og indlejrede visninger, nyttefiltre, hændelser ved tilstandsændringer og deklarative overgange mellem tilstande.
Der er et par forskellige måder, du kan bruge til at bevæge dig mellem forskellige tilstande. Den første måde er ui-sref-direktivet. Du kender sikkert href-attributten i HTML-ankertagget (som repræsenterer en hypertekstreference); på samme måde refererer ui-sref-direktivet til en tilstandsreference. Du bruger direktivet ved at deklarere et tilstandsnavn med ui-sref-direktivet anvendt på et anker. F.eks.:
<a ui-sref="about">About Us</a>
Når UI Router-rammen evaluerer dette direktiv, transformeres ankeret til at have den relevante URL-værdi. For eksempel:
<a ui-sref="about" href="#about">About Us</a>
Bemærk, at elementet opdateres, så det indeholder en href-attribut med en værdi, der svarer til, hvordan URL’en skal opdateres for at navigere til siden Om os. Direktivet ui-sref er ret fleksibelt. Det understøtter enkle scenarier såvel som måder at håndtere indlejrede tilstande og endda parametrerede værdier.
Den næste tilgang til at navigere mellem tilstande er at bruge en metode fra $state-objektet, der er tilgængelig for en Angular-controller. I dette næste uddrag kan du se, hvordan navigate-metoden er implementeret til at kalde $state.go og overføre applikationen til about-tilstanden.
angular.module('app') .controller('PageController', );
Det $state-objektet injiceres af UI Router-rammen og indeholder en række metoder, der hjælper dig med at administrere og manipulere tilstanden i applikationen. Værdien her er, at du fortæller programmet, at det skal “gå” til about-staten, og du slipper for at kende den bogstavelige URL-sti til siden.
Hentning og installation
Der er en række forskellige måder, hvorpå du kan få adgang til UI Router-rammen. Du kan downloade den nyeste version direkte fra GitHub-repositoriet på https://github.com/angular-ui/ui-router. Alternativt kan du installere rammen via Bower eller NuGet eller endda inkludere CDN-links i dine sider; begge dele er tilgængelige på http://cdnjs.com/libraries/angular-ui-router.
Anvendelse af Angular UI Router
Det følgende er en vejledning, der viser, hvordan man opbygger en simpel statisk indholdsbaseret applikation ved hjælp af UI Router-rammen. Figur 2 viser den prøveapplikation for startsiden, som du lærer at opbygge, mens du læser denne artikel igennem. På dette skærmbillede kan du se applikationsskallen og se, hvordan indholdet af hjemmesiden injiceres i placeholderen ved hjælp af ui-view-direktivet.
Du kan ændre tilstande for at navigere til kontaktsiden, som vist i figur 3. Mekanismen på kontaktsiden bruger $state-objektets go-metode ved at overdrage et tilstandsnavn til metoden.
Den næste tilstand er knyttet til artiklens listeside, som det ses i figur 4. Her stilles et array af artikeldata til rådighed for visningen efter at have fået de rå værdier injiceret i controlleren af UI Framework. Navigationen på denne side lettes gennem ui-sref-direktivet, der giver mulighed for deklarativt at udtrykke den programtilstand, som man ønsker at navigere til.
Den sidste side, der er illustreret i figur 5, viser, hvordan en nested state anvendes i programmet.
Konfiguration
For at begynde at arbejde med UI Router-rammen skal du konfigurere siden. Det første skridt er at tilføje applikationsnavnet i ng-app-attributten i HTML-elementet på siden. Her er app-navnet simpelthen app.
< html ng-app="app">
Næst skal du tilføje ui-view-direktivet til et element på siden for at fungere som pladsholder for det indhold, der injiceres af rammen. I dette tilfælde tilføjes direktivet til et div-element.
< div ui-view></div>
Endeligt skal du referere til både Angular og Angular UI Router på siden.
<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>
Dette kodestykke indeholder også en henvisning til scriptet app.js i mappen script/app, som indeholder koden til initialisering af Angular-applikationen. Initialiseringsprocessen er der, hvor det meste af opsætningen og grænsefladen med UI Router-rammen er implementeret.
Definering af tilstande
Som tidligere nævnt er grundlaget for UI Router-rammen brugen af forskellige tilstande i en applikation. Ved at få adgang til hver af disse tilstande kan applikationen navigere til eller genoprette sig selv efter omstændighederne i applikationens livscyklus. Det følgende afsnit demonstrerer, hvordan man definerer tilstande for applikationen; al koden er implementeret i filen app.js. Hvert afsnit undersøges isoleret, men hvis du vil se det fulde initialiseringsscript, kan du se Listing 1.
Det første trin er at konfigurere UI Router i din AngularJS-applikation. Når du har navngivet modulet, har du mulighed for at registrere UI Router-rammen som en afhængighed af applikationen ved at tilføje bogstaveligt ui.router i dependencies-arrayet. (Bemærk, hvordan kommentaren angiver en pladsholder for kode i et efterfølgende uddrag.)
angular.module('app', ) .config(/* add configuration here */);
Når modulet er defineret, og afhængighederne er registreret, er programmet sat op til at køre en anonym funktion, der udføres i løbet af programmets konfigurationsfase. Her er der injiceret et par ressourcer i funktionen, som er relevante for UI Router-rammen.
Objektet $stateProvider indeholder state-metoden, der gør det muligt at definere granulære programtilstande, der kan eller ikke kan falde sammen med ændringer i URL’en. $urlRouterProvider er et objekt, der giver dig kontrol over, hvordan browserens placering styres og observeres. I forbindelse med UI Router bruges $urlRouterProvider til at hjælpe med at definere et catch-all-navigationsscenarie. Hvert af disse objekter diskuteres mere detaljeret i de kommende kodestumper. (Bemærk igen, at de efterfølgende kodestumper placeres på den position, hvor stedholderkommentaren i den foregående stump er placeret.)
Hver programtilstand defineres ved at angive et navn og fortælle rammen, hvor den skal finde markup for visningen. Her defineres tilstanden home ved at angive rodplaceringen for url’en og en værdi for templateUrl-egenskaben.
$stateProvider .state('home', { url: '/', templateUrl: '/partials/home.html' })
Dette fortæller programmet, at det skal indlæse indholdet af filen home.html i ui-view-placeholderen, når brugeren navigerer til roden af programmet. Her begynder du at se en af fordelene ved at have tilstandscentreret routing. Hvis du af en eller anden grund ville ønske, at URL’en for home-staten skulle pege på /home i stedet for den nøgne rodplacering (/), ville den ændring kun skulle ske her i konfigurationen. Denne tilstand giver afkald på enhver avanceret opsætning og indlæser en statisk side i browseren. Der kan være andre tilfælde, hvor du ønsker at tilknytte en bestemt controller til tilstanden.
Kontakttilstanden er konfigureret til at indlæse markeringen af siden contact.html i ui-view-pladsholderen. Ud over at udføre en grundlæggende erstatningsoperation er ContactsController også tilknyttet visningen scoped på niveauet for det DOM-element, der er vært for ui-view-direktivet.
.state('contact', { url: '/contact', templateUrl: '/partials/contact.html', controller: 'ContactController', })
Som vist i figur 3 indeholder kontaktsiden en knap til at navigere til artikelsiden. Navigationen foregår i ContactsController og demonstrerer, hvordan man kobler en controller til en visning, der indlæses på anmodning af UI Router-rammen.
Artikelsidens tilstand tager konfigurationen et skridt videre ved at tilføje værdier i objektet, der opløser alle konfigurerede værdier, der er defineret i objektet. Formålet med denne tilstand er at gengive en liste over de tilgængelige artikler på webstedet. Denne tilstand er konfigureret til at have artikeloplysningerne tilgængelige for controlleren, før den instantieres. I det følgende uddrag definerer tilstanden en værdi i resolve-objektet.
.state('articles', { url: '/articles', templateUrl: '/partials/articles.html', resolve: { articles: 'ArticlesService' }, controller: 'ArticlesController' })
I dette tilfælde peger articles-egenskaben på strengen ArticlesService. Når du overfører en streng som værdi til resolve-egenskaben, kontakter rammen en tjeneste, der er registreret under samme navn, og opløser tjenesten ned til dens endelige værdi. I dette tilfælde returnerer ArticlesService et løfte, så den tilknyttede controller instantieres ikke, før servicens løfte er opløst, og det endelige objekt er tilgængeligt som en injicerbar værdi for controlleren. Implementeringen for ArticlesService findes i Listing 3.
Når listen over artikler er gengivet til brugeren som vist i figur 4, kan brugeren vælge en artikel og bore sig ind i webstedets indhold. Denne handling er repræsenteret af en indlejret tilstand. Bemærk, hvordan state-navnet indeholder et punktum (.) mellem articles og article for at angive et forældre- og barnforhold mellem states.
.state('articles.article', { url: '/:pageName', templateUrl: function ($stateParams) { return '/partials/articles/' + $stateParams.pageName + '.html'; } });
Her er der anvendt en særlig regel for, hvordan url-egenskaben evalueres. Da der er tale om en indlejret visning (som angivet ved punktummet i statens navn), vil værdien af url-egenskaben blive sammenkædet med den overordnede stats url-værdi. Det betyder, at alle matchende tilstande vil have en URL, der begynder med /articles og derefter indeholder sidens navn på artiklen.
Anstedeværelsen af kolonet (:) indikerer, at der er tale om en URL-parameter. Ved at indføre en parameter i URL’en bliver tilstandsdefinitionen fleksibel nok til at håndtere enhver tilstand, der passer til det forhold, den har til sin overordnede tilstand. Denne tilstand indeholder også en funktion, der køres for at returnere værdien for templateUrl. Ved at bruge en funktion her får du mulighed for at bruge de parametre, der er defineret i url’en for tilstanden. Uanset hvilket navn du giver parameteren i url-egenskaben, passer det til egenskabsnavnet på objektet $stateParams. Derfor tager denne tilstand det pageName, der er overgivet i URL’en, til brug i funktionen templateUrl for at få adgang til individuelle indholdsfiler, der i sidste ende injiceres i det element, der er vært for ui-view-direktivet.
Dette er den sidste tilstand, der er defineret i programmet. For at se, hvordan alle tilstande er implementeret i det egentlige initialiseringsscript, henvises til Listing 1.
Den sidste kommando, der kræves for at give programmet, er, hvad der skal ske, hvis brugeren forsøger at få adgang til en URL, der ikke er defineret i configure-metoden. Ved at bruge metoden otherwise-metoden fra objektet $urlRouterProvider kasseres alle ikke-genkendte URL’er, og programmet omdirigeres til en standardplacering. I dette tilfælde er programmet konfigureret til at omdirigere til rod-URL’en, hvis den givne URL ikke passer til en defineret tilstand.
$urlRouterProvider.otherwise('/');
Nu, hvor hver programtilstand er defineret, kan du begynde at vende opmærksomheden mod opbygningen af ArticlesService.
Resolving Data with the Articles Service
Konfigurationen for artiklens tilstand indeholder en værdi for resolve-indstillingen. Dette objekt er konfigureret til at have en strengværdi på ArticlesService, der er indstillet til articles-egenskaben (se Listing 1 for kontekst). Ved at give en streng til resolve-objektet får rammen besked om at finde en tjeneste, der er registreret i programmet, og om at opløse tjenesten ned til dens endelige værdi. ArticlesService er implementeret til at returnere et løfte.
angular.module('app').factory('ArticlesService', ); return deferred.promise; }]);
Her bruger tjenesten $q-tjenesten til at oprette et løfte om at returnere et array. I dette tilfælde er værdierne hard-codet, men i en reel kontekst kan det være nødvendigt at få adgang til en fjernserver for at levere dataene. Under alle omstændigheder skal tjenesten være fuldt opløst, før routerrammen vil videregive udførelsen til den tilknyttede controller. Når artiklens tilstand påberåbes, får controlleren derfor i sidste ende overdraget et array af artikelobjekter som en afhængighed.
Brug af opløste data i ArticlesController
En af fordelene ved at bruge UI Router-rammen er muligheden for at håndhæve adskillelse af bekymringer. Da artiklestaten implementerer et resolve-objekt, injiceres det rå array af artikler i controlleren.
angular.module('app') .controller('ArticlesController', );
Denne tilgang er overlegen i forhold til at kræve, at ArticlesController skal “kende” til ArticlesService, fordi det er meget nemmere at mocke et rå array af objekter til testformål i stedet for at håndtere mocking af selve tjenesten. Den fulde implementering for applikationens controllere findes i Listing 2.
Rendering af artikellisten
Nu, hvor applikationen har navigeret til artiklens tilstand, og controlleren har det opløste artikelarray sat i scope, er visningen nu klar til at blive renderet. Visningen Artikler består af to dele. Den første er endnu en div-pladsholder, der bruger ui-view-direktivet for at skabe en indlejret visning. Den anden del er en uordnet liste over de forskellige artikler, der er tilgængelige på webstedet. Ved at konstruere visningen på denne måde kan man klikke på forskellige artikeltitler, mens listen over artiklen forbliver på siden. (Du kan se et eksempel på dette i figur 5.) Dette er muligt, fordi sidens indhold indlæses i ui-viewet på artikelniveau, mens siden som helhed gengives i ui-viewet i programskallen. Den fulde implementering af programskallen findes i Listing 4.
Det følgende kodestykke demonstrerer, hvordan artikelvisningen implementerer en nested visning.
<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>
Der er tre måder, hvorpå denne markup bruger UI Router-rammen. For det første bruger div-elementet ui-view-direktivet som en pladsholder, og som det fremgår af kommentaren, kan du sende standardindhold til gengivelse i pladsholderen, før noget indhold gengives af rammen. Listing 5 viser, hvordan en statisk meddelelse bruges som pladsholderindhold på siden, før noget indhold indlæses i visningen.
For det andet er anchor-elementet forsynet med ui-sref-direktivet. Dette signalerer til UI Router-rammen, at den skal behandle dette link i rammens kontekst og i sidste ende rendere en standard href-værdi, der passer til URL’en for den erklærede tilstand baseret på de indstillinger, der er defineret i applikationskonfigurationen (se Listing 1).
Den tredje måde, rammen bruges på, er, at værdien af ui-sref-direktivet accepterer et udtryk til at generere den korrekte URL-værdi for en indlejret tilstand. Her sendes en hash ind i det indlejrede tilstandshierarki (i dette tilfælde articles.article), hvor værdien for pageName er bundet til den indkommende artikels pageName. Når UI Router-rammen evaluerer dette udtryk, genereres en tilsvarende URL-værdi for hver artikel, der passer til de definerede tilstandsregler.
Den sidste controller, der skal implementeres, er ContactController, som bruger tilstandsparameterens go-metode til at navigere programmet til en ny tilstand.
app.controller('ContactController', );
Her, ved blot at kalde go med et tilstandsnavn, er din controller kun optaget af at deklarere den tilstand, du ønsker at skifte til, i stedet for at forsøge at holde styr på det konkrete routingskema i applikationen.
Slutning
Selv om AngularJS leveres med en funktionel routing-implementering, kan du hurtigt indse fordelene ved at bruge en tilstandsbaseret routing-ramme til ikke-trivielle applikationer. UI Router-rammen giver dig nemme måder at definere tilstande, løse afhængigheder på og gøre brug af indlejrede visninger. Hvis du vil have endnu flere oplysninger om, hvad rammen kan gøre, skal du sørge for at besøge projektets hjemsted på GitHub på https://github.com/angular-ui/ui-router/.