Skip to content

Instantly share code, notes, and snippets.

@marcoslin
Last active November 28, 2017 09:33
Show Gist options
  • Save marcoslin/b59cfa9a9a44bde04f9f to your computer and use it in GitHub Desktop.
Save marcoslin/b59cfa9a9a44bde04f9f to your computer and use it in GitHub Desktop.
ui-router: Lazy Loading State

Lazy Load States

Sample code for on how to lazy load state for ui-router

h4 {
color: blue;
}
a {
padding-right: 20px;
}
<!DOCTYPE html>
<html ng-app="lazyapp">
<head>
<title>Lazying Loading Routes</title>
<link rel="stylesheet" href="css_style.css">
</head>
<body>
<h3>Lazying Loading Routes: <a href="https://gist.github.com/marcoslin/b59cfa9a9a44bde04f9f" target="_blank">Source</a></h3>
<div ui-view></div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.js" type="text/javascript"></script>
<script src="js_app.js" type="text/javascript"></script>
</body>
</html>
!function() {
var app = angular.module("lazyapp", ['ui.router']),
config_injector;
/**
* Configure state in 2 steps:
* 1. `app.config` will create `home` that leads to `view1` and `view2`
* 2. `LazyRoute` will configure sub-state `view1.profile` and `view1.interest`
*
* `LazyRoute` should be invoked in `View1Ctrl` to simulate the delegation nature
* of `$state` configuration.
*/
app.config(function($stateProvider, $urlRouterProvider, $injector) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'view_home.html',
controller: 'HomeCtrl'
})
.state('view1', {
url: '/view1',
templateUrl: 'view_view1.html',
controller: 'View1Ctrl'
})
.state('view2', {
url: '/view2',
templateUrl: 'view_view2.html',
controller: 'View2Ctrl'
});
$urlRouterProvider
.otherwise('/home');
// Cache injector
config_injector = $injector;
});
app.factory("LazyRoute", function ($log) {
config_injector.invoke(function ($stateProvider) {
$stateProvider
.state('view1.profile', {
url: '/profile',
templateUrl: 'view_view1_profile.html'
})
.state('view1.interests', {
url: '/interest',
templateUrl: 'view_view1_interest.html'
});
$log.log("Sub View1 state configured.");
});
return {};
});
/**
* When going directly to sub-state, load the parent state first then go to the sub-state.
* E.g.: If going to `view1.profile` and it doesn't yet exists, attempt to go to `view1`
* first then go to `view1.profile` if it exists.
*
* Note: The code below only works one level down. Need to improve it to recursively go down
* the route.
*/
app.run(function ($rootScope, $state, $log) {
// var scope = $rootScope.$new();
console.log("$stateNotFound configured.");
$rootScope.$on('$stateNotFound', function(event, unfoundState, fromState, fromParams) {
$log.log("$stateNotFound event: ", event);
$log.log("$stateNotFound unfoundState: ", unfoundState);
$log.log("$stateNotFound fromState: ", fromState);
$log.log("$stateNotFound fromParams: ", fromParams);
var toState = unfoundState.to,
toStateParent = toState;
if (toState.indexOf(".")) {
// Check if parent state is a valid state
var firstToState = toState.split(".")[0];
if ($state.get(firstToState)) {
$log.log("Parent state '" + firstToState + "' found for '" + toState + "'" );
toStateParent = firstToState;
// Load first the parent state
$state.go(toStateParent).then(function (resolved) {
// Only go to final state if it exists.
if ($state.get(toState)) {
return $state.go(toState);
} else {
// Go back to calling state
$log.log("Going back to ", fromState);
return $state.go(fromState.name);
}
});
event.preventDefault();
} else {
$log.error(toState + " state not found.");
}
};
});
});
/**
* Define controllers
*/
app.controller("HomeCtrl", function ($scope) {
$scope.message = "Message from HomeCtrl";
});
app.controller("View1Ctrl", function ($scope, $state, LazyRoute, $log) {
$log.log("view1 controller called with scope id " + $scope.$id);
$scope.isState = $state.is;
$scope.message = "Message from View1Ctrl with state";
});
app.controller("View2Ctrl", function ($scope, $log) {
$log.log("view1 controller called with scope id " + $scope.$id);
$scope.message = "A View2Controller Message";
});
}();
<h2>Home Page</h2>
<div>Message: {{message}}</div>
<hr>
<a ui-sref="view1">View 1</a>
<a ui-sref="view2">View 2</a>
<a ui-sref="view1.profile">View1Profile</a>
<a ui-sref="view1.notexists">View1NotExists</a>
<a ui-sref="view3">View3NotExists</a>
<h2>View1</h2>
<div>View Message: {{message}}</div>
<h3>Detail</h3>
<div ui-view></div>
<h4 ng-show="isState('view1')">View 1 Main Page</h4>
<hr>
<a ui-sref="home">Home</a>
<a ui-sref="view1.profile">Profile</a>
<a ui-sref="view1.interests">Interests</a>
<h4>Nested View: Interest</h4>
<h4>Profile SubView</h4>
<h2>View2</h2>
<div>View Message: {{message}}</div>
<hr>
<a ui-sref="home">Home</a>
<a ui-sref="view1.profile">View1Profile</a>
<a ui-sref="view1.notexists">View1NotExists</a>
<a ui-sref="view3">View3NotExists</a>
@milkshoes
Copy link

Marcos,

I really appreciate this example! It's really helped me actually get something working for a lazy load for post authenticated user login. I am still uncertain of a few things and was wondering if you might know the answers?

  1. Is it possible to remove the lazyloaded states at some point say after a session times out or something requires the states not be registered anymore? Would be really nice to not just be able to add them, but take them the states back out too!
  2. I was going to ask about looping your 'config_injector.invoke' but we seem to be loading the json externally and looping like so below in case anyone also wanted to do the same thing:
'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 == "[email protected]" && password == "abc123"){

                    win.alert('Valid Credentials. Logging In.');
                    //console.log('POST LOGIN ROUTES - NEED TO ADD TO STATES REGISTRY');

                    app.config_injector.invoke(function ($stateProvider) {
                        var nextState = $stateProvider;
                        angular.forEach(response, function(newState) {

                            if(!$state.get(newState.name)){
                                $stateProvider.state(newState.name, newState.params);
                            }

                        });
                        console.log("Private Authenticated Routes Configured.");
                    });

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

                }
            });

        };
    }]);

Again, I really thank you for you sharing this code, it's helped me get really far.

I just needed to have the injected states be not so hard coded into the app and have a way to un-register the states.

Any ideas on removing a state after it's already loaded? Clearing it out of the apps config or registration?

Thanks a Ton Marcos!,
Frankie Loscavio

Copy link

ghost commented Jan 27, 2015

Nice job! It really helped me, tnx for sharing it ;)

@sukrosono
Copy link

thank marcos 😄

@CodeLiftSleep
Copy link

@frankie .success is deprecated, you should be using .then/.catch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment