'UI-Router-Extras' - Can't Attach / Ad

2019-09-14 15:26发布

问题:

I am having problems with adding new states to the runtime phase of my app using 'UI-Router-Extras'.

I have been trying for quite some time now to get new states attached and loaded AFTER a user has successfully authenticated using the 'UI-Router-Extras' plugin 'ui-router-extras'

Here is a reference 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 that what's shown or I'm missing something altogether.

EXAMPLE CODE IN PLUNKER - CLICK LOGIN BUTTON -> http://plnkr.co/edit/PQwNQcLNMyPfpke076yy


Code Below Loads and Works:


I was successful in getting the initial app.config() lazy loaded from an external file. Like the code describes 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"]
    }
]

Initial App Load - Successful Lazy Load of Public States To Begin with On a 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 does not work:

PRIVATE - External Initially 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. So far no luck :(

[
    {
        "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 triggering the lazy creation of states 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 a user clicks on a login button it mocks the success and calls '$scope.callNotify' triggering the code you see below. What ends up happening is everything works up until the 'app.futurestateProvider.futureState(newState);' and the trying to call the new state to see if was added '$state.go('app.dashboard');'. All of this results in an error that states 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 what this looks like to me is that the states never really got added when we expected, thus causing an error saying something to the likes of "Sorry, i don't see the route you are 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'm very sorry for not having a working example ready, I'm on it right now, but i figured i'd post this now to show what I do have and possibly get a discussion or solution as to how I can load states to ui-router at runtime via my controller listed above after the application has already loaded the config etc..

What I'm trying to ultimately do here is this:

I really need to only expose two safe public routes to begin with on login. Then once a user logs in the previous public routes stay and i'm trying to add or decorate the existing routes with new ones that now allow the user to only have access to the routes their role provides. Security for us is extremely important and I do not see any benefit whatsoever loading every possible route upfront on a login page letting someone know what out api or server routs are without at least being logged in.

I'm very sorry for rambling but I've come to the conclusion that I'm just flat doing it wrong and need some extra eyes to maybe catch why i can't add new states post load.

Seriously thank you so much!

EXAMPLE CODE IN PLUNKER - CLICK LOGIN BUTTON -> http://plnkr.co/edit/PQwNQcLNMyPfpke076yy

回答1:

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

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

Then after the app injector was setup and the user was authenticated and a role(s) was validated by the server a json response defining the allowed ui-router views / routes / states are looped over and added dynamically to the ui-router state definitions. These routes are defined in the json but so is any accompanying controllers and views that are lazy loaded using $ocLazyLoad.

Overall what I ended up doing was the following:

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'S /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
        };
    }
]);

Public 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"]
                }
            }
        }
    }
]