AngularJS – UI-router – Como configurar vistas dinâmicas

Eu tive dificuldade em encontrar qualquer documentação sobre a utilização do roteador-ui dinamicamente através de um database. Par para o curso, tudo é codificado.

Meu Json:

[ { "name": "root", "url": "/", "parent": "", "abstract": true, "views": [ {"name": "header", "templateUrl": "/app/views/header.html"}, {"name" :"footer", "templateUrl": "/app/views/footer.html" } ] }, { "name": "home", "url": "", "abstract" : false, "parent": "root", "views": [ {"name": "container@", "templateUrl": "/app/views/content1.html"}, {"name" :"left@", "templateUrl": "/app/views/left1.html" } ] }, { "name": "about", "url": "/about", "abstract": false, "parent": "root", "views": [ {"name": "container@", "templateUrl": "/app/views/content2.html"}, {"name" :"left@", "templateUrl": "/app/views/left2.html" } ] } ] 

Meu aplicativo:

 'use strict'; var $stateProviderRef = null; var $urlRouterProviderRef = null; var app = angular.module('app', ['ngRoute', 'ui.router']); app.factory('menuItems', function ($http) { return { all: function () { return $http({ url: '/app/jsonData/efstates.js', method: 'GET' }); } }; }); app.config(function ($locationProvider, $urlRouterProvider, $stateProvider) { $urlRouterProviderRef = $urlRouterProvider; $stateProviderRef = $stateProvider; $locationProvider.html5Mode(false); $urlRouterProviderRef.otherwise("/"); }); app.run(['$q', '$rootScope', '$state', 'menuItems', function ($q, $rootScope, $state, menuItems) { menuItems.all().success(function (data) { angular.forEach(data, function (value, key) { $stateProviderRef.state(name = value.name, { "url": value.url, "parent" : value.parent, "abstract": value.abstract, "views": { // do not want the below hard coded, I want to loop // through the respective json array objects and populate state & views // I can do this with everything but views. // first loop 'header': { 'templateUrl': '/app/views/header.html' }, 'footer': { 'templateUrl': '/app/views/footer.html' }, // second loop 'left@': { 'templateUrl': '/app/views/left1.html' }, 'container@': { 'templateUrl': '/app/views/container1.html' }, // third loop 'left@': { 'templateUrl': '/app/views/left2.html' }, 'container@': { 'templateUrl': '/app/views/container2.html' }, } }); }); $state.go("home"); }); }]); 

Estou tendo dificuldades para configurar minhas visualizações dinamicamente. Alguma ideia?


ATUALIZAR:

Eu fiz uma resposta de Plunker por Radim Köhler para qualquer pessoa interessada. Eu agradeço a ajuda.

Acho ui-roteador é o roteador defacto para angular e por ser dynamic vai fazer um aplicativo grande muito mais fácil de gerenciar.

Há uma plunker mostrando como podemos configurar as visualizações dinamicamente. A versão atualizada do .run() seria assim:

 app.run(['$q', '$rootScope', '$state', '$http', function ($q, $rootScope, $state, $http) { $http.get("myJson.json") .success(function(data) { angular.forEach(data, function (value, key) { var state = { "url": value.url, "parent" : value.parent, "abstract": value.abstract, "views": {} }; // here we configure the views angular.forEach(value.views, function (view) { state.views[view.name] = { templateUrl : view.templateUrl, }; }); $stateProviderRef.state(value.name, state); }); $state.go("home"); }); }]); 

Verifique que tudo em ação aqui

Eu tenho que acrescentar uma versão melhorada, aquela que é capaz de fazer mais.

Então, agora vamos carregar estados dinamicamente – usando $http , json e definir estados em .run()

Mas agora podemos navegar para qualquer estado dynamic com url (basta colocá-lo na barra de endereços).

A mágica está embutida no roteador de interface do usuário – veja esta parte do documento:

$ urlRouterProvider

O deferIntercept(defer)

Desativa (ou ativa) a interceptação de alteração de local.

Se você deseja personalizar o comportamento de sincronizar o URL (por exemplo, se desejar adiar uma transição, mas manter o URL atual) , chame esse método no momento da configuração. Em seguida, em tempo de execução, chame $urlRouter.listen() depois de configurar seu próprio manipulador de events $ locationChangeSuccess.

Fragmento citado:

 var app = angular.module('app', ['ui.router.router']); app.config(function($urlRouterProvider) { // Prevent $urlRouter from automatically intercepting URL changes; // this allows you to configure custom behavior in between // location changes and route synchronization: $urlRouterProvider.deferIntercept(); }).run(function($rootScope, $urlRouter, UserService) { $rootScope.$on('$locationChangeSuccess', function(e) { // UserService is an example service for managing user state if (UserService.isLoggedIn()) return; // Prevent $urlRouter's default handler from firing e.preventDefault(); UserService.handleLogin().then(function() { // Once the user has logged in, sync the current URL // to the router: $urlRouter.sync(); }); }); // Configures $urlRouter's listener *after* your custom listener $urlRouter.listen(); }); 

Então o plunker atualizado está aqui . Agora podemos usar até mesmo o .otherwise() para navegar para o estado definido ultimamente, ou ir até lá pelo url:

A fase .config()

 app.config(function ($locationProvider, $urlRouterProvider, $stateProvider) { // Prevent $urlRouter from automatically intercepting URL changes; // this allows you to configure custom behavior in between // location changes and route synchronization: $urlRouterProvider.deferIntercept(); $urlRouterProvider.otherwise('/other'); $locationProvider.html5Mode({enabled: false}); $stateProviderRef = $stateProvider; }); 

A fase .run()

 app.run(['$q', '$rootScope','$http', '$urlRouter', function ($q, $rootScope, $http, $urlRouter) { $http .get("myJson.json") .success(function(data) { angular.forEach(data, function (value, key) { var state = { "url": value.url, "parent" : value.parent, "abstract": value.abstract, "views": {} }; angular.forEach(value.views, function (view) { state.views[view.name] = { templateUrl : view.templateUrl, }; }); $stateProviderRef.state(value.name, state); }); // Configures $urlRouter's listener *after* your custom listener $urlRouter.sync(); $urlRouter.listen(); }); }]); 

Verifique o plunker atualizado aqui

Eu tenho investigado diferentes abordagens para adicionar rotas dinâmicas para ui.router .

Primeiro de tudo eu encontrei o repositori com ngRouteProvider e a principal idéia dele, está resolvendo $stateProvider e salvando-o em encerramento na fase .config e então use essa referência em qualquer outro momento para adicionar novas rotas rapidamente. É uma boa ideia e pode ser melhorada. Acho que a melhor solução é dividir a responsabilidade de salvar as referências dos provedores e manipulá-las em qualquer outro momento e lugar. Veja o exemplo do refsProvider :

 angular .module('app.common', []) .provider('refs', ReferencesProvider); const refs = {}; function ReferencesProvider() { this.$get = function () { return { get: function (name) { return refs[name]; } }; }; this.injectRef = function (name, ref) { refs[name] = ref; }; } 

Usando o refsProvider :

 angular.module('app', [ 'ui.router', 'app.common', 'app.side-menu', ]) .config(AppRouts) .run(AppRun); AppRouts.$inject = ['$stateProvider', '$urlRouterProvider', 'refsProvider']; function AppRouts($stateProvider, $urlRouterProvider, refsProvider) { $urlRouterProvider.otherwise('/sign-in'); $stateProvider .state('login', { url: '/sign-in', templateUrl: 'tpl/auth/sign-in.html', controller: 'AuthCtrl as vm' }) .state('register', { url: '/register', templateUrl: 'tpl/auth/register.html', controller: 'AuthCtrl as vm' }); refsProvider.injectRef('$urlRouterProvider', $urlRouterProvider); refsProvider.injectRef('$stateProvider', $stateProvider); } AppRun.$inject = ['SectionsFactory', 'MenuFactory', 'refs']; function AppRun(sectionsFactory, menuFactory, refs) { let $stateProvider = refs.get('$stateProvider'); // adding new states from different places sectionsFactory.extendStates($stateProvider); menuFactory.extendStates($stateProvider); } 

SectionsFactory e MenuFactory são locais comuns para declaração / carregamento de todos os estados.

Outra ideia que está sendo usada é a existência de fases de aplicativo angular e estender estados de aplicativo por provedores adicionais, algo como:

 angular.module('app.side-menu', []); .provider('SideMenu', SideMenuProvider); let menuItems = [ { label: 'Home', active: false, icon: 'svg-side-menu-home', url: '/app', state: 'app', templateUrl: 'tpl/home/dasboard.html', controller: 'HomeCtrl' }, { label: 'Charts', active: false, icon: 'svg-side-menu-chart-pie', url: '/charts', state: 'charts', templateUrl: 'tpl/charts/main-charts.html' }, { label: 'Settings', active: false, icon: 'svg-side-menu-settings', url: '/' } ]; const defaultExistState = menuItems[0].state; function SideMenuProvider() { this.$get = function () { return { get items() { return menuItems; } }; }; this.extendStates = ExtendStates; } function ExtendStates($stateProvider) { menuItems.forEach(function (item) { if (item.state) { let stateObj = { url: item.url, templateUrl: item.templateUrl, controllerAs: 'vm' }; if (item.controller) { stateObj.controller = `${item.controller} as vm`; } $stateProvider.state(item.state, stateObj); } else { item.state = defaultExistState; } }); } 

Neste caso, não precisamos usar a fase .run para estados de AppRouts e o AppRouts do exemplo acima será alterado para:

 AppRouts.$inject = ['$stateProvider', '$urlRouterProvider', 'SideMenuProvider']; function AppRouts($stateProvider, $urlRouterProvider, SideMenuProvider) { $urlRouterProvider.otherwise('/sign-in'); $stateProvider .state('login', { url: '/sign-in', templateUrl: 'tpl/auth/sign-in.html', controller: 'AuthCtrl as vm' }) .state('register', { url: '/register', templateUrl: 'tpl/auth/register.html', controller: 'AuthCtrl as vm' }) SideMenuProvider.extendStates($stateProvider); } 

Além disso, ainda temos access a todos os itens de menu em qualquer lugar: SideMenu.items