[AngularJs] Carregando dinamicamente as rotas do ngRoute

By | 7 de fevereiro de 2016

AngularJS-Shield-large
Olá pessoal!

Há alguns dias atrás, me deparei com uma necessidade em um projeto pessoal: carregar o roteamento do angular-route dinamicamente, específica para cada perfil de usuário.

Após uma longa pesquisa e incansáveis testes, cheguei a uma solução que venho compartilhar com vocês agora!

Primeiro, vamos considerar um cenário simples onde temos uma index com 3 rotas: home, view1 e view2:

?Download index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html ng-app="testeApp">
  <head>
    <meta charset="utf-8">
    <script type="text/javascript" src="https://code.angularjs.org/1.5.0/angular.min.js"></script>
    <script type="text/javascript" src="https://code.angularjs.org/1.5.0/angular-route.min.js"></script>
    <script type="text/javascript">
      var app = angular.module("testeApp", ["ngRoute"]);
 
      app.config([
          "$routeProvider", function ($routeProvider) {
              $routeProvider.when("/home", {
                  controller: "homeController",
                  templateUrl: "views/home.html"
              });
              $routeProvider.when("/view1", {
                  controller: "viewController",
                  templateUrl: "views/view1.html"
              });
              $routeProvider.when("/view2", {
                  controller: "viewController",
                  templateUrl: "views/view2.html"
              });
              $routeProvider.otherwise({
                  redirectTo: "/home"
              });
          }
      ]);
 
      app.controller("homeController", function(){});
      app.controller("viewController", function(){});
    </script>
  </head>
  <body ng-view>
  </body>
</html>

Aqui criamos uma tela com navegação simples, onde você entra na Home e pode ir pra View 1 ou 2, e voltar para a Home. Até ai sem mistérios.

Mas e se precisamos trazer as rotas do servidor, seja para facilitar a manutenção ou para, assim como eu precisei, filtrar as rotas de acordo com o usuário?

Geralmente a primeira coisa que pensamos é criar um service para buscar essas rotas e então registrar-las, mas logo somos frustrados ao perceber que, dentro do bloco config onde registramos as rotas, não podemos injetar services, apenas providers. Vamos testar:

Abaixo dos controllers, crie o service:

?View Code JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
app.service("routeService", ["$http","$q", function($http, $q){
  this.obterRotas = function(){
    var _deferred = $q.defer();
    $http.get("rotas.txt").success(function (response) {
      _deferred.resolve(response);
    }).error(function (err) {
      _deferred.reject(err);
    });
    return _deferred.promise;
  };
}]);

Neste exemplo, vou obter as rotas de um arquivo texto com o JSON correspondente, mas no seu projeto você pode buscar as rotas de uma WebApi, por exemplo.

Ao tentarmos injetar o service no config, recebemos o seguinte erro: Unknown provider: routeService.
Como contornar o fato de que temos que utilizar um service para recuperar os dados em um bloco que só pode ser injetados providers?
A solução é que por trás de todo service, existe um provider, e a partir dele conseguimos obter a instância do service. Eis como:

?View Code JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
app.config([
    "$routeProvider", "routeServiceProvider", function ($routeProvider, routeServiceProvider){
        var _routeService = routeServiceProvider.$get();
        $routeProvider.when("/home", {
            controller: "homeController",
            templateUrl: "views/home.html"
        });
        $routeProvider.otherwise({
            redirectTo: "/home"
        });
    }
]);

Os providers oferecem a função $get(), que vai fazer exatamente isso. Repare que apaguei as rotas da view, mas mantive a da home e o otherwise. Isto porque quando a página é carregada, como estamos fazendo uma requisição assíncrona para obter as rotas, executa as rotas ANTES deste request terminar. Então para contornar isso, uma rota padrão deve ser configurada manualmente de forma fixa.

Segue o conteúdo do rotas.txt:

?Download rotas.txt
1
2
3
4
5
6
7
8
9
[{
    "url": "/view1",
    "controller": "viewController",
    "view": "views/view1.html"
},{
    "url": "/view2",
    "controller": "viewController",
    "view": "views/view2.html"
}]

Agora precisamos resolver uma coisa: precisamos fazer o ngRoute esperar o request (_routeService.obterRotas()) terminar para registrar as rotas. Para isso, vamos utilizar o parãmetro resolve. Com ele, podemos passar uma promise e aguardar o término da mesma.
Além disso, precisamos registrar as rotas a partir do resultado desta mesma promise.

Segue então as alterações a serem feitas no bloco config:

?View Code JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
app.config([
    "$routeProvider", "routeServiceProvider", function ($routeProvider, routeServiceProvider){
        var _routeService = routeServiceProvider.$get();
        var _obterRotas = _routeService.obterRotas();
        $routeProvider.when("/home", {
            controller: "homeController",
            templateUrl: "views/home.html",
            resolve: {
                rotas: function () {
                    return _obterRotas;
                }
            }
        });
        $routeProvider.otherwise({
            redirectTo: "/home",
            resolve: {
                rotas: function () {
                    return _obterRotas;
                }
            }
        });
        _obterRotas.then(function (rotas) {
            rotas.forEach(function (rota) {
                $routeProvider.when(rota.url, {
                    controller: rota.controller,
                    templateUrl: rota.view,
                    rotas: {
                        routes: function () {
                            return _obterRotas;
                        }
                    }
                });
            });
        });
    }
]);

Pronto! Com estas alterações, a tela já deve estar funcionando exatamente como de início, mas agora trazendo as rotas dinamicamente.
Apague uma das rotas do arquivo txt e recarregue a página. Repare que o link para a view da rota apagada não funcionará.

Segue código completo da página:

?Download index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<!DOCTYPE html>
<html ng-app="testeApp">
  <head>
    <meta charset="utf-8">
    <script type="text/javascript" src="https://code.angularjs.org/1.5.0/angular.min.js"></script>
    <script type="text/javascript" src="https://code.angularjs.org/1.5.0/angular-route.min.js"></script>
    <script type="text/javascript">
      var app = angular.module("testeApp", ["ngRoute"]);
 
      app.config([
          "$routeProvider", "routeServiceProvider", function ($routeProvider, routeServiceProvider){
              var _routeService = routeServiceProvider.$get();
              var _obterRotas = _routeService.obterRotas();
              $routeProvider.when("/home", {
                  controller: "homeController",
                  templateUrl: "views/home.html",
                  resolve: {
                      rotas: function () {
                          return _obterRotas;
                      }
                  }
              });
              $routeProvider.otherwise({
                  redirectTo: "/home",
                  resolve: {
                      rotas: function () {
                          return _obterRotas;
                      }
                  }
              });
              _obterRotas.then(function (rotas) {
                  rotas.forEach(function (rota) {
                      $routeProvider.when(rota.url, {
                          controller: rota.controller,
                          templateUrl: rota.view,
                          rotas: {
                              routes: function () {
                                  return _obterRotas;
                              }
                          }
                      });
                  });
              });
          }
      ]);
 
      app.controller("homeController", function(){});
      app.controller("viewController", function(){});
 
      app.service("routeService", ["$http","$q", function($http, $q){
        this.obterRotas = function(){
          var _deferred = $q.defer();
          $http.get("rotas.txt").success(function (response) {
            _deferred.resolve(response);
          }).error(function (err) {
            _deferred.reject(err);
          });
          return _deferred.promise;
        };
      }]);
    </script>
  </head>
  <body ng-view>
  </body>
</html>

Bom, é isso! Espero que ajude e que tenham gostado. Aberto para dúvidas, sugestões, críticas e elogios.

Att,

Augusto Claro

2 thoughts on “[AngularJs] Carregando dinamicamente as rotas do ngRoute

  1. Gustavo

    Esta de parabéns! Muito bem pensado. Irá ajudar bastante. Já tinha tentado fazer isso, mas também não tinha conseguido.

    Valeu!!

    Reply

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *