Multiple views, multiple resolve but for one controller in AngularJS - javascript

I have 2 views that share the same controller. In each one of this view I used resolve to retrieve some date before displaying it. Then I inject it in my controller (so I inject two dependancy for each view).
But the problem is when I go from a view to another, the console display an error because it doesn't see the dependancy from the other view. This is my route configuration
.state('ambassade', {
url: '/mot_ambassadeur',
views: {
"#": {
templateUrl: "pages/GestionAmbassade/mot_ambassadeur.html",
controller: "GestionAmbassadeController",
resolve: {
informationsAmbassade: function(GestionAmbassadeService) {
return GestionAmbassadeService.getMotAmbassadeurService();
}
}
}
}
})
.state('personnel', {
url: '/personnel',
views: {
"#": {
templateUrl: "pages/GestionAmbassade/personnel.html",
controller: "GestionAmbassadeController",
resolve: {
personnelAmbassade: function(GestionAmbassadeService) {
return GestionAmbassadeService.getPersonnelService();
}
}
}
}
})
This is my controller, the 2 injections are informationsAmbassade and personnelAmbassade :
.controller('GestionAmbassadeController', function ($rootScope, $scope, $injector, $sce,
informationsAmbassade,
personnelAmbassade) {
$scope.getMotAmbassadeur = function () {
if (localStorage.getItem("lang") == "fr") {
$scope.motAmbassadeur = $sce.trustAsHtml(informationsAmbassade.contents[0].translation.fr_fr.contenu);
$scope.load = true;
}
$scope.photoAmbassadeur = informationsAmbassade.contents[0].content.path;
};
$scope.getPersonnel = function () {
$scope.Personnels = [];
if (localStorage.getItem("lang") == "fr") {
for (var i = 0; i < personnelAmbassade.contents.length; i++) {
//if ( angular.isDefined(res.contents[i].type) && res.contents[i].type.nom == 'personnel' )
$scope.Personnels.push({
nom: $sce.trustAsHtml(personnelAmbassade.contents[i].translation.fr_fr.contenu),
poste: $sce.trustAsHtml(personnelAmbassade.contents[i].translation.fr_fr.titre)
});
}
$scope.load = true;
}
};
So when I go to ambassade route it doesn't see the personnalAmbassade injection and vice-versa. I know I can use the two resolves for each state but this is what I'm avoiding for performance purpose. Can someone help me figure out this "issue".

You should see the inject in the controller as an Interface from more object orientated languages.
So name it something like entityService and make it so both services you are trying to inject have a method of the same name that you can call from the controller. (Or in your case 'contents' arrays)
.controller('GestionAmbassadeController', function ($rootScope, $scope, $injector, $sce,entityService) {
Then in the resolve put:
entityService: return GestionAmbassadeService.getPersonnelService();
or
entityService: return GestionAmbassadeService.getMotAmbassadeurService();
Depending on the controller instance.
Edit: It doesn't seem like a good idea to have the same controller for both states it this case. You will only be able to use one of the two defined scope functions anyway. So it would be beter to just have separate controllers.

Thank you guys. Both answers solved the problem (I tried them :D). But I think the first one is more suitable because in the second one when I have too many pages for the same controller it will be a little slower than the first answer.
But both still works. Thank you.
edit : when I said the first one I was talking about Bob Brinks answered. It moves down when he edited it.

You should create a top abstract state to resolve your datas, then you are sure to load the right dependencies (both of them) when you are in your controller.
.state('app', {
abstract:true,
template:'<div ui-view></div>',
resolve:{
informationsAmbassade: function(GestionAmbassadeService) {
return GestionAmbassadeService.getMotAmbassadeurService();
},
personnelAmbassade: function(GestionAmbassadeService) {
return GestionAmbassadeService.getPersonnelService();
}
}
}
.state('app.ambassade', {
url: '/mot_ambassadeur',
views: {
"#": {
templateUrl: "pages/GestionAmbassade/mot_ambassadeur.html",
controller: "GestionAmbassadeController"
}
}
}
})
.state('app.personnel', {
url: '/personnel',
views: {
"#": {
templateUrl: "pages/GestionAmbassade/personnel.html",
controller: "GestionAmbassadeController"
}
}
})

Related

When will the digest cycle be triggered, if at all?

I have a small problem, I am using a route like
.when('/beta/profiles/new', {
controller: 'ProfilesController',
controllerAs: 'vm',
templateUrl: 'profiles/new.html',
resolve: {
action: function() { return "new"; }
}
})
.when('/beta/profiles/index', {
controller: 'ProfilesController',
controllerAs: 'vm',
templateUrl: 'profiles/index.html',
resolve: {
action: function() { return "index"; }
}
})
now in my controller I have something like this:
function ProfilesController(profileService, action) {
var vm = this,
permittedActions = ["index", "new"];
var actions = {
index: function() {
vm.hideProfile = {
currentProfile: null,
showModal: false,
setCurrentProfile: function(profile_id) { this.currentProfile = profile_id },
toggleHide: function() {/*...*/}
}
profileService.all().then(function(data) {
vm.profiles = data.result;
})
},
new: {/*.. */}
}
if(permittedActions[action] > -1) actions[action]();
}
now my question is if I hit a link (in the header,lets say) multiple times in succession, each time I hit profiles/index.html should it re-initialize everything ?
if I want to takethe advantage of angular's dirty checking thing should I put the vm.hideProfile out of the index function ? and If I do that should I also not do the same with vm.profiles = [];?
how/what can I do to check if variables are getting re-initialized and angular's dirty checking is not in play, or is it just common sense!! or should I just have a separate controller for each action?
and in case of re-initialization, is there any better way so that I can keep the hideProiles inside the index because I really don't need my new and show actions to know about hideProfile, since its unnecessary?
A few things that hopefully can help:
Dirty checking is for objects bound to an active $scope (and $rootScope). https://docs.angularjs.org/guide/scope
To use $scope in your controller, (a) inject it. e.g. ProfilesController($scope, profileService, action), (b) put things on it, e.g. $scope.vm = vm;
looking at your code, the raw initialization should be super fast.
Don't think javascript is secure. If security is a real concern for your app, protect at the server.
Lastly on app arch, since angular is an MVC framework: define functions in your controller, and connect them to actions in the view via $scope. eg
$scope.index = function() { // do stuff
if (action !== "index") {return}; // disallowed by route
//continue
}

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.

Angular UI Router replace view on runtime

I'm trying to change a parent view template on runtime - inside a service.
My app config looks like:
$stateProvider
.state('base', {
abstract: true,
views: {
'header': {
controller: 'HeaderCtrl',
templateUrl: 'header.html'
},
'': {
template: '<div ui-view="main"></div>'
}
}
})
.state('base.home', {
url: '/',
views: {
'main': {
controller: 'SomeContentCtrl',
templateUrl: 'content.html'
}
}
});
I then have a service which is called from SomeContentCtrl that will listen for an event and upon such event I want to set the templateUrl for the header to null. Something like:
angular
.module('RemoveTemplate', [ ])
.factory('RemoveTemplate', ['$window', '$view', '$state',
function RemoveTemplate ( $window, $view, $state ) {
var windowElem = angular.element($window);
var listen = function ( ) {
windowElem.on('RemoveTemplate', function ( event ) {
$view.load('header#base', {
templateUrl: null
});
// Trying both, even tried without refreshing the state
$state.reload();
$state.go('wh.lobby');
});
};
return {
listen: listen
};
}
]);
});
But this isn't working at all. Have anyone came across a similar use case before?
Thanks
You can specify a templateURL function that runs each time you navigate to the state.
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#template-templateurl-templateprovider
this method can check if it should supply a url or something else;
example:
$stateProvider.state('home', {
url: '/',
templateUrl: function () {
if (header === true) {
return 'app/templates/home.html';
} else {
return somethingElse;
}
}
})
If you want to 'hide' the header section without removing the html try to use a ng-show directive inside the header tag and check the actual state.
<div ng-show="state.is('base')">
This way you only need to inject the $state service in the controller.
When the state is base the div will show, if you navigate to child states it will hide.
p.s. $state.is returns a boolean, this method works with ng-if as well.

Angular UI-Router Overwrite Resolve in Child States

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}}

Categories