Navegando por uma Single Page Application Angular

Depois da ótima repercussão do post sobre as perspectivas e análise do cenário atual do Angular, vamos partir para algo mais técnico. Na série de artigos vamos construir uma aplicação fazendo um CRUD básico em Angular e neste vamos abordar a parte de navegação. Nos textos, vou seguir a padronização definida pelo John Papa.

Primeiro vamos começar pela navegação padrão do Angular, usando a api ngRoute. Depois de fazer a configuração básica do projeto, temos que importar o JavaScript de navegação necessário.

<script type="text/javascript" src="vendor/angular/angular-route-1.3.0-min.js">

Não é escopo desse artigo, mas você pode usar uma biblioteca JS para fazer este trabalho com o Require.js e o Head.js.

Além dele, temos que importar o JavaScript que contém as informações básicas da aplicação, o Angular denomina como “root module“. Nele temos que importar os módulos que fazem parte da nossa aplicação, um deles será o ngRoute

var caelumapp = angular.module('caelumapp', [ 'ngRoute', 'app.login']);

Ainda neste arquivo, vamos configurar as rotas que estarão disponíveis na aplicação. As rotas são definidas usando o $routeProvider, sendo que cada rota é declarada no método when(path, object).O primeiro parâmetro recebe qual será o nome URL e o segundo é um objeto JSON com uma série de configurações básicas.

caelumapp.config(['$routeProvider','$locationProvider',
	function($routeProvider, $locationProvider) {
		$routeProvider
			.when('/login', {
				templateUrl: "app/login/paginas/login.html",
				controller : "LoginController",
			      	controllerAs: "vm"
				})
			.when('/usuario-cadastro', {
				templateUrl: "app/usuario/paginas/usuario-cadastro.html",
				controller: 'UsuarioController',
				controllerAs: 'vm'
				})
			.otherwise({
				redirectTo : '/login'
				});
	} ]);

Por fim, definimos qual será a parte do nosso template que terá o conteúdo dinâmico, fazemos isso atráves da  diretiva ng-view, além de colocar links para navegação

<nav>
	<ul>
		<li><a href="#/login">Login</a></li>
		<li><a href="#/usuario-cadastro">Cadastro de Usuários</a></li>
	</ul>
</nav>

<section>
		<article ng-view></article>
</section>

O navegador básico do angular possui algumas limitações, como não lidar com nested e multiple views, além de ser baseado essencialmente nas URL’s, portanto, caso queira modificar alguma URL, terá que fazer isso em todos os arquivos que fazem referência pra ela.

Pensando nisso, foi criado o AngularUI-router,  um framework para o Angular que trabalha baseado em uma máquina de estados. Basicamente, o programador não fica preso às url’s, mas sim aos estados. Não obstante, ele resolve problemas de múltiplas views em uma única página, ou seja, várias ng-views (no caso do framework seria ui-view) em um template, além das nested views (de uma uma view, tem outra view, que tem outra view e por ai vai).  

Aqui tem uma apresentação bem divertida demonstrando as principais diferenças entre as duas soluções e uma aplicação de exemplo para entender o funcionamento do framework.

Refatorando nosso código para Angular-ui-Router temos que fazer alguns ajustes básicos, como importar o JavaScript do componente, mudar as injeções no JavaScript responsável pela navegação ( agora injeção do $stateProvider e $urlRouterProvider), além de trocar os links para ui-sref e conteúdo dinâmico para ui-view.


<script type="text/javascript" src="vendor/angular-ui-router/angular-ui-router.min.js"></script>

<section>
		<header>Opções do Sistema</header>
		<nav>
			<ul>
				<li><a ui-sref="login">Login</a></li>
				<li><a ui-sref="cadastroDeUsuario">Cadastro de Usuários</a></li>
				<li><a ui-sref="pesquisaDeUsuario({id:vm.idUsuario})">Pesquisa de Usuários</a></li>

			</ul>
		</nav>
	</section>
	<section>
		<article ui-view></article>
    </section>

 

Perceba que agora os links estão ligados aos nomes dos estados, e a sua url pode ser facilmente mudada somente no arquivo de configuração javascript.


var caelumapp = angular.module('caelumapp', [ 'ui.router', 'app.login']);

	caelumapp.config(['$stateProvider', '$urlRouterProvider',
		'$httpProvider',
		function($stateProvider, $urlRouterProvider) {
			  $urlRouterProvider.otherwise("/login");
			  $stateProvider
			    .state('login', {
			      url: "/login",
			      templateUrl: "app/login/paginas/login.html",
			      controller : "LoginController",
			      controllerAs: "vm"
			    })
			    .state('cadastroDeUsuario', {
			      url: "/cadastro/usuario",
			      templateUrl: "app/usuario/paginas/usuario-cadastro.html",
			      controller: 'UsuarioController',
				  controllerAs: 'vm'
			    })
			    .state('pesquisaDeUsuario', {
			      url: "/usuarios/:id",
			      templateUrl: "app/usuario/paginas/usuario-detalhe.html",
			      controller: 'UsuarioDetalheController',
				  controllerAs: 'vm'
			    });
		} ]);

Um caso interessante de navegação é passar um parâmetro por ID, por exemplo acessar a url http://caelum.com.br/usuarios/17. Perceba que no JavaScript de configuração o state pesquisaDeUsuario recebe o parâmetro id na URL, logo o ui-sref ficaria assim:


<li><a ui-sref="pesquisaDeUsuario({id:vm.idUsuario})">Pesquisa de Usuários</a></li>

vm.idUsuario é uma varíavel que foi definida no controller. Ela poderia ser preenchida em um input

<input type="text" ng-model="vm.idUsuario">

A formas básicas de navegação são com $state.go e ui-sref. A segunda opção é melhor para links HTML, pois permite o link ser copiado, já a primeira é ideal para lógicas internas de JavaScript, quando queremos redirecionar o usuário para a página inicial após o login ser realizado com sucesso por exemplo.

No próximo post falaremos sobre como implementar o cadastro de usuários. Operações GET/POST/PUT/DELETE. Não perca a chance de estudar mais sobre o framework pela plataforma de ensino online Alura ou no curso presencial.

E se quiser saber mais sobre o desenvolvimento Mobile, Front-end, Micro Services, APIs e outros não deixe de visitar a Mobile Conf, dia 30/05/2015 no Rio De Janeiro!
 

 

9 Comentários

  1. Raphael 11/02/2015 at 14:14 #

    Olá!

    Muito bom o artigo, faz refletir sobre as novas tecnologias da web, parabéns!

    Mas uma dúvida.. Quanto ao SEO de um site totalmente com angular. Como fizeram?

    Obrigado!

  2. Flávio Almeida 11/02/2015 at 14:46 #

    Olá Rafael, mesmo o simplório módulo de rotas do Angular possui recursos interessantíssimos como uso de interceptadores, bastante utilizado quando lidamos com autenticação/autorização ou com virtual pages. Aliás, dou um exemplo clássico de autenticação utilizando este recurso no meu livro sobre MEAN http://goo.gl/Vq5zYT que faz uma cobertura generosa do Angular entre outras coisas. Parabéns pelo post!

  3. Raphael Lacerda 11/02/2015 at 15:06 #

    Pois é Flávio, eu ia até falar sobre o Interceptor aqui mas ia ficar muito grande!

    Por que vc não faz um post sobre o interceptor?

  4. Rafael Ponte 12/02/2015 at 11:30 #

    Excelente post, Rapha!

    Agora me veio uma dúvida: tendo todo o workflow de navegação no client-side, isso não poderia ser uma brecha de segurança? Como o Angular ou plugins amigos ajudam a lidar com isso?

    Qual sua opinião sobre isso, já que você tem trabalhado bastante com ele?

    Um abraço!

  5. Fernando Moraes 12/02/2015 at 16:01 #

    @Rafael Ponte
    Acredito que não seja uma brecha de segurança, pois a parte cliente é responsável apenas por consumir uma api. O cliente não deve possuir informações sensíveis, isso deve ficar no server. E no server sim, não pode ter brecha de segurança.

  6. Alan Ghelardi 16/02/2015 at 09:47 #

    Ótimo post, Raphael.

    Outro aspecto útil é a possibilidade de definir atributos nas routes do Angular (e.g. o título da página, caso este seja estático) e depois, utilizar esses atributos quando a rota é modificada: $rootScope.$on(“$routeChangeSuccess”, function(event, currentRoute) { … });

    Sobre a segurança, concordo com o Fernando. Acredito que a preocupação seja idêntica à de uma aplicação Web tradicional. A API Rest precisa gerenciar o acesso dos usuários aos dados e a aplicação cliente só reflete esse processo. Mesmo que um usuário mal intencionado hackeie o cliente, ele não alterará os dados que estão no servidor. Por exemplo, quando na aplicação cliente é acessada a URL /x/1, o cliente faz uma requisição à API para obter o recurso x com id 1. A API verifica se o usuário tem autorização para visualizar o recurso e em caso positivo, devolve os dados. Caso contrário, devolve um status forbidden. A aplicação cliente cuida do tratamento da resposta. Nesse caso, poderia haver um interceptador para redirecionar o usuário para uma página específica quando ocorre uma resposta com status 403.

    Abraços!

  7. Cezar Cruz 04/03/2015 at 12:25 #

    Angular.JS for the Win.

  8. Mauricio Guedes 10/07/2015 at 20:30 #

    Estou pensando em utilizar o roteamento do Angular, indiferentemente de qual das duas formas abordadas neste tópico, me reparei com uma situação que gostaria de discuti-la.

    Estou pensando em desenvolver uma aplicação de médio porte, em torno de 400 a 500 páginas, tendo então 400 a 500 controllers, entre diversas factorys e services.

    Integrando o AngularJS com o RequireJS eu posso carregar dinamicamente essas páginas, mas o porém é que, indiferentemente de estar utilizando todos os meus javascripts, eu vou precisar carrega-los ele para que as rotas funcione, a minha dúvida é, isso é viável? Carregar 500 arquivos de javascript?

  9. Mauricio Guedes 10/07/2015 at 20:31 #

    Lembrando que tudo isso é para que minha aplicação funcione de forma de Single Page.

Deixe uma resposta