How to call onEnter when changing from Substate to Parentstate? - angularjs

I wonder why the onEnter function is not called, when changing from substate to parentstate? Is there any other function that gets called? I tried this:
$stateProvider.state('stateA', {
url: '/stateA',
views: {
'content': {
templateUrl: 'views/Aview.html',
controller: 'ControllerA'
},
'dialog': {
template: "<div ui-view='dialog'></div>"
}
},
onEnter: function ($state, olSelectInteractionService){
// IS NOT getting called
},
onExit: function (olSelectInteractionService){
}
});
$stateProvider.state('stateA.sub', {
url: '/sub/:id',
views: {
'dialog': {
templateUrl: 'views/Bview.html',
controller: 'ControllerB', // This Controller does something like $state.go("stateA");
resolve: {
ad: function (AdLoader, $stateParams) {
return AdLoader($stateParams.id);
}
}
}
}
});

Well, the answer is:
when navigating from a child to its parent - onEnter cannot be triggered - because we already are in the parent state. When child state is init, its parent must be as well
But having in place comment in your code:
...
controller: 'ControllerB', // This Controller does something like $state.go("stateA");
resolve: {
...
Especially this:
This Controller does something like $state.go("stateA");
We can adjust that call:
$scope.goToParent = function(){$state.go('parent', null, {reload : true});}
There is a working plunker. It shows that when we are in child state, this won't re-trigger onExit:
<a ui-sref="parent"> // we already are in parent state
But this will:
<button ng-click="goToParent()">
See: go(to, params, options) (mostly reload)
... options (optional) object
Options object. The options are:
location - {boolean=true|string=} - If true will update the url in the location bar, if false will not. If string, must be "replace", which will update url and also replace last history record.
inherit - {boolean=true}, If true will inherit url parameters from current url.
relative - {object=$state.$current}, When transitioning with relative path (e.g '^'), defines which state to be relative from.
notify - {boolean=true}, If true will broadcast $stateChangeStart and $stateChangeSuccess events.
reload (v0.2.5) - {boolean=false}, If true will force transition even if the state or params have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd use this when you want to force a reload when everything is the same, including search params.
Check it here

Related

JavaScript | AngularJS | UI-Router: Force Controller Execution Always

My scenario entails a route with a controller and view for state app.foo. When a user lands on this route via a menu-link, I would like UI-Router to always rerun/execute the controller -- as I have an event being dispatched here to get data from another module. The problem is that if User switches to app.bar and comes back to app.foo, this event never fires.
How can I force UI-Router to run this controller again?
Example:
.state('app.foo', {
url: '/foo',
alwaysReload: true
});
Is this possible?
This is default behavior if you add a controller to the route definition, for ex:
$stateProvider.state('app.foo', {
url: '/foo',
views: {
"main": {
controller: function ($scope) {
console.log('foo controller loaded.');
},
controllerAs: 'foo',
templateUrl: 'foo/foo.tpl.html'
}
}
'foo controller loaded.' will always log when the 'app.foo' state is loaded.

Angular UI-Routing, Page automatically redirects to parent state when refreshed

I am working on a project in which I have used Angular-UI routing. When I try to refresh web page or enter URL directly, It is redirected to parent state. It does load the state of the URL which I have reloaded but then quickly redirects to parent state. Here is my state routing
$stateProvider
.state('home', {
url: "",
views: {
"home": {
templateUrl: root_url + "home"
},
},
})
.state('home.store', {
url: "/store",
views: {
"store": {
templateUrl: root_url + "store"
},
},
})
.state('home.store.storecontent', {
views: {
"storecontent": {
templateUrl: root_url + "storecontent"
}
}
})
Suppose currently I am on home.store.storecontent state. If I refreshed the page here, after reloading current state, It redirects me to the parent state which is home. I want to avoid this.
We have two options in general. First is to define url for child, the second is - make parent abstract which will mean that it child is default.
There is a working example for first approach
In case we would like to keep parent 'home.store' and child 'home.store.storecontent' non-abstract, we have to define unique url for both of them, e.g.:
.state('home.store', {
url: "/store",
...
})
.state('home.store.storecontent', {
url: "/content",
...
})
So, now we have two links:
<a href="#/store">
<a href="#/store/content">
And each of them will on refresh have unique target. Check it here
There is a working plunker for second approach
In case, that we want UI-Router to navigate to a child of 'home.store' state, we just have to make it abstract. Then - if there is a child with url: "" - it will be used as a non abstract state to be initiated.
These adjustment should do that:
.state('home.store', {
url: "/store",
// here, we make home.store to be abstract
abstract: true,
views: {
"store": {
templateUrl: root_url + "store"
},
},
})
.state('home.store.storecontent', {
// here, we say, that instead of parent (which url is selected)
// this child state should be initiated
url: "",
views: {
"storecontent": {
templateUrl: root_url + "storecontent"
}
}
})
Check it here in action
In case, that the 'home.store' should not be abstract, we should give some non empty url to its child - to distinguish these two...
are you using md-tabs with ui-sref and md-active?
if you are, the problem is that md-active fire click on tab, then you are redirected to the parent state.
i was with this problem and it is solved right now with https://github.com/angular/material/issues/5351
On each state change success event you need to cache or store in localStorage the current state name and when you refresh you can load from it if the login is valid.
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {//You can save your storing state name function here}

ui.router: why is url parameter empty for a parent state when navigating to its child

I am trying to get a url parameter at the parent state when navigating to its child states, but at the parent state level, the url parameter is returned as an empty string "".
Here's my state config definition:
$stateProvider
.state("view", {
url: "/{viewName}",
controller: function($scope, $stateParams){
$scope.viewName = $stateParams.viewName;
},
template: "<pre>viewName: {{viewName}}</pre> <div ui-view></div>",
})
.state("view.A", {
url: "^/viewA/{id}",
template: "<div>content of A</div>"
})
.state("view.B", {
url: "^/viewB/{id}",
template: "<div>content of B</div>"
});
(full plunker)
Example urls are:
#/viewA/111
#/viewA/222
#/viewB/555
What I am trying to get is the view name from the URL (e.g. "viewA") at the parent level.
Q: Is it possible to do without resorting to URL regex`ing?
EDIT:
An important requirement to note is to be able to navigate to the child state using ui-sref or $state.go:
<a ui-sref="view.A({id: 1})">
and have the URL change accordingly. This requirement prevents the approach with relative urls for the child states:
.state("view.A", {
url: "/{id}", // relative URL
template: "<div>content of A</div>"
})
To solve for the case above, one can define the viewName in the custom data field of each child:
.state("view.A", {
url: "/viewA/{id}",
template: "<div>content of A</div>",
data: {
viewName: "viewA"
}
})
.state("view.B", {
url: "/viewB/{id}",
template: "<div>content of B</div>",
data: {
viewName: "viewB"
}
});
and use the $state variable to $watch for changes in $state.$current.data:
.state("view", {
controller: function($scope, $state){
$scope.$state = $state; // need to watch $state, not $state.$current
$scope.$watch("$state.$current.data.viewName", function(val){
$scope.viewName = val;
});
},
template: "<pre>viewName: {{viewName}}</pre> <div ui-view></div>",
});
Note, that you cannot just get or watch for $state.$current.data.viewName, because parent's controller runs only once for any changes in state between its descendants.
plunker
Does using $stateParams even make sense?
The URL parameters of the parent state are not defined, presumably because the child state is using an absolute URL, so, the URL definition of the parent (with the requested url parameter) is not used.
Although, it seems in the example described above that this is an omission of functionality, the more I think about this, this functionality does not even fully make sense. What if the parent's URL is completely different from the child:
.state("parent", {url: "/parent/{something}"})
.state("child", {url: "^/child"})
What should $stateParams.something be equal to?

Angular UI Router - Default Parameter

Is there anyway to specify a default parameter for every route using the Angular UI Router?
My app is entered through the context of another application by selecting a user and then navigating to my application. The URL in my application will always have the user ID in the URL so that people can bookmark the URL, email it, etc. So, as you navigate around, the URL always follows a scheme of:
#/{userId}/view/...
#/{userId}/edit/...
etc.
This userId will always be the same for a user inside the app for any route they go to. If they happen to log out, go back to the main app, select a new user and come back to my app, this userId will change, but will be the same value for every route.
Is there anyway to read this value from say a service/factory and then plug it into every route?
EDIT:
I should mention I want to avoid having to explicitly set this parameter on every route when I navigate to a state. For example, I don't want to have to do ui-sref="new-state({userId : blah})" every time I navigate to a new state. That userId will never change in the context of my application.
EDIT AGAIN:
I actually went about this a different way concerning the requirement to not have to send 'userId' to every route manually. Instead of using a directive, I used a $provide.decorator to add this functionality to the 'go' method. I've added an answer below to what I did.
You can declare an abstract parent state from which child states inherit:
$stateProvider
.state('user', {
url: '/:userid',
abstract: true,
resolve:
// assuming some kind of User resource factory
currentUser: function($stateParams, User) {
return User.get($stateParams.userid);
}
}
})
.state('user.view', {
url: '/view', // actual url /:userid/view
controller: function($scope, currentUser) {
// currentUser resource available
}
});
.state('user.edit', {
url: '/edit', // actual url /:userid/edit
controller: function($scope, currentUser) {
// currentUser resource available
}
});
In terms of navigating to a state, you need to pass in the desired user:
$state.go('user.view', {userid: 'myuserid'});
As a consequence it might make sense to create some kind of .go() wrapper method on your currentUser service, so that you needn't specify the user id each time.
UPDATE:
To counter the problem posted in your edit, you could introduce a directive like this:
angular.module('app')
.directive('userSref', function($state) {
return function(scope, elem, attrs) {
var state = 'user.' + attrs.userSref;
elem.bind('click', function() {
$state.go(state, {userid: $state.params.userid});
});
scope.$on('$destroy', function() {
elem.unbind('click');
});
};
});
Then, any future links to user-based states can be done so with:
<a user-sref="view">View User</a>
Instead of writing a directive that handled the auto-sending of userID, I used $provide.decorator as follows:
app.config(['$provide',
function($provide) {
$provide.decorator('$state', function($delegate, UserService) {
// Save off delegate to use 'state' locally
var state = $delegate;
// Save off reference to original state.go
state.baseGo = state.go;
// Decorate the original 'go' to always plug in the userID
var go = function(to, params, options) {
params.userID = UserService.userID;
// Invoke the original go
this.baseGo(to, params, options);
};
// assign new 'go', decorating the old 'go'
state.go = go;
return $delegate;
});
}
]);
I got the idea from this post:
Changing the default behavior of $state.go() in ui.router to reload by default
You can use the "nested states" and "resolves" features of UI-Router to create a hierarchy of states in your app. You'll define a top level state that resolves the userId. Then define any number of child states, and they will automatically inherit the "resolved" userId.
Check out this page of the documentation, in particular the section titled "Important $stateParams Gotcha". I will paste the two code snippets from that page here.
Incorrect method:
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
controller: function($stateParams){
$stateParams.contactId //*** Exists! ***//
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams){
$stateParams.contactId //*** Watch Out! DOESN'T EXIST!! ***//
$stateParams.itemId //*** Exists! ***//
}
})
Correct method using "resolves":
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
controller: function($stateParams){
$stateParams.contactId //*** Exists! ***//
},
resolve:{
contactId: ['$stateParams', function($stateParams){
return $stateParams.contactId;
}]
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams, contactId){
contactId //*** Exists! ***//
$stateParams.itemId //*** Exists! ***//
}
})
Since the "contactId" parameter is resolved by the parent state, the child state will inherit that.

UI-Router - Change $state without rerender/reload of the page

I've been looking at these pages (1, 2, 3). I basically want to change my $state, but I don't want the page to reload.
I am currently in the page /schedules/2/4/2014, and I want to go into edit mode when I click a button and have the URL become /schedules/2/4/2014/edit.
My edit state is simply $scope.isEdit = true, so there is no point of reloading the whole page. However, I do want the $state and/or url to change so that if the user refreshses the page, it starts in the edit mode.
What can I do?
For this problem, you can just create a child state that has neither templateUrl nor controller, and advance between states normally:
// UPDATED
$stateProvider
.state('schedules', {
url: "/schedules/:day/:month/:year",
templateUrl: 'schedules.html',
abstract: true, // make this abstract
controller: function($scope, $state, $stateParams) {
$scope.schedDate = moment($stateParams.year + '-' +
$stateParams.month + '-' +
$stateParams.day);
$scope.isEdit = false;
$scope.gotoEdit = function() {
$scope.isEdit = true;
$state.go('schedules.edit');
};
$scope.gotoView = function() {
$scope.isEdit = false;
$state.go('schedules.view');
};
},
resolve: {...}
})
.state('schedules.view', { // added view mode
url: "/view"
})
.state('schedules.edit', { // both children share controller above
url: "/edit"
});
An important concept here is that, in ui-router, when the application is in a particular state—when a state is "active"—all of its ancestor states are implicitly active as well.
So, in this case,
when your application advances from view mode to edit mode, its parent state schedules (along with its templateUrl, controller and even resolve) will still be retained.
since ancestor states are implicitly activated, even if the child state is being refreshed (or loaded directly from a bookmark), the page will still render correctly.
REF: https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statetransitiontoto-toparams--options
$state.transitionTo('yourState', params, {notify: false});
Adding my answer because I think it's different enough from the accepted answer and may be useful to others:
I had two states, begin and view, with a bunch of optional parameters being synced with the URL for view, like so:
$stateProvider
.state('begin',
{
url: '/',
template: '<app-element></app-element>'
})
.state('view',
{
url: '/View?param1&param2&...&paramN',
template: '<app-element></app-element>'
params: {
param1: {
value: null,
squash: true
},
...
}
});
The link function for <app-element> would run any time I tried to sync the parameters using $state.go. Using {notify: false, reload: false} did not work for me. The link function still ran each time. I'm on 0.2 so dynamic isn't an available param option, either. I followed #b0nyb0y's suggestion and turned it into a parent/child relationship, which worked:
$stateProvider
.state('app',
{
url: '/',
template: '<app-element></app-element>'
})
.state('app.view',
{
url: 'View?param1&param2&...&paramN',
params: {
param1: {
value: null,
squash: true
},
...
}
});

Resources