How to pass resolved dependencies in state manager view controllers? - javascript

I am trying to access the response from dependencies in the controller. [ui-router]
$stateProvider.state('project', {
url: '/:projectId/:forkId',
resolve: {
foo: function() {
return {
bar: "4143"
};
}
},
views: {
'main': {
templateUrl: 'partials/analysis/project/main.html',
controller: 'mainCtrl'
},
sidebar: {
templateUrl: 'partials/analysis/project/sidebar.html',
controller: 'sidebarCtrl'
}
}
});
My mainCtrl is defined in some other angular module.
Issue: How to access the foo function's returned object in controller?
p = angular.module('project', []);
p.controller('mainCtrl', ['foo', function(foo) {}]);
PS. Code is written in coffeescript.

Probably you should add controller: 'mainCtrl' to the project1 state, and make sure they(controller and the stateProvider config) are in the same module? (the latter is probably unnecessary, didn't test.)
A complete example from Ionic:
http://learn.ionicframework.com/formulas/data-the-right-way/

Related

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.

how to call angular factory method from module setup?

I have a module App and factory i18n, what is the best way to call i18n.load
method form App (config? run? etc?)
angular
.module('App', [
'ngRoute',
'service.i18ndb'
])
.config(function ($routeProvider) {
//want to i18n.load() here somehow
$routeProvider
.when('/signin', {
templateUrl: '../views/sign-in.html',
controller: 'SigninCtrl'
})
.when('/mix', {
templateUrl: '../views/mix.html',
controller: 'MixCreateCtrl'
})
.otherwise({
redirectTo: '/signin'
});
});
angular.module('App')
.factory('service.i18ndb', function() {
return {
load: function() { console.log("Busy"); }
}
}
);
The problem you will always have if you use .run is having to deal with a page that has no i18n loaded. This means you will need to have a way to deal with your view when their is no i18n loaded. You can either hide it or the text will flash with the wrong values at first.
However, AngularJS gives you a wonderful feature to make sure it is loaded before your view is loaded: the resolver!
Here is how to do it.
var i18nResolver = function(service.i18ndb) {
return service.i18ndb.promise;
};
$routeProvider
.when('/signin' {
templateUrl: '../views/sign-in.html',
controller: 'SigninCtrl',
resolve: {
i18n: i18nResolver
}
});
You can fix this code to use the correct promise of your HTTP request or whatever service you are using.
One of the benefits of using this way is you can have a different labels for a different page for your i18n and use the i18n service to recover them no matter where you are.
You are defining your app module twice. One you create your factory, it can be injected to the controller and used there. You could try something like this:
angular.module('App', ['ngRoute','service.i18ndb'])
.factory('service.i18ndb', function() {
return {
load: function() { console.log("Busy"); }
}
})
.config(function ($routeProvider) {
//want to i18n.load() here somehow
$routeProvider
.when('/signin', {
templateUrl: '../views/sign-in.html',
controller: 'SigninCtrl'
})
.when('/mix', {
templateUrl: '../views/mix.html',
controller: 'MixCreateCtrl'
})
.otherwise({
redirectTo: '/signin'
});
})
.controller('SigninCtrl', function($scope, service.i18ndb) {
// Call your factory function here
service.i18ndb.load();
// If the function returns a value you could assign it to a scope
// variable so it can be used in your template 'sign-in.html'
$scope.your_variable = service.i18ndb.load();
});
angular
.module('App', [
'ngRoute'
])
.config(function ($routeProvider) {
//want to i18n.load() here somehow
$routeProvider
.when('/signin', {
templateUrl: '../views/sign-in.html',
controller: 'SigninCtrl'
})
.when('/mix', {
templateUrl: '../views/mix.html',
controller: 'MixCreateCtrl'
})
.otherwise({
redirectTo: '/signin'
});
})
.run(['i18ndb', function(i18ndb) {
i18ndb.load();
}])
.factory('i18ndb', function() {
return {
load : function() {console.log('test')}
};
});
);
You were requiring a module which has not been defined (as far as I can tell). The factory you were adding was on the 'App' module not the 'service.i18ndb'.
You then need to dependency inject the i18ndb factory in to the run method to call it from there (presuming that you want to call that function to bootstrap your app).

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

Injecting services/constants into provider in Angularjs

The problem here is I am able to access the getRoutes(), but I am unable to access the injected constant -"configuration". What am I missing? Thanks.
(function () {
'use strict';
var app = angular.module('app');
app.constant('configuration', {
PARTIAL_PATH: "/app/components/partials"
});
app.module('app', [
'routeService'
]);
var routeServiceModule = angular.module('routeService', ['common']);
routeServiceModule.provider('routeConfig',function () {
this.getRoutes = function () {
return [
{
url: '/login',
config: {
title: 'admin',
templateUrl: 'app/components/login/login.html'
}
}, {
url: '/',
config: {
templateUrl: 'app/components/dashboard/dashboard.html',
title: 'Dashboard'
}
}
];
};
this.$get = ['configuration', function (configuration) {
var service = {
getRoutes: getRoutes(),
configuration: configuration.PARTIAL_PATH
};
return service;
}];
app.config(['$routeProvider', 'routeConfigProvider', function ($routeProvider, routeConfigProvider) {
//Unable to get the configuration value
console.log(routeConfigProvider.configuration);
//Console is returning as "undefined"
routeConfigProvider.getRoutes().forEach(function(r) {
$routeProvider.when(r.url, r.config);
});
$routeProvider.otherwise({ redirectTo: '/' });
}
]);
})();
Created a plunkr demo : http://plnkr.co/edit/2TIqgxMxBJEPbnk2Wk6D?p=preview
(Regarding your last comment, with the plnkr)
The result is expected.
At config time (within app.config() ), you access raw providers, as you defined them, which allows you to call "private" methods or fields (testItem1) and to configure it for run time use. "private" because they won't be accessible at run time.
At run time (within app.run() and the rest of your app), when you ask for a dependency for which you wrote a provider, the angular injector hands you the result of the $get method of your provider, not the provider itself, so you can't access the "private" function.
This page was my path to enlightenment : AngularJS: Service vs provider vs factory
I think you may be over complicating the route stuff. You may have a very good reason for it but as I do not know it may I suggest keeping it simple with something more like this:
MyApp.config(function ($routeProvider) {
$routeProvider.when('/home', {
templateUrl: 'home.html',
controller: 'HomeController',
activeTab: 'home'
})
};
MyApp.controller('HomeController', function ($route) {
console.log($route.current.activeTab);
});
I would be interested in knowing why you may not able to use this routing pattern or purposely chose something different.
I think it has to do with the way you are creating your initial module. Try this:
var app = angular.module('app', []);
app.constant('configuration', {
PARTIAL_PATH: "/app/components/partials"
});
var routeServiceModule = angular.module('routeService', ['app']);

Categories