"UI-Router-Extras" - cannot attach / add new FutureState objects after App.Config () has already been loaded

I am having problems adding new states to the execution phase of my application using UI-Router-Extras .

I've been trying to connect and load new states for quite some time AFTER the user has successfully authenticated with the UI-Router-Extras plugin ui-router-extras '

Here is a link to the 'UI-Router-Extras' Examples for the FutureState documentation that I'm using, but I feel like maybe my scenario is either slightly different from what is shown, or I'm missing something altogether.

EXAMPLE OF CODE IN PLUNKER BUTTON - CLICK LOGIN โ†’ http://plnkr.co/edit/PQwNQcLNMyPfpke076yy


Code below downloads and works:


I was able to get the initial app.config () file loaded from an external file. Like the code below:


PUBLIC - external initially loaded routes using "UI-Router-Extras" - "lazyload / states / public-states.json"

[
    {
        "name": "unauth",
        "url": "/",
        "controller": "LoginCtrl",
        "templateUrl": "app/components/core/login/login.html",
        "roles": ["public"]
    },
    {
        "name": "otherwise",
        "url": "/",
        "roles": ["public"]
    }
]

      

Application Bootstrap - Successfully lazy loading state states to start with login:

'use strict';

var app = angular.module('app', ['ui.router', 'ct.ui.router.extras'])

  .config(function ($urlRouterProvider, $stateProvider, $futureStateProvider) {

        app.stateProvider = $stateProvider; // MIGHT NEED REFERNCE LATER?
        app.futurestateProvider = $futureStateProvider;  // MIGHT NEED REFERNCE LATER?

        // ASYNC LOAD OF ROUTES AVAILABLE - SOON TO BE BY ROLE
        var futureStateResolve = ["$http", function($http) {
            return $http.get("lazyload/states/public-states.json").then(function(response) {
                angular.forEach(response.data, function(state) {

                    $stateProvider.state(state);
                    console.log(state.roles);

                })
            })
        }];

        $futureStateProvider.addResolve(futureStateResolve);
        console.log($futureStateProvider);

  });

      



Code below does NOT work


Below is my code for the part that doesn't work:

PRIVATE - External originally loaded routes using "UI-Router-Extras" - "lazyload / states / public-states.json"

This json below is meant to be added after user login using lazy loading and FutureStates. No luck yet :(

[
    {
        "name": "app",
        "abstract": true,
        "url": "?menu1State",
        "templateUrl": "app/components/core/layout/layout.html",
        "controller": "LayoutCtrl",
        "roles": ["level1"]
    },
    {
        "name": "app.dashboard",
        "url": "/app",
        "views": {
            "feature": {
                "templateUrl": "app/components/core/features/features.html",
                "controller": "FeatureCtrl"
            }
        },
        "roles": ["level1"]
    },
    {
        "name": "app.associations_beanbags",
        "url": "/app/associations/bean-bags?myParam1&myParam2&modalState",
        "views": {
            "feature": {
                "templateUrl": "app/components/core/features/associations/bean-bags.html",
                "controller": "BeanbagsCtrl"
            }
        },
        "roles": ["level2"]
    }
]

      

Login button that triggers lazy state creation after successful authentication:

<a id="btn-fblogin" href="" class="btn btn-primary pull-right" ng-click="callNotify(username, password);">Login</a>

      


What happens is when the user clicks the login button, he mocks the success and calls "$ scope.callNotify " calling the code you see below. As a result, everything works as long as " app.futurestateProvider.futureState (newState); " and trying to invoke a new state to see if " $ state.go ('app.dashboard'); was added . All this results in error indicating the following:

Console error:

Error: Could not resolve 'app.dashboard' from state 'unauth'
at Object.transitionTo (http://localhost:3000/bower_components/angular-ui-router/release/angular-ui-router.js:2521:17)
at Object.$state.transitionTo (http://localhost:3000/bower_components/ui-router-extras/release/ct-ui-router-extras.js:136:34)
at Object.$state.transitionTo (http://localhost:3000/bower_components/ui-router-extras/release/ct-ui-router-extras.js:874:55)
at Object.$state.transitionTo (http://localhost:3000/bower_components/ui-router-extras/release/ct-ui-router-extras.js:1301:48)
at Object.go (http://localhost:3000/bower_components/angular-ui-router/release/angular-ui-router.js:2454:21)
at http://localhost:3000/app/components/core/auth/auth-service.js:58:13
at http://localhost:3000/bower_components/angular/angular.js:8113:11
at wrappedCallback (http://localhost:3000/bower_components/angular/angular.js:11573:81)
at wrappedCallback (http://localhost:3000/bower_components/angular/angular.js:11573:81)
at http://localhost:3000/bower_components/angular/angular.js:11659:26 

      

So it looks like the states were never added when we expected, which would throw an error saying things like "Sorry, I can't see the route you're looking for."

'use strict';

app.controller('AuthServiceTestCtrl', ['$window','$scope','$http', '$state', function (win, $scope, $http, $state) {

    $scope.callNotify = function(username,password, $stateProvider, $futureStateProvider) {
            //notify(username, password); // CALL SERVICE AND GET A RETURN VALUE / ACTION

            var loadedAgain = $http.get("lazyload/states/private-states.json").success(function(response) {

                if(username == "testuser@example.com" && password == "abc123"){

                    console.log('Valid Credentials. Logging In.');

                    // NOW THAT USER IS LOGGED IN REGISTER NEW PRIVATE ROUTE / STATES - PREFERABLY FROM THE SECURED SERVER FILE ('private-states.json') ABOVE
                    var adminModuleFutureStates = [
                        {
                            "name": "app",
                            "abstract": true,
                            "url": "?menu1State",
                            "templateUrl": "app/components/core/layout/layout.html",
                            "controller": "LayoutCtrl",
                            "roles": ["level1"]
                        },
                        {
                            "name": "app.dashboard",
                            "url": "/app",
                            "views": {
                                "feature": {
                                    "templateUrl": "app/components/core/features/features.html",
                                    "controller": "FeatureCtrl"
                                }
                            },
                            "roles": ["level1"]
                        },
                        {
                            "name": "app.associations_bean-bags",
                            "url": "/app/associations/bean-bags?myParam1&myParam2&modalState",
                            "views": {
                                "feature": {
                                    "templateUrl": "app/components/core/features/associations/bean-bags.html",
                                    "controller": "BeanBagsCtrl"
                                }
                            },
                            "roles": ["level2"]
                        }
                    ];

                    angular.forEach(adminModuleFutureStates, function(newState) {
                        console.log(newState);
                        app.futurestateProvider.futureState(newState); // WAS SAVED AS A VAR IN APP CONFIG FOR LATER REFERENCE
                    });

                    // FINALLY GO TO ONE OF THE NEWLY ADDED PRIVATE STATES WE JUST ADDED WHICH IS A DASHBOARD
                    $state.go('app.dashboard');

                }
            });

        };
    }]);

      

I am very sorry that I do not have a working example ready, I am currently on it, but I figured I will post this now to show what I have and maybe get a discussion or solution on how I can upload states in ui-router at runtime via my controller above, after the app has already loaded the config, etc.

What I'm trying to end up doing here is this:

I really only need to open two secure public routes to start with on login. Then, once the user enters the previous public routes, I try to add or decorate the existing routes with new ones, which now only allow the user to have access to the routes that their role grants. Security is extremely important to us and I don't see any benefit in loading every possible route on the login page, letting someone know that from the api or server routes at least not logged in.

I am very sorry about the incoherence, but I have come to the conclusion that I am just flat, doing it wrong and need extra eyes to possibly catch why I cannot add new load to the post.

Thank you very much!

EXAMPLE OF CODE IN PLUNKER BUTTON - CLICK LOGIN โ†’ http://plnkr.co/edit/PQwNQcLNMyPfpke076yy

0


source to share


1 answer


I decided to use the $ ocLazyLoad service instead, but still use the app injector to add routes dynamically after the app has loaded and was initially configured with basic public routes before authentication, etc.

angular.module("app").configInjector.invoke(['$stateProvider', function ($stateProvider) {

      

Then, after the application injector has been configured and the user has been authenticated and the role (s) has been verified by the server, the json response defining the allowed views / routes / states of the ui-router is looped and dynamically added to the ui- of the router state definitions. These routes are defined in json, but there are also any companion controllers and views that are lazy loaded with $ ocLazyLoad.

All in all what I ended up with was this:

angular.module("auth")
.factory('AuthRouteLoaderFactory', ['$window', '$rootScope', '$http', '$state', '$cookieStore','$location', '$timeout','AuthSession',
    function(win, $rootScope, $http, $state, $cookieStore, $location, $timeout, AuthSession) {

        // PRIVATE INTERFACE
        function loadPrivateRoutes() {

            console.log('AuthRouteLoaderFactory :: LOADING PRIVATE ROUTES');

            var loadedPrivateRoutes = $http.get("lazyload/states/private-states.json").success(function (response) {

        angular.module("app").configInjector.invoke(['$stateProvider', function ($stateProvider) {

                    // VERY IMPORTANT - POST LOGIN ROUTE LOADER / INJECTOR;// DYNAMIC AND SERVER DETERMINED JSON ITERATED BASED ON SPECIFIC ROLES PRE-MADE BY SERVER.
                    angular.forEach(response, function (state) {

                        if(!state.abstract){
                            state.views.feature.resolve[state.views.feature.data.controllerAlias] = ['$ocLazyLoad', function($ocLazyLoad){return $ocLazyLoad.load({"name": state.views.feature.data.controllerAlias,"files": state.views.feature.data.controllerFiles})}];
                            state.views.feature.resolve.isAuthenticated = function(){
                // CHECK IF WE ARE A VALID SESSION FOR EACH LAZY LOAD
                //AuthSession.validateToken();
              };
                        }

                        console.log('AuthRouteLoaderFactory :: loadPrivateroutes state loaded -> ' + state.name);
                        $stateProvider.state(state.name, state);
                    });

                    $state.go('app.dashboard');

                }]);

            });

        }

        // FOR NOW WE LOAD ROUTES ANYWAY UNTIL WE CALL API /ME OR PING SERVICE, THEN ON SUCCESS WE LOAD ROUTES
        if(AuthSession.validateToken()){
          $rootScope.hideLoader = true;
          loadPrivateRoutes();
          console.log('AuthRouteLoaderFactory :: SESSION VALIDATION SUCCESSFUL :: PROCEED');
        }

        // PUBLIC INTERFACE
        return {
            testFactory: function() {
                console.log('AuthRouteLoaderFactory :: testFactory');
            },
            isSessionValid: function(){
                return $cookieStore.get('fakeLoginSession');
            },
            invalidateSession: function(){
                $cookieStore.remove('fakeLoginSession');
                $location.path('/login.html');
            },
            loadRoutes: loadPrivateRoutes
        };
    }
]);

      



Open states / routes:

[
    {
        "name": "unauth",
        "url": "/",
        "controller": "LoginCtrl",
        "templateUrl": "app/components/core/login/unauth.html",
        "data": {
            "roles": ["public"]
        }
    },
    {
        "name": "login",
        "url": "/login",
        "controller": "LoginCtrl",
        "templateUrl": "app/components/core/login/unauth.html",
        "data": {
            "roles": ["public"]
        }
    },
    {
        "name": "otherwise",
        "url": "/",
        "data": {
            "roles": ["public"]
        }
    }
]

      

Private states / routes:

[
    { "name": "app", "abstract": true, "url": "", "templateUrl": "app/components/core/layout/layout.html", "controller": "LayoutCtrl" },
    {
        "name": "app.dashboard",
        "views": {
            "feature": {
                "templateUrl": "lazyload/components/core/features/dashboard/dashboard.html",
                "controller": "DashboardCtrl as dashboard",
                "resolve": {},
                "data": {
                    "controllerAlias": "dashboard",
                    "controllerFiles": ["lazyload/components/core/features/dashboard/dashboard-controller.js"]
                }
            }
        }
    },
    {
        "name": "app.associations_role-type",
        "views": {
            "feature": {
                "templateUrl": "lazyload/components/core/features/associations/role-type.html",
                "controller": "RoleTypeCtrl as roleType",
                "resolve": {},
                "data": {
                    "controllerAlias": "roleType",
                    "controllerFiles": ["lazyload/components/core/features/associations/role-type-controller.js"]
                }
            }
        }
    }
]

      

0


source







All Articles