AngularJS UI-Router parent state reloaded - angularjs

I'm trying to create some kind of inbox app with messages list on the left, and the detail of the selected message on the right, using UI-Router to do this.
I've a parent state that resolves the messages list from database, and a child state that resolves the selected message from database (id in parameter).
Problem is : when navigating between messages (child state, juste the id parameter changing), the parent state is also reloaded (view, controller... and the messages list). I don't want it, and I don't understand why it happens.
Navigation - first solution tried (parent reloaded)
<a ui-sref="app.messengers.show({id: row.id})"></a>
Navigation - second solution tried (parent reloaded)
// Solution 2.1
$state.go('app.messengers.show', {id: id});
// Solution 2.2
$state.transitionTo('app.messengers.show', {id: id});
Tried also to add the parameter "reload: false" in both solutions (ui-sref-opts for first solution, in third parameter for the second) : parent reloaded.
UI-Router
.state('app.messengers', { // parent state
abstract: true,
url: '/messenger',
templateUrl: 'app/messengers/list.html',
controller: 'MessengersController',
resolve: {
load: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load([
'app/messengers/messengerService.js',
'app/messengers/messengersController.js'
]);
}],
messages: ['load', 'MessengerService', function (load, MessengerService) {
return MessengerService.list();
}]
}
})
.state('app.messengers.show', { // child state
url: '/:id',
templateUrl: 'app/messengers/detail.html',
controller: 'MessengerDetailController',
params: {
id: null
},
resolve: {
load1: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load([
'app/messengers/messengerDetailController.js'
]);
}],
message: ['load', '$stateParams', 'MessengerService', function (load, $stateParams, MessengerService) {
var regexp = new RegExp('^\\d+$');
return (regexp.test($stateParams.id)) ? MessengerService.get($stateParams.id) : null;
}]
}
})

Try this:
$state.go('^. show', {id: id});
The ^ means parent state.

Oh my god...
I forgot that I was doing some checks in the $stateChangeStart (Authentication purpose)... and if everything's ok :
$state.go(next.name, params, {reload: true});
I've changed the reload parameter to next.name.
It works.
Sorry for that miss...

Related

ui-router's state params in data object

I am using ui-router's state data to decide what page title to display on the page. I have a specific scenario where one state might have 2 different page titles because it is shared.
Say I have page titles 'Directors' and 'HR'. These are parameters passed into the state by $state.go('main.employees', { pageType: 'HR' });
How can I possibly get data to return params.pageType like data: { pageTitle: this.params.pageType }. Is there a way to get params in data?
.state('main.employees', {
url: '/employees',
params: {
pageType: null
},
views: {
'content': {
templateUrl: 'app/employees/employees.html',
controller: 'employeesCtrl as vm'
}
},
data: {
pageTitle: this.params.pageType //<-- I have problem here
}
})
I have to use params.pageType only in this specific case because other states do not have this param. I am relying on $state.$current.data.pageTitle to display it.
I've also tried this but then when calling this with $state.$current.data.pageTitle() I get undefined...
data: {
pageTitle: function($state) {
return $state;
}
}
No need to use the data property. You can directly use the params in the controller using $stateParams service.
Ex-
$state.go('main.employees', { pageType: 'HR' });
$state.go('main.employees', { pageType: 'Directors' });
And in controller access the params as given below-
console.log($stateParams.pageType)

ui-router 1.x.x change $transition$.params() during resolve

Trying to migrate an angularjs application to use the new version of angular-ui-router 1.0.14 and stumbled upon a problem when trying to change $stateParams in the resolve of a state.
For example, previously (when using angular-ui-router 0.3.2) modifying $stateParams worked like this:
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
resolve: {
availableDates: ['CalendarService', '$stateParams', function(CalendarService, $stateParams) {
return CalendarService.getAvailableDates().then(function(response){
$stateParams.firstAvailableDate = response[0];
return response;
});
}]
}
})
The problem is firstAvailableDate is populated after a resolve and I do not know how to update $transition$.params() during a resolve when usign the new version of angular-ui-router 1.0.14.
I have tried, and managed to update the url parameter with
firing a $state.go('myState', {firstAvailableDate : response[0]}) but this reloads the state, so the screen flickers
modified $transition$.treeChanges().to[$transition$.treeChanges().length-1].paramValues.firstAvailableDate = response[0]; to actually override the parameters. I have done this after looking through the implementation on params() for $transition$.
Although both those options work, they seem to be hacks rather than by the book implementations.
What is the correct approach to use when trying to modify parameters inside a resolve?
Approach with dynamic parameter:
Take a look at this document: params.paramdeclaration#dynamic. Maybe thats what you are looking for: ...a transition still occurs....
When dynamic is true, changes to the parameter value will not cause the state to be entered/exited. The resolves will not be re-fetched, nor will views be reloaded.
Normally, if a parameter value changes, the state which declared that the parameter will be reloaded (entered/exited). When a parameter is dynamic, a transition still occurs, but it does not cause the state to exit/enter.
This can be useful to build UI where the component updates itself when the param values change. A common scenario where this is useful is searching/paging/sorting.
Note that you are not be able to put such logic into your resolve inside your $stateProvider.state. I would do this by using dynamic parameters to prevent the state reload. Unfortunally, the dynamic rules doesn't work when you try to update your state (e.g. by using $stage.go()) inside the resolve part. So I moved that logic into the controller to make it work nice - DEMO PLNKR.
Since userId is a dynamic param the view does not get entered/exited again when it was changed.
Define your dynamic param:
$stateProvider.state('userlist.detail', {
url: '/:userId',
controller: 'userDetail',
controllerAs: '$ctrl',
params: {
userId: {
value: '',
dynamic: true
}
},
template: `
<h3>User {{ $ctrl.user.id }}</h3>
<h2>{{ $ctrl.user.name }} {{ !$ctrl.user.active ? "(Deactivated)" : "" }}</h2>
<table>
<tr><td>Address</td><td>{{ $ctrl.user.address }}</td></tr>
<tr><td>Phone</td><td>{{ $ctrl.user.phone }}</td></tr>
<tr><td>Email</td><td>{{ $ctrl.user.email }}</td></tr>
<tr><td>Company</td><td>{{ $ctrl.user.company }}</td></tr>
<tr><td>Age</td><td>{{ $ctrl.user.age }}</td></tr>
</table>
`
});
Your controller:
app.controller('userDetail', function ($transition$, $state, UserService, users) {
let $ctrl = this;
this.uiOnParamsChanged = (newParams) => {
console.log(newParams);
if (newParams.userId !== '') {
$ctrl.user = users.find(user => user.id == newParams.userId);
}
};
this.$onInit = function () {
console.log($transition$.params());
if ($transition$.params().userId === '') {
UserService.list().then(function (result) {
$state.go('userlist.detail', {userId: result[0].id});
});
}
}
});
Handle new params by using $transition.on* hooks on route change start:
An other approach would be to setup the right state param before you change into your state. But you already said, this is something you don't want. If I would face the same problem: I would try to setup the right state param before changing the view.
app.run(function (
$transitions,
$state,
CalendarService
) {
$transitions.onStart({}, function(transition) {
if (transition.to().name === 'mySate' && transition.params().firstAvailableDate === '') {
// please check this, I don't know if a "abort" is necessary
transition.abort();
return CalendarService.getAvailableDates().then(function(response){
// Since firstAvailableDate is dynamic
// it should be handled as descript in the documents.
return $state.target('mySate', {firstAvailableDate : response[0]});
});
}
});
});
Handle new params by using $transition.on* hooks on route change start via redirectTo
Note: redirectTo is processed as an onStart hook, before LAZY resolves.
This does the same thing as provided above near the headline "Handle new params by using $transition.on* hooks on route change start" since redirectTo is also a onStart hook with automated handling.
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
redirectTo: (trans) => {
if (trans.params().firstAvailableDate === '') {
var CalendarService = trans.injector().get('CalendarService');
return CalendarService.getAvailableDates().then(function(response){
return { state: 'myState', params: { firstAvailableDate: response[0] }};
});
}
}
});

ui-router: passing in a param that not in the url?

I am having a problem passing in a param that's not a parameter in the url.
I basically have the following on a click event
let stateParams = {
id: event.info.id,
info: event.info
};
this.$state.go('home.showinfo', stateParams);
I have double checked on the stateParams contains the id and also the info object.
I then have the following setup on the state
.state('home.showinfo', {
url: 'info/info/:id',
resolve: {
info: function($stateParams){
return $stateParams.info;
}
},
params: {
info: null
}
In my controller I am checking the value of $stateparams, and I see the id (also the URL contains the ID), but the info object is null. It's always null. I just want to be able to access it in the controller. Also "this.info" is also null.
I put a breakpoint in the resole and info is null.
I have tried removing the params:{} above and still nothing.
Any ideas what I am doing wrong?
There is a working plunker
The code should be working, check twice the calling side. These links will do what is expected:
<a ui-sref="home.showinfo({id:1, info:'hi'})">
<a ui-sref="home.showinfo({id:2, info:'bye'})">
There is a state as is above:
.state('home.showinfo', {
url: 'info/info/:id',
templateUrl: 'showinfo.tpl.html',
resolve: {
info: function($stateParams){
return $stateParams.info;
}
},
params: {
info: null
}
})
Check it here

Angular ui router view loaded but not passing parameters

I'm working on a website with angular ui-router. There is a page which needs to pass some parameters to another view. I defined my states like this:
.state('locaties', {
url: "/locaties",
data: {rule: function($cookieStore) {} },
controller: "FranchisesCtrl",
templateUrl: "view/locaties.html"
})
.state('locaties.detail', {
params: {
locatieID: 1,
locatieName: "Derptown",
locatieLat: 50,
locatieLong: 50
},
url: "/:locatieName",
controller: "LocatieDetailCtrl",
templateUrl: "view/locatie.html",
resolve: {
locatiedetail:
function ($stateParams, $http){
var url ="http://website/api/franchises/" + $stateParams.locatieID + "/nl.json";
return $http.get(url).then(function(res){
return res.data;
});
}
}
})
Inside LocatieDetailCtrl there's this
angular.module('PremiumMeat').controller('FranchisesDetailCtrl',
function ($scope, $window, franchisedetail) {
$scope.franchiseDetail = franchisedetail;
});
The "Locaties" (plural) view works properly and when I click on a specific "locatie" (single), the url changes and the view gets loaded within the locaties view and no parameters are passed. On the image you can see the top 2 items from the "locaties" view. Then a single locatie is loaded under the "locaties" view. This should be a new page (view) with the parameters from the clicked locatie. Can anyone help me / explain, I'm rather new to angular, thank you.
Solution
The parameters where hard-coded, to make them dynamic, syntax needed adjustment according to angular docs.
params: {
locatieID: {value : "1"},
locatieName: {value : "Stad"},
locatieDescr: {value : "Beschrijving"},
locatieLat: {value: 51.2},
locatieLong: {value : 4.4}
},
Where parameters are passed with ui-href like this
<a ui-sref="locaties.detail({
locatieID: item.id,
locatieName: item.name,
locatieDescr: item.description,
locatieLat: item.location[0].lat,
locatieLong: item.location[0].long
})"
class="detail">Bekijk detail >></a>
The 'params' defined should return the key-value pair object.
But it is a better practice if you are passing values from one state to another to use 'data' instead of appending everything in the URL.
The following code should work :
//The following values are default values of the parameters
.state('locaties.detail', {
params: {
locatieID: '1',
locatieName: 'Derptown',
locatieLat: '50',
locatieLong: '50'
}, ........
This should work. The values expected are of string type and not number.
As far as your LocatieDetailCtrl is concerned, you need to inject what you have in the resolve of the 'locaties.detail' state (i.e. 'locatiedetail'). So your 'LocatieDetailCtrl' should look like following:
angular.module('PremiumMeat').controller('FranchisesDetailCtrl',
function ($scope, $window, franchisedetail, locatiedetail) {
$scope.franchiseDetail = franchisedetail; //make sure you have franchiseDetail as well.
$scope.locatiedetail = locatiedetail;
});
I hope that will work.

Identify current ui view from controller

Is there a guy to know in which ui view a controller is in?
The problem that I have is that a two element(object) array is being sent to a state. Each element of said array is supposed to render in each of my ui-views. The order in which the elements is rendered depends on one of the keys provided in the object. Once I have the value of the key, I can render the views where they correspond, but without knowing the name of the view in which the controller is acting, I can't determine the order.
This is a sample response that I would be getting.
"resources": [
{
"video":"http://url.to/some/video+on+the+server",
"position": 1
}, {
"image":"http://placehold.it/600x450&text=img2",
"position": 0
}
]
This is what my template looks like:
<div class="parent"><div class="col-md-6" ui-view="left"></div></div>
<div class="parent"><div class="col-md-6" ui-view="righ"></div></div>
The content of each view is dependent on the position key that each object has. This is the state responsible for this:
.state('media', {
url: '/media',
views: {
'left': {
templateUrl: '../js/media/media.tmpl.html',
controller: 'MediaController as mediaCtrl'
},
'right': {
templateUrl: '../js/media/media.tmpl.html',
controller: 'MediaController as mediaCtrl'
}
}
})
Your state should configuration should be,
Code
.state('media', {
url: '/media',
views: {
'': {
templateUrl: '../js/media/baseTemplate.html',
controller: 'ParentController as parentCtrl'
},
'left#media': {
templateUrl: '../js/media/media.tmpl.html',
controller: 'MediaController as mediaCtrl'
},
'right#media': {
templateUrl: '../js/media/media.tmpl.html',
controller: 'MediaController as mediaCtrl'
}
}
})
I ended up identifying the instance of each controller by creating a service that essentially acts as a global counter. Upon controller instantiation, it makes a call to the service and returns its current index. The service increments a private variable upon each call. This is what the service ended up looking like:
(function() {
angular.module('screenIdentifier', [])
.factory('screenIdentifier', screenIdentifier);
screenIdentifier.$inject = [];
function screenIdentifier() {
var _currentIndex = 0;
return {
getCurrentIndex: getCurrentIndex,
resetIndex: resetIndex,
assignPosition: assignPosition
};
function assignPosition() {
return _currentIndex += 1;
}
}
})();
By knowing the index of each controller, I can properly identify the index of the view.

Resources