Embora o AngularJS seja fornecido com roteamento embutido, às vezes você pode achá-lo limitativo; a estrutura do Angular UI Router pode ajudar a aliviar a dor. A implementação de roteamento AngularJS nativo é responsável pela inicialização dos controladores que combinam com os roteadores da aplicação. Embora esta funcionalidade funcione bem em cenários básicos, em situações avançadas, você rapidamente descobre que o roteamento nativo do AngularJS:
- Requer que você altere manualmente as strings de URLs em toda a sua base de código quando uma URL muda.
- Requer que você se lembre da sintaxe da rota literalmente para navegar para uma página.
- Não oferece vistas aninhadas.
- Não oferece vistas nomeadas.
- Não permite que você passe dados durante a navegação.
Como alternativa, o framework Angular UI Router é uma camada de abstração para roteamento que apresenta uma abordagem mais declarativa para a navegação. O framework UI Router também preenche algumas das lacunas da implementação nativa, fornecendo vistas aninhadas e nomeadas, permite que você passe dados entre vistas, e muito mais.
O framework Angular UI Router é uma camada de abstração para roteamento que apresenta uma abordagem declarativa para navegação
Neste artigo, você aprenderá a construir uma aplicação simples que usa o framework UI Router. Ao longo do caminho, você se familiarizará com estados, como resolver dependências e aprenderá vários métodos de navegação.
Estados compreensivos
Talvez a melhor maneira de apreciar o problema que o framework de Roteador UI espera resolver é considerar a natureza da Web. Na maioria dos aplicativos, quando você cria um link para uma página, você define um caminho de URL explícito. Por exemplo, se quiser navegar para a página de Produtos do seu site, você pode ter uma URL como:
Código Amostra 1: Versão 2 Container Flexível
http://www.example.com/products
Existem dois problemas com essa situação. O primeiro é que você deve lembrar o caminho literal exato para a página de Produtos cada vez que estabelecer um link. Embora o exemplo dado aqui possa ser fácil de lembrar, muitos URLs do mundo real não são tão facilmente memorizados. Você encontra o próximo problema quando o inevitável acontece e alguém decide mudar o caminho para outra coisa. Quando um URL muda então você tem que ter certeza que todos os links existentes são atualizados para apontar para a nova localização.
Em vez de ter que manter o controle dos caminhos do URL literalmente, você não prefere apenas dizer ao aplicativo para “ir para a página de Produtos”? Ao permitir que a aplicação se preocupe com a navegação, você fica revivido de ter que conhecer o caminho literal e fica protegido de links quebrados, causados pela inevitabilidade da mudança. O uso de estados dá-lhe este nível de flexibilidade. Um estado encapsula uma localização URL, o nome do estado, dados especializados para a visualização, identifica uma forma de localizar ou gerar a visualização, e pode até expor eventos personalizados.
Roteador UI Angular-Ingular
Roteador UI Angular é uma estrutura que substitui totalmente o roteamento nativo disponível no AngularJS. O UI Router é muito semelhante ao roteamento nativo AngularJS, pois as aplicações são compostas por uma shell que ocupa um espaço para o conteúdo dinâmico. A Figura 1 demonstra como a shell da aplicação hospeda um elemento que utiliza a diretiva ui-view. Como as regras de estado são avaliadas no framework, o conteúdo HTML é renderizado dentro do placeholder.
Angular UI Router é um framework que substitui totalmente o roteamento nativo disponível no AngularJS.
Além de renderizar conteúdo HTML, o framework UI Router suporta roteamento de URL, a capacidade de resolver dependências antes que os controladores sejam inicializados, vistas nomeadas e aninhadas, filtros utilitários, eventos de mudança de estado e transições declarativas entre estados.
Existem algumas maneiras diferentes de se mover entre estados diferentes. A primeira maneira é a diretiva ui-sref. Você provavelmente está familiarizado com o atributo href da tag HTML anchor (que representa uma referência de hipertexto); da mesma forma, a diretiva ui-sref se refere a uma referência de estado. Você usa a diretiva declarando um nome de estado com a diretiva ui-sref aplicada a uma âncora. Por exemplo:
<a ui-sref="about">About Us</a>
Como o framework UI Router avalia esta diretiva, a âncora é transformada para ter o valor URL apropriado. Por exemplo:
<a ui-sref="about" href="#about">About Us</a>
Note que o elemento é atualizado para incluir um atributo href com um valor correspondente a como a URL deve ser atualizada para navegar para a página Sobre Nós. A diretiva ui-sref é bastante flexível. Ela suporta cenários simples assim como formas de lidar com estados aninhados e até mesmo valores parametrizados.
A próxima abordagem para navegar entre estados é usar um método fora do objeto $state que está disponível para um controlador Angular. Neste próximo trecho, você pode ver como o método navigate é implementado para chamar $state.go e fazer a transição da aplicação para o estado about.
angular.module('app') .controller('PageController', );
O objeto $state é injetado pelo framework do Roteador UI e inclui um número de métodos para ajudá-lo a gerenciar e manipular o estado na aplicação. O valor aqui é que você diz ao aplicativo para “ir” para o estado about e você fica livre de conhecer o caminho literal do URL para a página.
Downloading and Installation
Existem várias maneiras diferentes de se ter acesso ao framework do Roteador UI. Você pode baixar a última versão diretamente do repositório GitHub em https://github.com/angular-ui/ui-router. Alternativamente, você pode instalar o framework via Bower ou NuGet ou mesmo incluir links CDN em suas páginas; ambos estão disponíveis em http://cdnjs.com/libraries/angular-ui-router.
Using Angular UI Router
O que se segue é um tutorial demonstrando como construir uma aplicação simples baseada em conteúdo estático usando o framework UI Router. A Figura 2 mostra o exemplo de aplicação que você aprende a construir à medida que lê este artigo. A partir desta captura de tela, você pode ver a shell da aplicação e como o conteúdo da página inicial é injetado no espaço reservado usando a diretiva ui-view.
Você pode mudar os estados para navegar para a página de contato, como mostrado na Figura 3. O mecanismo na página de contato usa o método $state’s go do objeto passando um nome de estado para o método.
O próximo estado está associado à página de listagem do artigo, como mostrado na Figura 4. Aqui, um array de dados do artigo é disponibilizado para a visualização após ter os valores brutos injetados no controlador pelo UI Framework. A navegação nesta página é facilitada através da diretiva ui-sref que permite que você declare o estado da aplicação para a qual você deseja navegar.
A página final, ilustrada na Figura 5, mostra como um estado aninhado é usado na aplicação.
Configuração
Para começar a trabalhar com a estrutura do UI Router, você tem que configurar a página. O primeiro passo é adicionar o nome da aplicação no atributo ng-app do elemento HTML na página. Aqui, o nome da aplicação é simplesmente app.
< html ng-app="app">
Next, você precisa adicionar a diretiva ui-view a um elemento da página para agir como o espaço reservado para o conteúdo injetado pelo framework. Neste caso, a diretiva é adicionada a um elemento div.
< div ui-view></div>
Finalmente, você deve fazer referência tanto ao Angular como ao Angular UI Router na página.
<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>
Este trecho de código também inclui referência ao script app.js na pasta script/app que contém o código para inicializar a aplicação Angular. O processo de inicialização é onde a maior parte da configuração e interface com o framework do Roteador UI é implementada.
Definindo Estados
Como dito anteriormente, a base para o framework UI Router é o uso de diferentes estados em uma aplicação. Ao acessar cada um desses estados, a aplicação pode navegar ou reconstituir-se a circunstâncias dentro do ciclo de vida da aplicação. A seção seguinte demonstra como definir os estados para a aplicação; todo o código é implementado no arquivo app.js. Cada seção é examinada isoladamente, mas se você gostaria de ver o script de inicialização completo, por favor consulte Listing 1.
O primeiro passo é configurar o Roteador UI em sua aplicação AngularJS. Após nomear o módulo, você tem a oportunidade de registrar o framework UI Router como uma dependência da aplicação, adicionando o literal ui.router ao array de dependências. (Note como o comentário denota um espaço reservado para o código em um snippet subseqüente.)
angular.module('app', ) .config(/* add configuration here */);
Após o módulo ser definido e as dependências serem registradas, a aplicação é configurada para executar uma função anônima que é executada durante a fase de configuração da aplicação. Aqui, há alguns recursos injetados na função que são relevantes ao framework do Roteador UI.
O objeto $stateProvider apresenta o método de estados que permite definir estados granulares da aplicação que podem ou não coincidir com as mudanças na URL. O $urlRouterProvider é um objeto que lhe dá controle sobre como a localização do navegador é gerenciada e observada. No contexto do UI Router, o $urlRouterProvider é usado para ajudar a definir um cenário de navegação de captura de todos. Cada um desses objetos é discutido com mais detalhes nos próximos trechos de código. (Novamente, note que os trechos de código subseqüentes são colocados na posição do comentário de espaço reservado no trecho anterior.)
Cada estado de aplicação é definido fornecendo um nome e dizendo ao framework onde encontrar a marcação para a visualização. Aqui, o estado home é definido fornecendo a localização da raiz para a url e um valor para a propriedade templateUrl.
$stateProvider .state('home', { url: '/', templateUrl: '/partials/home.html' })
Diz à aplicação para carregar o conteúdo do arquivo home.html para o espaço reservado ao ui-view quando o usuário navega para a raiz da aplicação. Aqui, você começa a ver uma das vantagens de ter um roteamento centrado no estado. Se, por alguma razão, você quisesse que a URL para o estado inicial apontasse para /home ao invés da localização da raiz (/), essa mudança só precisaria acontecer aqui na configuração. Este estado renuncia a qualquer configuração avançada e carrega uma página estática para o navegador. Pode haver outros momentos em que você queira associar um controlador específico com o estado.
O estado de contato é configurado para carregar a marcação da página contact.html para o espaço ui-view. Além de fazer uma operação básica de substituição, o ContatosController também é associado à visualização escopada no nível do elemento DOM que hospeda a diretiva ui-view.
.state('contact', { url: '/contact', templateUrl: '/partials/contact.html', controller: 'ContactController', })
Como mostrado na Figura 3, a página Contatos inclui um botão para navegar até a página Artigos. A navegação é feita no ContactController e demonstra como ligar um controlador a uma view carregada sob demanda pelo framework do Roteador UI.
O estado da página de Artigos leva a configuração um passo adiante, adicionando valores ao objeto que resolve quaisquer valores configurados definidos no objeto. O objetivo deste estado é renderizar uma lista dos artigos disponíveis no site. Este estado é configurado para ter a informação do artigo disponível para o controlador antes que ele seja instanciado. No snippet seguinte, o estado define um valor no objeto de resolução.
.state('articles', { url: '/articles', templateUrl: '/partials/articles.html', resolve: { articles: 'ArticlesService' }, controller: 'ArticlesController' })
Neste caso, a propriedade dos artigos aponta para a string ArticlesService. Quando você passa uma string como um valor para a propriedade resolve, o framework entra em contato com um serviço registrado com o mesmo nome e resolve o serviço até o seu valor final. Neste caso, o ArticlesService retorna uma promessa para que o controlador associado não seja instanciado até que a promessa do serviço seja resolvida e o objeto final esteja disponível como um valor injetável para o controlador. A implementação para o ArticlesService está disponível em Listing 3.
Após a lista de artigos ser renderizada ao usuário como mostrado na Figura 4, o usuário pode selecionar um artigo e perfurar o conteúdo do site. Esta ação é representada por um estado aninhado. Observe como o nome do estado inclui um ponto (.) entre os artigos e o artigo para denotar uma relação de pais e filhos entre os estados.
.state('articles.article', { url: '/:pageName', templateUrl: function ($stateParams) { return '/partials/articles/' + $stateParams.pageName + '.html'; } });
Aqui, há uma regra especial aplicada à forma como a propriedade url é avaliada. Como esta é uma visão aninhada (como indicado pelo ponto no nome do estado) o valor da propriedade url será concatenado com o valor da propriedade url do estado pai. Isto significa que qualquer estado correspondente terá uma URL que começa com /articles e depois inclui o nome da página do artigo.
A presença dos dois pontos (:) é indicativa de um parâmetro URL. Ao introduzir um parâmetro na URL, a definição do estado torna-se flexível o suficiente para lidar com qualquer estado que combine com a relação que ele tem com o seu estado pai. Este estado também possui uma função que é executada para retornar o valor para a templateUrl. O uso de uma função aqui dá-lhe a oportunidade de usar os parâmetros definidos na url do estado. Qualquer nome que você dê ao parâmetro na propriedade url corresponde ao nome da propriedade do objeto $stateParams. Portanto, este estado toma o pageName passado na URL para usar na função templateUrl para acessar arquivos de conteúdo individuais que eventualmente são injetados no elemento que hospeda a diretiva ui-view.
Este é o último estado definido na aplicação. Para ver como todos os estados são implementados no script de inicialização real, consulte Listagem 1.
O comando final necessário para dar a aplicação é o que fazer se o usuário tentar acessar uma URL que não está definida no método de configuração. Usando o método de outra forma do objeto $urlRouterProvider, quaisquer URLs não reconhecidas são descartadas e a aplicação é redirecionada para um local padrão. Neste caso, o aplicativo é configurado para redirecionar para a URL raiz se a URL dada não corresponder a um estado definido.
$urlRouterProvider.otherwise('/');
Agora, com cada estado do aplicativo definido, você pode começar a voltar sua atenção para a construção do ArticlesService.
Resolvendo Dados com o Articles Service
A configuração para o estado do artigo inclui um valor para a opção resolver. Este objeto é configurado para ter um valor de string do ArticlesService definido para a propriedade ArticlesService (ver Listagem 1 para contexto). O fornecimento de uma string ao objeto de resolução diz ao framework para localizar um serviço registrado na aplicação e resolver o serviço até o seu valor final. O ArticlesService é implementado para retornar uma promessa.
angular.module('app').factory('ArticlesService', ); return deferred.promise; }]);
Aqui, o serviço está usando o serviço $q para criar uma promessa de retornar um array. Neste caso, os valores são codificados, mas em um contexto real, você pode precisar acessar um servidor remoto para fornecer os dados. Em qualquer caso, o serviço deve ser totalmente resolvido antes que o framework do roteador passe a execução para o controlador associado. Portanto, como o estado do artigo é invocado, em última instância o controlador é passado um array de objetos de artigo como uma dependência.
Usar dados resolvidos no ArticlesController
Uma das vantagens de usar a estrutura do Roteador UI é a capacidade de impor a separação de preocupações. Como o estado dos artigos implementa um objeto de resolução, a matriz bruta de artigos é injetada no controlador.
angular.module('app') .controller('ArticlesController', );
Esta abordagem é superior a exigir que o ArticlesController “saiba” sobre o ArticlesService porque é muito mais fácil zombar de uma matriz bruta de objetos para fins de teste, ao invés de lidar com a zombaria do próprio serviço. A implementação completa para os controladores da aplicação é encontrada em Listing 2.
Rendering the Articles List
Agora a aplicação tenha navegado para o estado do artigo e o controlador tenha o array de artigos resolvido definido em escopo, a visualização está agora pronta para ser renderizada. A visualização de Artigos é composta de duas partes. A primeira é outra parte que usa a diretiva ui-view para criar uma visão aninhada. A segunda é uma lista não-ordenada dos diferentes artigos disponíveis no site. Construir a vista desta forma permite-lhe clicar em diferentes títulos de artigos enquanto a lista de artigos permanece na página. (Você pode ver um exemplo disto na Figura 5.) Isto é possível porque o conteúdo da página é carregado no nível de artigo ui-view enquanto a página como um todo é renderizada na ui-view na shell da aplicação. A implementação completa da shell da aplicação está disponível em Listing 4.
O seguinte trecho de código demonstra como a visão dos artigos implementa uma visão aninhada.
<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>
Existem três maneiras desta marcação usar a estrutura do Roteador UI. Primeiro, o elemento div usa a diretiva ui-view como um espaço reservado, e, como diz o comentário, você pode passar o conteúdo padrão para renderizar no espaço reservado antes que qualquer conteúdo seja renderizado pelo framework. Listando 5 demonstra como uma mensagem estática é usada como espaço reservado na página antes que qualquer conteúdo seja carregado na view.
Segundo, o elemento anchor tem a diretiva ui-sref aplicada. Isto sinaliza para a estrutura do Roteador UI para processar este link no contexto da estrutura e, em última instância, torna um valor href padrão que corresponde ao URL para o estado declarado baseado nas configurações definidas na configuração da aplicação (veja Listagem 1).
A terceira maneira em que o framework é usado é que o valor da diretiva ui-sref aceita uma expressão para gerar o valor correto da URL para um estado aninhado. Aqui, um hash é passado para a hierarquia de estados aninhados (neste caso articles.article) onde o valor para pageName é vinculado ao pageName do artigo de entrada. Quando o framework UI Router avalia esta expressão, um valor URL correspondente é gerado para cada artigo que corresponde às regras de estado definidas.
O último controlador a implementar é o ContactController, que usa o método go do parâmetro de estado para navegar a aplicação para um novo estado.
app.controller('ContactController', );
Aqui, simplesmente chamando go com um nome de estado, seu controlador só se preocupa em declarar o estado para o qual você deseja mudar, ao invés de tentar manter o controle do esquema de roteamento concreto na aplicação.
Conclusão
Embora AngularJS venha equipado com uma implementação de roteamento funcional, você pode rapidamente perceber as vantagens de usar um framework de roteamento baseado em estado para aplicações não triviais. O framework UI Router fornece maneiras fáceis para você definir estados, resolver dependências e fazer uso de vistas aninhadas. Para obter ainda mais informações sobre o que a estrutura pode fazer, não deixe de visitar a casa do projeto no GitHub em https://github.com/angular-ui/ui-router/.