Angular UI Router Nested State resolve in child states - angularjs

In an angular app I'm working on, I'd like there to be an abstract parent state which must resolve certain dependencies for all of its children's states. Specifically, I'd like all states requiring an authenticated user to inherit that dependency from some authroot state.
I'm running into issues having the parent dependency not always being re-resolved. Ideally, I'd like to have the parent state check that the user is still logged in for any child state automatically. In the docs, it says
Child states will inherit resolved dependencies from parent state(s), which they can overwrite.
I'm finding that the parent dependency is only being re-resolved if I enter any child state from a state outside the parent, but not if moving between sibling states.
In this example, if you move between states authroot.testA and authroot.testB, the GetUser method is only called once. When you move to the other state and back, it will get run again.
I am able to put the User dependency on each of the child states to ensure the method is called every time you enter any of those states, but that seems to defeat the purpose of the inherited dependency.
Am I understanding the docs incorrectly? Is there a way to force the parent state to re-resolve its dependencies even when the state changes between siblings?
jsfiddle
<!doctype html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.0/angular-ui-router.min.js"></script>
<script>
(function(ng) {
var app = ng.module("Test", ["ui.router"]);
app.config(["$stateProvider", "$urlRouterProvider", function(sp, urp) {
urp.otherwise("/testA");
sp.state("authroot", {
abstract: true,
url: "",
template: "<div ui-view></div>",
resolve: {User: ["UserService", function(UserService) {
console.log("Resolving dependency...");
return UserService.GetUser();
}]}
});
sp.state("authroot.testA", {
url: "/testA",
template: "<h1>Test A {{User|json}}</h1>",
controller: "TestCtrl"
});
sp.state("authroot.testB", {
url: "/testB",
template: "<h1>Test B {{User|json}}</h1>",
controller: "TestCtrl"
});
sp.state("other", {
url: "/other",
template: "<h1>Other</h1>",
});
}]);
app.controller("TestCtrl", ["$scope", "User", function($scope, User) {$scope.User = User;}]);
app.factory("UserService", ["$q", "$timeout", function($q, $timeout) {
function GetUser() {
console.log("Getting User information from server...");
var d = $q.defer();
$timeout(function(){
console.log("Got User info.");
d.resolve({UserName:"JohnDoe1", OtherData: "asdf"});
}, 500);
return d.promise;
};
return {
GetUser: GetUser
};
}]);
})(window.angular);
</script>
</head>
<body ng-app="Test">
<a ui-sref="authroot.testA">Goto A</a>
<a ui-sref="authroot.testB">Goto B</a>
<a ui-sref="other">Goto Other</a>
<div ui-view>Loading...</div>
</body>
</html>

The way I find the ui-router exceptional, is in the behaviour you've just described.
Let's think about some entity, e.g. Contact. So it would be nice to have a right side showing us the list of Contacts, the left part the detail. Please check the The basics of using ui-router with AngularJS for quick overvie about the layout. A cite:
ui-router fully embraces the state-machine nature of a routing system.
It allows you to define states, and transition your application to
those states. The real win is that it allows you to decouple nested
states, and do some very complicated layouts in an elegant way.
You need to think about your routing a bit differently, but once you
get your head around the state-based approach, I think you will like
it.
Ok, why that all?
Because we can have a state Contact representing a List. Say a fixed list from perspective of the detail. (Skip list paging filtering now) We can click on the list and get moved to a state Contact.Detail(ID), to see just selected item. And then select another contact/item.
Here the advantage of nested states comes:
The List (the state Contact) is not reloaded. While the child state Contact.Detail is.
That should explain why the "weird" behaviour should be treated as correct.
To answer your question, how to handle user state. I would use some very top sibling of a route state, with its separated view and controller and some lifecycle arround... triggered in some cycles

Real Short answer is use:
$rootScope.$on("$stateChangeStart")
to listen for any scope changes and do appropriate actions.
Longer answer is, check out the fiddle:
http://jsfiddle.net/jasallen/SZGjN/1/
Note that I've used app.run which means i'm resolving the user for every state change. If you want to limit it to state changes while authRoot is in the parentage, put the check for $stateChangeStart in the authRoot controller.

Related

Running ui.router in modal and ui-view

So not certain if this is possible with ui.router, but thought I would add a nice little modal that launches and says welcome to the site, give the user a chance to enter some details and so forth.
Setup is that the base view have two ui-views (mainview and menuview), and I added formview in the modal. I can get it to work so that when the modal opens it loads formview
.state('form-welcome', {
views: {
formview:
{
templateUrl: "/modals/form-welcome",
},
},
//parent:"index"
})
Didn't actually think it would work that easy, but it did, the problem is that as soon as it has loaded, it resets mainview and menuview (and as it is a modal, that means the background goes grey).
I tried to make form-welcome a child of index (the initial view), however, that breaks the whole thing. (index looks as follows)
.state('index', {
url:"/int/index",
views: {
mainview: {
templateUrl: "/pages/index",
controller: "sketchMagController"
},
menuview: {templateUrl: "/pages/top-menu"},
},
})
I can reload all three views (mainview, menuview and formview), and other than a flickering screen its not to bad. But is there a way I can limit state, so that it only changes formview but leaves the other ones alone.
Reason is that I want to change formview through five different screens, and hate flickering pages:)
It seems to me like it should be possible, but I may have missunderstood how it works
UI-router is for changing the application state, not nesting views together. For that purpose you have angular#component.
var app = angular.module('app', []);
app.component('myModal', {
template: '<div ng-show="isShowing">Hello User</div>',
controller: 'modalCtrl'
});
app.controller('modalCtrl', ['$scope', 'modalService', function($scope, modalService) {
//Save showing state in a service (default to false) so you don't popup your modal everytime user visit homepage
$scope.isShowing = modalService.getShowStatus();
$scope.pressButton = function() {
$scope.isShowing = false;
modalService.setShowStatus(false);
}
});
Then using ui-router, declare your index state, with its template as follow
<-- INDEX.HTML -->
<my-modal></my-modal>
<div ui-view='mainview'></div>
<div ui-view='menuview'></div>
The power of ui-router is the ability to replace the ui-views by different template each different state
stateIndex: abstract
stateIndex.stateA: mainview: /home.html
stateIndex.stateB: mainview: /information.html
So ask yourself, will menuview gonna change to different templates in future states? If not, make it a component.
I would try a different approach, and not having this "welcome" modal part of UI router. It doesn't sounds it should be a "state" in an app, where you can navigate to etc.
I would just pop up this welcome modal after your app finished to bootstrap (e.g. in your run() method or after w/e logic you have to start your app), based on your business logic (e.g. show it only one time).
Why not try $uibModal, part of ui-bootstrap? https://angular-ui.github.io/bootstrap/
It sounds like its everything you need, It pretty much has its own state (includes its own view and controller) so piping data in is easy. You can even pass on data captured within the modal with a simple result.then(function(){} . We use it at work and it doesn't reload the state its on.
You'll probably just want to have it be a function that runs automatically in your controller. If you want to limit how often it pops up you can even have some logic for determining when it pops up in the resolve, pass it to your controller and open the modal based on the resolve.
I think the best way to accomplish what you want is to listen on state changed event and fire the modal. The idea is you fire the welcome modal when the first view is opened and then you set variable in localStorage or some service (depends what you want to achive). Here is an example
$rootScope.$on(['$stateChangeSuccess', function(){
var welcomeModalFired = localStorage.get('welcomeModalFired');
if(!welcomeModalFired) {
//fire modal
localStorage.set('welcomeModalFired', true);
}
})

Is it acceptable to load controllers in the route AND body?

Most articles demonstrate one method or the other... specifying the controller in the route OR the body. While I realize specifying the controller in the route provides additional benefits WHEN NEEDED (pre-loading required view resources, etc.), it is illogical to think that a modular application will be able to (or SHOULD) handle all of the functionality for a complex view.
Any proven examples (links) showing a combined approach would really help ease my mind.
Thanks, in advance.
No.
You may use both, but not for the same instance of a controller.
As an example, you can use ng-controller for your page menu controller and define the page controller loaded into your ui-view in the state config. This is fine as long as the page view does not have an ng-controller the same controller.
Your controllers constructor will run twice if you invoke it twice.
Added Snippet to Demonstrate
So in the following code we bring in the controller IndexCtrl in the uiRouter state config and in the index.html view. As you can see if you run this snippet, you have two instances of the IndexCtrl running.
Why is this a problem?
It is not a problem if this is your intention. It becomes a problem if this is your intention, to have two instances of the controller. If you follow this pattern unknowingly and create an application you will have an unneeded instance of every controller.
This will also cause developer confusion. How come I can't get back the value of a property in a controller? You will see bugs like this because a developer will set a property on one instance of a controller and then expect the same value to be in the other instance.
Controllers are classes. They are constructor functions -- so you can have multiple instances of them. And there are valid use cases to have multiple instances of a controller. But you shouldn't accidentally have two instances.
angular.module('app', ['ui.router'])
.config(['$stateProvider',
function($stateProvider) {
$stateProvider.state('index', {
url: '*path',
controller: "IndexCtrl as vm1",
templateUrl: 'index.html'
});
}
])
.controller('IndexCtrl', IndexCtrl);
function IndexCtrl() {
this.value = 0;
}
IndexCtrl.prototype.add = function() {
this.value += 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js"></script>
<div ng-app="app">
<ui-view></ui-view>
<script type="text/ng-template" id="index.html">
<div ng-controller="IndexCtrl as vm2">
Controller1 {{vm1.value}} Controller2 {{vm2.value}}
<button ng-click="vm1.add()">Add Ctrl1</button>
<button ng-click="vm2.add()">Add Ctrl2</button>
</div>
</script>
</div>

AngularJS UI-Router get the current state from within a view

Considering the following states taken from the ui-router documentation:
.state('state1', {
url: '/state1',
templateUrl: 'partials/state1.html'
controller: 'State1Ctrl'
})
.state('state1.list', {
url: '/list',
templateUrl: 'partials/state1.list.html',
})
And the controller for "partials/state1.html" for state "state1":
.controller('State1Ctrl', function () {
});
Is there any built-in feature to determine from within the controller or within the template, what state the controller/template is associated with?
For example:
.controller('State1Ctrl', function ($state) {
console.log($state.this); // state1
});
And if there is no built-in solution, how would you "decorate" $state or $stateParams, to contain this information?
The only solution I've come up with is to use $state.get() and then find the state with the controller or template value. This seems incredibly messy, though.
You can access the current state configuratin object like this:
$state.current
For further information take a look at the $state documentation.
You can do it as follow,
$state.current.name //Return the name of current state
Couldn't find this documented anywhere, so I looked in the source code.
There is a data field named $uiView attached to the ui-view element, it contains the view name and the associated state. You can get this state like this:
elem.closest('[ui-view]').data('$uiView').state
or even
elem.inheritedData('$uiView').state
We can see what is defined for current state, using the $state.current, check this example showing:
state1
{
"url": "/state1",
"template": "<div>state1 <pre>{{current | json }}</pre><div ui-view=\"\"></div> </div>",
"controller": "State1Ctrl",
"name": "state1"
}
list
{
"url": "/list",
"template": "<div>list <pre>{{current | json }}</pre></div>",
"controller": "State1Ctrl",
"name": "state1.list"
}
the controller:
.controller('State1Ctrl', function($scope, $state) {
$scope.current = $state.current
});
check that here
EXTEND: The above example will always return current state - i.e. if there is hierarchy of three states and we access the last state ('state1.list.detail') directly:
<a ui-sref="state1.list.detail({detailId:1})">....
Each controller will be provided with the same state: $state("state1.list.detail").
Why? beause this state has enough information what are all the views (hierarchically nested) and their controllers needed. We can observe that all in the
$state.$current // not .current
Quickly discussed here cite:
In addition, users can attach custom decorators, which will generate new properties within the state's internal definition. There is currently no clear use-case for this beyond accessing internal states (i.e. $state.$current), however, expect this to become increasingly relevant as we introduce additional meta-programming features.
BUT: there is NO way, how to get information from current controller instance, to which $state it belongs! Even any iterations, searching through some $state.get('stateName') will be unreliable, because simply there is no kept relation that way. One controller Class could be used for many views as different Instances. And also, from my experience, I do not remember any case, when I needed to know such information... wish now it is a bit more clear
This is useful if you are looking for Current state name, $state.current.name
Not sure it is the same version, but on 0.3.1 you can use this to get the current state:
$state.$current.name
And to perform a check:
$state.is('contact.details.item');
Documentation:
https://ui-router.github.io/ng1/docs/0.3.1/index.html#/api/ui.router.state.$state
A working "out of the box" Controller from your code, which shows state:
.controller('State1Ctrl', function ($state) {
console.log("Current State: ", $state.current.name);
});
If you want to check the current state
console.log("state", $state.current)
If you want to check the name of the current state name
console.log("statename", $state.current.name)

angular-ui-router: resolve on root state

As far as I understand ui.router
You have $stateProvider
in it you can write $stateProvider.state()
you can write
.state('account', {
url: '/account',
template: '<ui-view/>'
})
.state('account.register', {
url: '/register',
templateUrl: '/account/views/register.html',
data: { title: 'Create Account' },
controller: 'AccountRegister'
})
but 'account' is a child of some kind of root state.
So my thought is, the root state of ui.router has a <ui-view/>, just like 'account' has the template '<ui-view/>', so index.html would be the root state. Even 'home' with url: '/' would be or rather is a child of this root state.
and if I'm able to access the root state and say it should resolve a User service this resolved value should be available on all states (or better on every state that is a child of the root state, aka all). The User service promise makes a $http.get('/api/user/status') and it returns {"user":{id: "userid", role: "role"}} or {"user":null}
This would guarantee that the value returned by the User service is always populated.
How do I access the root state and say it should resolve e.g. User.get()?
Or let me rephrase that.
A user object should be available to all controllers.
The information about the currently logged in user is supplied by a $http.get('/api/user/status')
That's the problem and I'm looking for a DRY solution that, assuming the api is serving, is 100% safe to assume a user object is set, aka the promise is always fulfilled, aka "waiting for the promise to resolve before continuing".
As of ui-router 1.0.0-beta.3 you can now do this easily using transition hooks:
angular
.module('app')
.run(run);
function run($transitions, userService) {
// Load user info on first load of the page before showing content.
$transitions.onStart({}, trans => {
// userService.load() returns a promise that does the job of getting
// user info and populating a field with it (you can do whatever you like of course).
// Just remember to finally return `true`.
return userService
.load()
.then(() => true);
});
}
The state manager will wait for the promise to resolve before continuing loading of states.
IMPORTANT: Make sure that userService loads its data only once, and when it's already loaded it should just return an already resolved promise. Because the callback above will be called on every state transition. To deal with login/logout for example, you can create a userService.invalidate() and call it after doing the login/logout but before doing a $state.reload().
Disclaimer — messing around with you root scope is not a good idea. If you're reading this, please note that the OP, #dalu, wound up canning this route and solved his issue with http interceptors. Still — it was pretty fun answering this question, so you might enjoy experimenting with this yourself:
It might be a little late, but here goes
As mentioned in the comments and from my experience so far, this is not possible to do with the ui-router's api (v0.2.13) seeing as they already declare the true root state, named "". Looks like there's been a pull request in for the past couple years about what you're looking for, but it doesn't look like it's going anywhere.
The root state's properties are as follows:
{
abstract: true,
name: "",
url: "^",
views: null
}
That said, if you want to extend the router to add this functionality, you can do this pretty easily by decorating the $stateProvider:
$provide.decorator('$state', function($delegate) {
$delegate.current.resolve = {
user: ['User', '$stateParams', httpRequest]
};
return $delegate
});
Note that there are two currents: $delegate.current - the raw "" state - and $delegate.$current - its wrapper.
I've found a bit of a snag, though, before this becomes the solution you were looking for. Every time you navigate to a new state, you'll make another request which has to be resolved before moving forward. This means that this solution isn't too much better than event handling on $stateChangeStart, or making some "top" state. I can think of three work-arounds off the top of my head:
First, cache your http call. Except I can see how this pretty much invalidates certain use-cases, perhaps you're doing something with sessions.
Next, use one of your singleton options (controller/service) to conditionally make the call (maybe on just set a flag for first load). Since the state is being torn down, it's controller might be as well - a service might be your only option.
Lastly, look into some other way of routing - I haven't used ui.router-extras too much, but sticky states or deep state redirect might do the trick.
I guess, lastly, I'm obligated to remind you to be careful with the fact that that you're working on the root-level. So, i mean, be about as careful as you should be when doing anything in root-level.
I hope this answers your question!
Here is different approach
Add following code to your app's .run block
// When page is first loaded, it will emit $stateChangeStart on $rootScope
// As per their API Docs, event, toState, toParams, fromState values can be captured
// fromState will be parent for intial load with '' name field
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState){
// check if root scope
if(fromState.name == ''){
// add resolve dependency to root's child
if(!toState.resolve) toState.resolve = {};
toState.resolve.rootAuth = ['$_auth', '$q', '$rootScope', function($_auth, $q, $rootScope){
// from here on, imagine you are inside state that you want to enter
// make sure you do not reject promise, because then you have to use
// $state.go which will again call state from root and you will end up here again
// and hence it will be non ending infinite loop
// for this example, I am trying to get user data on page load
// and store in $rootScope
var deferred = $q.defer();
$_auth.user.basic().then(
function(authData){
// store data in rootscope
$rootScope.authData = authData;
deferred.resolve(authData);
console.log($rootScope.authData);
},
function(){
$rootScope.authData = null;
deferred.resolve(null);
console.log($rootScope.authData);
}
);
return deferred;
}];
}
});
Use $state.go('name', {}, {reload:true}); approach in case you need to refresh authData on state change (so that transition state will always start from root else resolve to load authData will never get called as once root is loaded, it never needs to get called again {except page reload}).
Use the resolve attribute on the state definition.
.state('root', {
resolve: {
user: ['$http', function($http) {
return $http.get('/api/user/status')
}]
},
controller: function($scope, user) {
$scope.user = user; // user is resolved at this point.
}
})
see:
https://github.com/angular-ui/ui-router/wiki#resolve
https://egghead.io/lessons/angularjs-resolve (not ui-router specific, but works the same)
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#what-do-child-states-inherit-from-parent-states

angularjs ui-router - how to build master state which is global across app

<html ng-app="app">
<head>
...
</head>
<body>
<div id="header"></div>
<div id="notification"></div>
<div id="container"></div>
<div id="footer"></div>
</body>
</html>
With the given structure of the app (derived from angular-app):
header: Here the site navigation, login/out toolbar etc comes in. This is dynamic and has it's own Controller
notification: Global notification container.
container: This used to be my <ng-view>. So this is where all other modules loads in.
footer: Global footer.
How do the state hierarchy looks like? I've gone through the example which shows a single module (contacts) but typically an app would have a global (root) state and inside the root state other module states are rendered.
What I'm thinking is my app module probably have the root state and then each module should have it's own state and I have to inherit from root state. Am I right?
Also from ui-state example, they have used both $routeProvider and $urlRouterProvider as well as $stateProvider has url defined. My understand was $stateProvide also handles routing. If I'm wrong, which provider should I use for routing?
EDIT: http://plnkr.co/edit/wqKsKwFq1nxRQ3H667LU?p=preview
Thanks!
The approach you took in your plunker is close. #ben-schwartz's solution demonstrates how you'd set defaults in your root state for the three essentially-static views. The thing missing in your plunker is that your child states still need to reference the top container view.
.state('root',{
url: '',
views: {
'header': {
templateUrl: 'header.html',
controller: 'HeaderCtrl'
},
'footer':{
templateUrl: 'footer.html'
}
}
})
.state('root.about', {
url: '/about',
views: {
'container#': {
templateUrl: 'about.html'
}
}
});
Note views: { 'container#': ...} instead of just templateUrl: ... in 'root.about'
What you may also be asking about is whether you can have modules define their own state-sets, which you then attach to your app's state hierarchy. A sort of plug-and-play for the routes/states each module provides.
To achieve this you'll have tightly couple your modules to your main app.
In the module:
angular.module('contact', ['ui.router'])
.constant('statesContact', [
{ name: 'root.contact',
options: {
url: 'contact',
views: {
'container#': {
templateUrl: 'contacts.html'
}
},
controller:'ContactController'
}}
])
.config(['$stateProvider', function($stateProvider){
}])
Then, in the app:
var app = angular.module('plunker', ['ui.router', 'contact']);
app.config( ['$stateProvider', 'statesContact',
function($stateProvider, statesContact){
$stateProvider
.state('root',{ ... })
.state('root.home', { ... })
.state('root.about', { ... })
angular.forEach(statesContact, function(state) {
$stateProvider.state(state.name, state.options);
})
}]);
This means all your modules will need to be compatible with this pattern set out in your app. But if you accept this constraint you can then choose include any combination of your modules, and their states magically get added to your app. If you want to get even fancier, you can modify state.options.url in your statesModuleName loop to, for example, prefix your module's url structure.
Also note that the module ui.compat is only necessary when you are transitioning from $routeProvider to $stateProvider. You should normally use ui.state instead.
Also don't forget to adjust in header.html $state.includes('root.contact'))
updated plunker
Although confusing, the FAQ in the ui-router wiki seems to say that this isn't possible: ui-router/wiki/faq
One approach is to allow each feature to define it's own root state (as in this example: AngularJS State Management with ui-router)
Another would be to define the entire state hierarchy in your "myApp" module and just leverage controllers etc. from the dependent modules. This works especially well when you are maintaining a mobile and desktop site; define each site's state hierarchy in a mobileApp and desktopApp module, and have the functionality (e.g. controllers, services, etc.) shared in separate modules.
It depends how you prefer to approach it.
All scopes inherit from rootScope so you may place global state there. The NG literature specifically mentions that you should only put global state there, and not functionality. Functionality required across the system belongs in services, and specifically you should not implement services whose sole purpose is to retain state. All the advice seems to be implicitly shared in the context of how it either facilitates or hampers your ability to easily do end to end testing.

Resources