Angular UI-Router Overwrite Resolve in Child States - javascript

I'm running into the issue, that I have the same resolve function in parent & child states - and depending on the child state, i would like to have it return a different value.
Somehow, instead of overwriting the implementation for it, it simply just takes the behavior from the parent state.
.state('wines', {
url: '/wines',
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "featured";
}
}
}).state('wines.featured', {
url: "/featured",
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "featured";
}
}
}).state('wines.curatorsChoice', {
url: "/curators-choice",
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "curators-choice";
}
}
}).state('wines.stillAvailable', {
url: "/still-available",
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "still-available";
}
}
});
Here, it always keeps on returning "featured", even when visiting wines/still-available, where I expect merchandiseView to be "still-available".
This is my controller:
angular.module('clubwApp').controller('cwProductsController', [
'$scope', 'cwProduct', '$stateParams', 'merchandiseView', function($scope, cwProduct, $stateParams, merchandiseView) {
console.log(merchandiseView);
$scope.wines = cwProduct.available();
return $scope.merchandiseView = angular.copy(merchandiseView);
}
]);
Is there a way, how i can overwrite this?

Attach you specific data to the data state property instead of the resolve.
At controller initialization time read $state.current.data from the $state injectable.

This is apparently an issue (up until 0.2.15).
Here is a hacky solution to the problem:
http://plnkr.co/edit/j1wCThlv1uczlX7d5cdg?p=preview
the resolve: {test: {this.data.someObject}}

Related

Why can’t I pass parameters between two states (with a parent-child relation) using $stateParams in Ionic v1.3.3?

Codes setting up the router (the two states have a parent-child relation):
.state("tab.my-profile", {
url: "/my/profile",
views: {
"tab-my": {
templateUrl: "templates/tab-my-profile.html",
controller: "MyProfileCtrl"
}
}
})
.state("tab.my-profile-mobileinput", {
url: "/my/profile/mobileinput",
views: {
"tab-my": {
params: {"mobile": null}
templateUrl: "templates/util-mobile-input.html",
controller: "MobileInputCtrl",
}
}
})
Codes in the controller of the parent state:
.controller("MyProfileCtrl", function ($scope, $state) {
$scope.goToMobileInput = function () {
$state.go("tab.my-profile-mobileinput", {"mobile": "123456"})
};
})
Codes in the controller of the child state:
.controller("MobileInputCtrl", function ($scope, $stateParams) {
alert($stateParams.mobile); // undefined
})
I can jump to the child state. But in the child state’s controller, I can’t receive the parameter (got an “undefined”). I’ve been stuck in this problem for hours. Could anyone help me find a way out? Thanks a lot in advance.
In my apps, I set parameters on url.
.state("tab.my-profile-mobileinput", {
url: "/my/profile/mobileinput/:mobile",
views: {
"tab-my": {
templateUrl: "templates/util-mobile-input.html",
controller: "MobileInputCtrl",
}
}
})

Injecting into lazy loaded AngularJS Controller when using ocLazyLoad

I started using ocLazyload to lazy load few of my AngularJs controllers. I have used it along with the $routeProvider like this
$routeProvider.when("/login", {
templateUrl: "login.html",
resolve: {
loadCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load('LoginCtrl.js');
}]
}
}).
and this works fine.
I have another route definition which uses resolve property to resolve few items before loading the controller.
when("/dashboard", {
templateUrl: "dashboard.html",
controller: "DashboardCtrl",
resolve: {
profileData: getProfile,
count : getCount
}
}).
Now I want to lazy load this controller too, and I tried it like this
when("/dashboard", {
templateUrl: "dashboard.html",
resolve: {
profileData: getProfile,
count : getCount,
loadCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load(['DashboardCtrl.js']);
}]
}
}).
The page loads in this case, but the profileData and count doesn't get injected into the controller. The controller definition is as given below.
var app = angular.module('gt');
app.controller('DashboardCtrl', ['$scope', '$rootScope', 'profileData', 'count',
function($scope, $rootScope, profileData, count) {
...
}]);
On debugging, I realise that the getProfile and getCount method get's called, but it happens asynchronously and the controller also lazy loads without waiting for these methods. How do I inject and lazy load at the same time? Can I use promises to resolve this in any way?
I am using AngularJS 1.3.10 & ocLazyLoad 1.0.5 versions
getProfile function for reference
var getProfile = function($q, $http, Profile, localStorageService) {
var deferred = $q.defer();
if (!localStorageService.get("loggedInUser")) {
$http.post('/loggedin').success(function(user) {
if (user) {
localStorageService.set("loggedInUser", user.email)
Profile.get(localStorageService.get("loggedInUser"), function(profileData) {
if (profileData) {
deferred.resolve(profileData);
} else {
deferred.resolve();
}
});
} else {
deferred.reject();
}
});
} else {
Profile.get(localStorageService.get("loggedInUser"), function(profileData) {
if (profileData) {
deferred.resolve(profileData);
} else {
deferred.resolve();
}
});
}
return deferred.promise;
}
getProfile.$inject = ["$q", "$http", "Profile", "localStorageService"];
I could get this working with the following configuration of $routeProvider
when("/dashboard", {
templateUrl: "dashboard.html",
controller :"DashboardCtrl"
resolve: {
profileData: getProfile,
count : getCount,
loadCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load(['DashboardCtrl.js']);
}]
}
}).
where DashboardCtrl is the controller defined in DashboardCtrl.js
does getProfile and getCount return a promise? I would guess this is the problem as this is required. Every object put in resolve should return a promise. see the documention
If you need your resolves to happen in a specific order, you can inject them into another resolve, like this:
when("/dashboard", {
templateUrl: "dashboard.html",
resolve: {
profileData: getProfile,
count : getCount,
loadCtrl: ['$ocLazyLoad', 'profileData', 'count', function($ocLazyLoad, profileData, count) {
return $ocLazyLoad.load(['DashboardCtrl.js']);
}]
}
}).

AngularJS UI Router data persists when navigating from grand-child to parent

I am having weird issue probably caching issue while navigating from grand-child(/dashboard/1/production) to parent(/dashboard).
Following are few screenshots:
The selections i.e Delphi-UI and production shouldn't persists.
Following is my snippet of application config:
$stateProvider
.state('root', {
url: '/',
views: {
'header': {
templateUrl: 'ngapp/templates/header.html'
}
}
})
// dashboard routes
.state('root.dashboard', {
url: 'dashboard',
views: {
'content#' : {
templateUrl: 'ngapp/home/templates/dashboard.html',
controller: 'DashboardCtrl',
controllerAs: 'vm'
}
}
})
.state('root.dashboard.app', {
url: '/{id:int}',
views: {
'body#root.dashboard' : {
templateUrl: 'ngapp/home/templates/dashboard-body.html',
controller: 'DashboardBodyCtrl'
}
}
})
.state('root.dashboard.app.env', {
url: '/:name',
views: {
'body#root.dashboard' : {
templateUrl: 'ngapp/home/templates/env-content.html',
controller: 'EnvContentCtrl'
}
}
});
And DashboardCtrl is:
controllers.controller('DashboardCtrl', ['$scope', '$http', '$state', '$timeout', 'appsFactory', function($scope, $http, $state, $timeout, appsFactory) {
$scope.envs = [];
$scope.deps = [];
$scope.envBtnText = $scope.appBtnText = "Choose here";
$scope.headerTitle = "Environment Configuration And Management";
$scope.appStatus = {
isopen: false
};
$scope.envStatus = {
isopen: false
};
appsFactory.list(function(data) {
$scope.apps = data;
});
}]);
Full controller code : http://goo.gl/BWtiU5
Project hosted here : https://github.com/budhrg/atlantis-dashboard
Also, navigating back to Atlantis UI(dashboard) doesn't reset data like
$scope.envs, $scope.deps, $scope.envBtnText and $scope.appBtnText.
What might be issue here? Am I missing anything?
Nested States & Views
When the application is in a particular state—when a state is "active"—all of its ancestor states are implicitly active as well. Below, when the "contacts.list" state is active, the "contacts" state is implicitly active as well, because it's the parent state to "contacts.list".
Your controller isn't getting re-instantiated (expected). There are a couple ways to handle this.
See:
How to make angular ui-router's parent state always execute controller code when state changes?

Pass sub-state from sub-module to the main module in angular.js with angular-ui-router

I design my SPA like this:
angular.module('app', ['submodule0', 'submodule1']);
Main module:
$stateProvider.state("sub0index", {
url: "/sub0",
// pass states defined in submodule0, is that possible?
}).state("sub1index", {
url: "/sub1",
// pass states defined in submodule1
})
And here are some states defined in submodule0
$stateProvider.state("index", {
url: "/index",
templateUrl: "template/index.html"
}).state("info", {
url: "/info",
templateUrl: "template/info.html"
})
So is that possible that I pass sub-state from sub-module to the main module? I ask this because now I define all my state in my main module, I think it may be more elegant to define the state of one submodule in the submodule itself.
And another question is: I'm not sure my module design is reasonable or not, is my submodules not necessary? Or just keep my whole app logic to one module? Thanks.
====Edited====
And here is the problem I've met.
var app = angular.module('test', ['ui.router', 'app.sub']);
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('index', {
url: "/a",
views: {
"general": {
templateUrl: "/template.html"
}
},
resolve: {
data: 'GetDataService'
}
});
}
The service GetDataService is defined in my submodule app.sub, and here is the service:
angular.module('app.sub',['ui.router'])
.service('GetDataService', ['$stateParams', function($stateParams) {
console.log($stateParams);
return null; // return null just for demo
}]);
The output of console.log($stateParams) is an empty object. But if use the service which is defined in its own module, the current state can be get correctly. So whats the issue?
===Edit===
Thanks for the example, it works fine if give a factory to data directly. But how about I give it a string?
I check the document of ui-router, and there is something about map object in resolve:
factory - {string|function}: If string then it is alias for service.
So if I use the code like this:
resolve: {
data: "GetDataService"
}
And the definition of GetDataService:
.service('GetDataService', ['$stateParams', function($stateParams) {
console.log($stateParams);
return null;
}])
But output of console.log($stateParams) is always an empty object.
Do I have some misunderstanding about the api document?
===Edit again===
If I use code like this:
resolve: {
// data: "GetDataService"
data: ['$stateParams', function($stateParams) {
console.log($stateParams);
return null;
}]
}
I can get the params object.
I would say, that modules should not stop us... we can split the app into many if needed.
But I would suggest: Services should be independent on $state.current. We should pass to them function parameters as needed, but these should be resolved outside of the Service body.
Bette would be to show it in action - there is one working example
This is the service:
angular.module('app.sub',['ui.router'])
.service('DataService', ['$state', function($state) {
return {
get: function(stateName, params){
console.log(stateName);
console.log(params);
return stateName;
}
}
}]);
And here is some adjsuted state def:
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('index', {
url: "/a/{param1}",
views: {
"general": {
templateUrl: "tpl.html"
}
},
resolve: {
data: ['DataService','$stateParams'
, function(DataService,$stateParams, $state){
return DataService.get('index', $stateParams)
}],
},
});
}])
Hope it helps a bit. The plunker link
Because this approach is ready to test service without any dependency on some "external" $state.current. We can just pass dummy, testing params

AngularJS ui-router: how to resolve typical data globally for all routes?

I have an AngularJS service which communicates with the server and returns
translations of different sections of the application:
angular
.module('utils')
.service('Translations', ['$q','$http',function($q, $http) {
translationsService = {
get: function(section) {
if (!promise) {
var q = $q.defer();
promise = $http
.get(
'/api/translations',
{
section: section
})
.success(function(data,status,headers,config) {
q.resolve(result.data);
})
.error(function(data,status,headers,config){
q.reject(status);
});
return q.promise;
}
}
};
return translationsService;
}]);
The name of the section is passed as the section parameter of the get function.
I'm using AngularJS ui-router module and following design pattern described here
So I have the following states config:
angular.module('app')
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('users', {
url: '/users',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('users');
}
]
},
templateUrl: '/app/users/list.html',
controller: 'usersController',
controllerAs: 'vm'
})
.state('shifts', {
url: '/shifts',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('shifts');
}
]
},
templateUrl: '/app/shifts/list.html',
controller: 'shiftsController',
controllerAs: 'vm'
})
This works fine but as you may notice I have to explicitly specify translations in the resolve parameter. I think that's not good enough as this duplicates the logic.
Is there any way to resolve translations globally and avoid the code duplicates. I mean some kind of middleware.
I was thinking about listening for the $stateChangeStart, then get translations specific to the new state and bind them to controllers, but I have not found the way to do it.
Any advice will be appreciated greatly.
Important note:
In my case the resolved translations object must contain the translations data, not service/factory/whatever.
Kind regards.
Let me show you my approach. There is a working plunker
Let's have a translation.json like this:
{
"home" : "trans for home",
"parent" : "trans for parent",
"parent.child" : "trans for child"
}
Now, let's introduce the super parent state root
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view=""></div>',
resolve: ['Translations'
, function(Translations){return Translations.loadAll();}]
});
This super root state is not having any url (not effecting any child url). Now, we will silently inject that into every state:
$stateProvider
.state('home', {
parent: 'root',
url: "/home",
templateUrl: 'tpl.html',
})
.state('parent', {
parent: 'root',
url: "/parent",
templateUrl: 'tpl.html',
})
As we can see, we use setting parent - and do not effect/extend the original state name.
The root state is loading the translations at one shot via new method loadAll():
.service('Translations', ['$http'
,function($http) {
translationsService = {
data : {},
loadAll : function(){
return $http
.get("translations.json")
.then(function(response){
this.data = response.data;
return this.data;
})
},
get: function(section) {
return data[section];
}
};
return translationsService;
}])
We do not need $q at all. Our super root state just resolves that once... via $http and loadAll() method. All these are now loaded, and we can even place that service into $rootScope:
.run(['$rootScope', '$state', '$stateParams', 'Translations',
function ($rootScope, $state, $stateParams, Translations) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.Translations = Translations;
}])
And we can access it anyhwere like this:
<h5>Translation</h5>
<pre>{{Translations.get($state.current.name) | json}}</pre>
Wow... that is solution profiting almost from each feature coming with UI-Router... I'd say. All loaded once. All inherited because of $rootScope and view inheritance... all available in any child state...
Check that all here.
Though this is a very old question, I'd like to post solution which I'm using now. Hope it will help somebody in the future.
After using some different approaches I came up with a beautiful angularjs pattern by John Papa
He suggest using a special service routerHelperProvider and configure states as a regular JS object. I'm not going to copy-paste the entire provider here. See the link above for details. But I'm going to show how I solved my problem by the means of that service.
Here is the part of code of that provider which takes the JS object and transforms it to the states configuration:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
$stateProvider.state(state.state, state.config);
});
I transformed it as follows:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
var resolveAlways = {
translations: ['Translations', function(Translations) {
if (state.translationCategory) {
return Translations.get(state.translationCategory);
} else {
return {};
}
}],
};
state.config.resolve =
angular.extend(state.config.resolve || {}, resolveAlways || {});
$stateProvider.state(state.state, state.config);
});
});
And my route configuration object now looks as follows:
{
state: ‘users’,
translationsCategory: ‘users’,
config: {
controller: ‘usersController’
controllerAs: ‘vm’,
url: ‘/users’.
templateUrl: ‘users.html'
}
So what I did:
I implemented the resolveAlways object which takes the custom translationsCategory property, injects the Translations service and resolves the necessary data. Now no need to do it everytime.

Categories