1994 words10 min read

AngularJS: Definindo rotas com com ngRoute e UI-Router

SPA’s (Single page applications) estão cada dia mais popular. Aplicações inteiras baseadas em single pages utilizando comportamentos e navegando entre views de uma forma dinâmica e rapida. Angular nos ajuda a criar aplicações desse porte facilmente.

Trabalhando com SPA’s a algum tempo, normalmente nos deparamos com rotas e views em nossa aplicação como home, sobre e contato. Isso é muito normal entre SPA’s e com nossa aplicação ira crescer e nós precisamos gerenciar de alguma forma, como apresentar diferentes views para o usuário através de nossa aplicação.

Com Angular podemos avançar muito mais que isso, mas esse não é o contexto desse post, vamos estudar alguns dos conceitos que são necessários para desenvolver grandes aplicações utilizando o melhor do framework.

Vamos aos objetivos desse tutorial (ou ao menos o que vou tentar exemplificar):

A primeira coisa que devemos fazer é definir nossa estrutura de arquivos e carregar a view padrão da nossa aplicação.

Estrutura de arquivos


- script.js // Onde sera armazenado nossa aplicação
- index.html // Layout principal
- pages // Onde ficará localizado nossas páginas
  ----- home.html
  ----- about.html
  ----- contact.html

Criando nosso HTML

Em nosso arquivo index.html, vou utilizar o Bootstrap apenas para termos uma cara mais amigável:


<!DOCTYPE html>
<html lang="en" ng-app="myapp">
<head>
  <meta charset="UTF-8">
  <title>ngRoute</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
</head>
<body>
  <nav class="navbar navbar-default">
    <div class="container-fluid">
      <div class="navbar-header">
        <a class="navbar-brand" href="#">ngRoute</a>
      </div>
      <div>
        <ul class="nav navbar-nav">
          <li><a href="#">Home</a></li>
          <li><a href="#">Sobre</a></li>
          <li><a href="#">Contato</a></li>
        </ul>
      </div>
    </div>
  </nav>

<ng-view></ng-view>

</body>
</html>

Até aqui temos apenas uma coisa nova, o ng-view que é o container onde o nosso conteúdo será renderizado, ele funciona como um include gerenciando nossas páginas de acordo com a URL atual. Em outras palavras ele carrega a view baseada na configuração do nosso router e injeta no lugar da diretiva. Você também deve ter percebido que adicionamos mais uma script a nossa aplicação, o angular-route.js. Nas primeiras versões do Angular, todos os componentes era acoplados no mesmo arquivo, o que fazia ele ficar muito grande para aplicações simples. A partir da versão 1.2 cada módulo é um script separado, o que nos permite injetar apenas o que realmente iremos utilizar em nossa aplicação.

Nossa aplicação Angular

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

// Definindo Rotas
app.config(function($routeProvider){
$routeProvider
.when("/", {
template:"<h1>Home Page</h1>"
})
.otherwise({redirectTo: '/'});
});

Pronto, já conseguimos visualizar o titulo “Home Page” que acabamos de definir utilizando $routeProvider que nos permite configurar nosso serviço $route na etapa de configuração da aplicação. Com o $routeProvider podemos ir ainda mais longe, habilitando o History API do HTML5, injetar controllers especificos para cada página e muito mais.

Existem dois métodos que o $routeProvider nos fornece para configuração de rotas: when que recebe dois parametros sendo uma string com a URL especiica e um objeto de configuração para aquela rota; e otherwise que funciona similar a um else caso a condição não seja satisfeita informamos uma rota já definida para redirecionar o usuário (home/404/etc).

As opções de configuração mais comuns do método when são:

Vamos continuar brincando, agora definindo também um controller para podermos colocar mais informações em nossas páginas e criar uma nova rota, apenas para testes:

/_..._/
.when("/users/:id", {
controller: "UserController",
template:"<h1>Hello {{name}}</h1>"
})
/_..._/

app.controller('UserController', function($scope, $routeParams) {
// Vamos imaginar que a URL acessada foi /users/rafaell
$scope.name = $routeParams.id;
});

Neste exemplo pegamos via $routeParams o parâmetro :id via URL e mostramos ao usuário a mensagem “Hello rafaell” através do objeto $scope no UserController.

O legal do $routeParams é que podemos ter N parametros informados via URL como por exemplo /books/:author/:title, pegar ambas as informações e fazer algo com elas.

Para finalizar nosso exemplo, vamos nossas views:


<!-- home.html -->
<div class="jumbotron text-center">
  <h1>Home Page</h1>
  <p>{{ message }}</p>
</div>

<!-- about.html -->
<div class="jumbotron text-center">
  <h1>About Page</h1>
  <p>{{ message }}</p>
</div>

<!-- contact.html -->
<div class="jumbotron text-center">
  <h1>Contact Page</h1>
  <p>{{ message }}</p>
</div>
/_..._/
.when('/', {
templateUrl : 'pages/home.html',
controller : 'HomeController'
})
.when('/about', {
templateUrl : 'pages/about.html',
controller : 'AboutController'
})
.when('/contact', {
templateUrl : 'pages/contact.html',
controller : 'ContactController'
})
/_..._/

app.controller('HomeController', function($scope) {
$scope.message = 'Routing pages with ngRoute is damn awesome!';
});

app.controller('AboutController', function($scope) {
$scope.message = 'You can see more about ngRoute in the oficial website.';
});

app.controller('ContactController', function($scope) {
$scope.message = 'No. :P';
});

Para finalizarmos, vamos setar as rotas em nossa navbar:


<ul class="nav navbar-nav navbar-right">
    <li><a href="#/">Home</a></li>
    <li><a href="#/about">Sobre</a></li>
    <li><a href="#/contact">Contato</a></li>
  </ul>
</div>

Sim, é estranho utilizar esse # também conhecido como hash ou hashbang, é quem determina qual página esta sendo chamada, isso quer dizer que /#about e /index.html#about são a mesma coisa, já que o index é o arquivo principal da nossa aplicação e gerencia as views através do nosso router.

Eu sei que pode parecer um tanto estranho utilizar desta forma, mas existe um modo para desabilitarmos o hashbang de nossa aplicação:

Habilitando o Modo HTML5

Sem dúvidas a primeira coisa e a mais simples para se fazer em nossa aplicação é configurar o $locationProvider. Isso vai permitir ativar html5Mode, o que também nos força a trocar nossos links da navbar para urls relativas e adicionar <base> tag a nossa <head>.

app.config(function($routeProvider, $locationProvider) {
// Utilizando o HTML5 History API
$locationProvider.html5Mode(true);

/_ ... _/
});

<!-- Head -->
  <base href="/">
</head>

<!-- Navbar -->
<ul class="nav navbar-nav navbar-right">
    <li><a href="/">Home</a></li>
    <li><a href="/about">Sobre</a></li>
    <li><a href="/contact">Contato</a></li>
  </ul>
</div>

Isso força o browser a entender qual é a URL base da aplicação que o documento atual pertence, ou seja, o endereço relativo ao documento atual. Caso seu path não seja o root da aplicação, você deve informar o caminho até seu arquivo index, por exemplo /myapp para utiliza-lo como seu base path.

Não se preocupe, pois o $location service gera fallback para browsers que não suportam o History API utilizando hashbang novamente. Você pode verificar isso na documentação oficial.

Configurando Rewrite no Apache

Agora basta direcionarmos o acesso da nossa aplicação para o index de nossa página, para isso podemos apenas criar um arquivo .htaccess na raiz da nossa aplicação e pronto!

RewriteEngine On

# If an existing asset or directory is requested go to it as it is

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]

# If the requested resource doesn't exist, use index.html

RewriteRule ^ /index.html

O mesmo pode ser feito na configuração de um Virtual Host no Apache ou no Nginx.

Mudando as coisas com UI-Router

Agora chegou a hora de falar de coisa legal, mesmo que o ngRoute faça muito bem o papel dele, as vezes vamos precisar de um plus de configuração, ou de algumas outras coisas imbutidas que esse módulo não pode nos fornecer. #chatiado

Quando falamos de SPA’s, roteamento sem dúvidas é uma das partes mais importantes da nossa aplicação. Mas esta na hora de colocarmos alguns esteroides usando o UI-Router.

Este foi um dos módulos desenvolvidos pelo time do AngularUI que nos permite fazer as mesmas coisas que o módulo anterior, porem de uma maneira diferente baseado no estado do aplicação e não apenas a rota atual.

WAT?!

Quando pensamos em **Estados da aplicação“ vs. “Rotas de URL” temos uma visão e modos diferentes para utilizar nossas views, mudando não apenas o que esta dentro do container do router, mas também partes especificas da nossa aplicação sem adicionar nenhuma mudança a rota atual.

De maneira groceira podemos pensar que funciona semelhante ao ng-inlude, porem de forma muito mais simples e dinâmica.

Chega de papo, vamos mudar um pouco nossa aplicação utilizando agora o UI-Router.


<!-- index.html -->

<!DOCTYPE html>
<html lang="en" ng-app="myapp">
<head>
  <meta charset="UTF-8">
  <title>ngRoute</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
</head>
<body>
  <nav class="navbar navbar-default">
    <div class="container-fluid">
      <div class="navbar-header">
        <a class="navbar-brand" href="#">ngRoute</a>
      </div>
      <div>
        <ul class="nav navbar-nav">
          <li><a ui-sref="home">Home</a></li>
          <li><a ui-sref="about">Sobre</a></li>
          <li><a ui-sref="contact">Contato</a></li>
        </ul>
      </div>
    </div>
  </nav>

  <div ui-view></div>

</body>
</html>

Sim, este é todo o código que precisamos escrever para adaptarmos nosso código ao UI-Router.

Agora vamos modificar nossa aplicação:

//script.js

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

app.config(function($stateProvider, $urlRouterProvider, $locationProvider) {

$urlRouterProvider.otherwise('/');

$stateProvider
.state('home', {
url: '/',
templateUrl: 'home.html',
controller: 'HomeController'
})
.state('about', {
url: '/about',
templateUrl: 'about.html',
controller: 'AboutController'
})
.state('contact', {
url: '/contact',
templateUrl: 'contact.html',
controller: 'ContactController'
});

    // Utilizando o HTML5 History API
    $locationProvider.html5Mode(true);

});

/_ ... _/

Pronto! Vejamos as mudanças para adequar nosso código utilizando UI-Router:

Uma coisa legal de se fazer é trabalhar com o que chamamos de nested views, que básicamente é uma das features mais interessantes do UI-Router depois de named routes, o que nos permite gerenciar views em uma parte especifica de nossa página sem ser necessário qualquer mudança em nossa URL. Infelizmente não vou entrar muito afundo neste post, pois no caso jã esta muito tarde e eu preciso dormir logo.

Ah! Uma outra coisa legal, é que também podemos utilizar a diretiva ui-view que funciona semelhante ao ng-include, com a diferença que os partials a serem carregados precisam ser definidos na configuração do estado daquela rota.

Exemplo:


<!-- contact.html -->

<div class="jumbotron text-center">
  <h1>Contact Page</h1>
  <p>{{ message }}</p>
</div>

<div class="row">

  <div class="col-sm-8">
      <div ui-view="form"></div>
  </div>

  <div class="col-sm-4">
      <div ui-view="maps"></div>
  </div>

</div>
/_ ... _/

.state('contact', {
url: '/contact',
controller: 'ContactController',
views: {
'': { templateUrl: 'contact.html' },
'maps@contact': { template: '<form></form>' },
'form@contact': {
templateUrl: 'contact-form.html',
controller: 'ContactFormController'
}
});

/_ ... _/

Nem preciso citar vantagens certo? Ok, vamos listar alguns pontos:

Conclusão

Estes foram exemplos bem simples, mas acredito que possa ajudar você. Conceitos básicos de views e router utilizando o ngRoute e UI-Router por mais que sejam superficiais, servem como norte para aprender cada vez mais, basta correr atrás. Existem muitas outras coisas bem mais avançadas no mundo Angular.

Se alguem quiser sugerir algum tópico sobre Angular ou até mesmo tenha alguma dúvida em que eu possa ajudar, basta informar nos comentários que irei formular algo para poder te ajudar.

–UPDATE–

Os exemplos de código utilizados neste post estão disponíveis neste Plunker utilizando ngRoute e aqui utilizando UI-Router, porem eles não funcionam no modo HTML5 por conta do base path, porem você pode baixa-los sem problemas.