'invocables' must be an object - angularjs

I have a state as following :
.state('core.recover', {
url: '/recover',
controller: 'RecoverPasswordCtrl',
templateUrl: 'views/tmpl/recoverAccount/recover-password.html'
})
I want when I enter to this state to check something before loading the template, in this case I want to call an api that checks for something if the promise is successful it will continue and display the template, otherwise it will redirect the user to another state.
I tried to do this on the top of the controller but I always see the template for a moment then it redirects me, so I tried to use resolve as in this post :
AngularJS | handle routing before they load
As following :
.state('core.recover', {
url: '/recover',
controller: 'RecoverPasswordCtrl',
resolve: function(recoverAccountService, $location, $state, $q) {
var deferred = $q.defer();
deferred.resolve();
recoverAccountService.get({email:$location.search().email, verificationKey:$location.search().verificationKey})
.$promise.then(function (result) {}).catch(function (err) {
$state.go("candidature.pre");
});
return deferred.promise;
},
templateUrl: 'views/tmpl/recoverAccount/recover-password.html'
})
but it didn't work and I'm getting this error in the browser's console :
Error: 'invocables' must be an object
How can I solve this ?

You're not using the correct syntax, uiRouter is expecting as entry for resolve an object, which keys it will try to evaluate.
Lets abbreviate your resolving function as aimadResolver, such that
var aimadResolver = function(recoverAccountService, $location, $state, $q) {
var deferred = $q.defer();
deferred.resolve();
recoverAccountService.get({ email: $location.search().email, verificationKey: $location.search().verificationKey })
.$promise.then(function(result) {}).catch(function(err) {
$state.go("candidature.pre");
});
return deferred.promise;
}
Of course, this is not mandatory, but I'm doing it for the sake of readability. Then, your state definition should be as follows:
state('core.recover', {
url: '/recover',
controller: 'RecoverPasswordCtrl',
resolve: {'yourResolverName': aimaidResolver}
},
templateUrl: 'views/tmpl/recoverAccount/recover-password.html'
})
Don't forget to inject yourResolverName in RecoverPasswordCtrl, or else your controller will be instantiated without waiting anyway. Source: look for the resolve examples
On the side
I'd like to point out that your use of deferred objects doesn't make sense. You're immediately resolving your deferred object on the second line within your function, which means that recoverAccountservice.get() could still be pending while RecoverPasswordCtrl is already being instantiated. Assuming that recoverAccountservice.get() returns a promise (and if it doesn't, you should change it such that it does), you can more efficiently write:
var aimadResolver = function(recoverAccountService, $location, $state, $q) {
return recoverAccountService.get({... })
.then(function(result) {
// do nothing? Apparently you only want to $state.go on a rejection of the promise
})
.catch(function(err) {
$state.go("candidature.pre");
return $q.when()
})
}
More on the use of $q.when() versus deferred can be found here.

Related

Promise not resolved correctly when routing

So basically I am using resolve in $routeProvider in order to get my current user's information before I instantiate the controller.
This is how I do it:
userMan.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/userManagement', {
templateUrl: 'userManagement/userManagement.html',
controller: 'userManCtrl',
resolve: {
myUser:function($http){
console.log("I am about to make the call");
$http.get("/irods-cloud-backend/searchByMetadata/getCurrentUser").then( function( result ) {
console.log(result.data);
return result.data;
},
function( result ) {
alert("Unable to find any users! Please try again...");
})
}
}
});
}]);
My controller also looks something like this:
userMan.controller('userManCtrl', ['$scope', '$log', '$http', '$location', 'myUser',
function($scope, $log, $http, $location, myUser){
var currUser = myUser;
Basically my error is that currUser is undefined. This prevents me to check the user's permissions and such.
I have also used:
$scope.$on('$routeChangeSuccess',function(event, current){
alert("loaded!!");
});
To see when this promise is resolved, but I see the alert message as soon as I load into the tab.
I then tried to use a dummy return object simply like
return { someAttr: 'val'};
This functions just fine.. I know its not a promise but did that for sanity purposes..
Any clues for the error?
You don't return from the myUser method, so there's nothing to wait for but undefined. As #JoelCDoyle commented:
Your resolve method must return a promise. $http method returns a promise.
resolve: {
myUser:function($http){
// return here!
return $http.get("/irods-cloud-backend/searchByMetadata/getCurrentUser").then( function( result ) {
console.log(result.data);
return result.data;
}
After feedback from #JoelCDoyle and #baao, I used a different approach.
I simply created a service that handled the GET request and used that in my resolve.
More on this can be viewed here

$state are not populated in resolving promises in Angular routes

I came across this situation where I can see $stateParams gets populated in one place but not in another. I'm kind of newbie to angular-ui-router so any help will be appreciated.Thanks !
In the resolve block of the following state, I injected $stateParams as a dependency in the function for data 'campaign' and the $stateParams is populated.
.state('campaign', {
url: "/campaign/{campaignId:int}",
templateUrl: campaign_template,
controller: 'CampaignCtrl',
parent: 'org',
abstract: true,
resolve: {
campaign: function(CampaignService, $stateParams) {
console.log('$stateParams is populated here!', $stateParams)
return CampaignService.get($stateParams.campaignId)
.then(function(campaign) {
return campaign;
});
}
}
Inside the CampaignService function, however, I require $stateParams but it's
empty. I'm confused because I'm assuming since it's populated when I injected it in the
resolve block, it should be the same no matter where else I get it again.
.service('CampaignService', function($injector, $q) {
this.get = function() {
var $stateParams = $injector.get('$stateParams');
console.log('$stateParams is empty here!', $stateParams);
var deferred = $q.defer();
setTimeout(function() {
deferred.resolve({
name: 'campaignName'
});
}, 1000);
return deferred.promise;
}
})
I'm assuming since it's populated when I injected it in the resolve block, it should be the same no matter where else I get it again.
The $stateParams injected into the resolve block is the proposed future state. At that point in time the application is still using the old state. And will remain in the old state if any of the resolve promises are rejected.
Under the hood, the $state service creates a local version of $stateParams that it injects in the resolve function:
var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
var locals = { $stateParams: $stateParams };
// Resolve 'global' dependencies for the state, i.e. those not specific to a view.
// We're also including $stateParams in this; that way the parameters are restricted
// to the set that should be visible to the state, and are independent of when we update
// the global $state and $stateParams values.
dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
var promises = [dst.resolve.then(function (globals) {
dst.globals = globals;
})];
— https://github.com/angular-ui/ui-router/blob/legacy/src/state.js#L1427-L1437
The solution is to pass the proposed future $stateParams to the service.
Your service probably shouldn't care about state parameters. You are already passing in the campaignId value inside of your state definition so in order to consume that within the service you could modify it like this:
.service('CampaignService', function($injector, $q) {
this.get = function(campaignId) {
console.log('campaignId = ' + campaignId);
var deferred = $q.defer();
setTimeout(function() {
deferred.resolve({
name: 'campaignName'
});
}, 1000);
return deferred.promise;
}
})

while unit testing ui-router config, ui-router's resolve function returns promise during expectation even after the service promise was resolved

Below is the sample route configuration that I have for myApp using ui-router
angular.module('myApp', ['ui.router']);
angular.module('myApp').config(stateConfig);
stateConfig.$inject = ['$stateProvider','$urlRouterProvider'];
function stateConfig($stateProvider, $urlRouterProvider) {
$stateProvider.state('view1', {
url: '/view1/:id?',
templateUrl: 'app/view1/view1.html',
resolve:{
init: ['$stateParams', 'view1Service', function($stateParams, view1Service){
if($stateParams.id !== ''){
return view1Service.getIdData($stateParams.id)
.then(function(response){
return { data: response.data, responseStatus: response.status };
}, function(response){
return { data:{}, responseStatus: response.status };
});
}
else{
return { data:{}, responseStatus: 200 };
}
}]
},
controller: 'View1Controller as controllerOne'
})
//some other routes with similar configuration
$urlRouterProvider.otherwise('/view1/');
}
Here is the spec for the above code that I have for now. Since resolve function for view1 state is dependent on view1Service I have mocked view1Service and also made it to return a promise(if promise was not returned from mocked service then infinite digest() loop was occuring).
describe('ui router config', function() {
var $rootScope, $state, $injector, myServiceMock, state = 'view2', deferred, mockedService;
beforeEach(function() {
angular.mock.module('myApp');
angular.mock.module('ui.router');
angular.mock.module(function($provide){
$provide.factory('view1Service', function($q){
function getIdData(id){
deferred = $q.defer();
return deferred.promise;
}
return {getIdData: getIdData}
});
});
inject(function(_$rootScope_, _$state_, _$injector_, $templateCache, _$stateParams_, view1Service) {
$rootScope = _$rootScope_;
$state = _$state_;
$injector = _$injector_;
$stateParams = _$stateParams_;
$templateCache.put('app/view1/view1.html', '')
})
});
it('should respond to URL', function() {
expect($state.href(state, { id: 1 })).toEqual('#/view1/1');
});
it('should resolve data', function() {
$state.go(state, {id: '9999'});
deferred.resolve({
data: 'some data',
status: 666
});
$rootScope.$digest();
expect($state).toBe('checking');
expect($state.current.name).toBe(state+'q');
// Call invoke to inject dependencies and run function
expect($injector.invoke($state.current.resolve.init)).toBe('findAll+1');//this assertion fails with below error
});
});
I'm currently able to assert on the current state. I would like to test the resolve function's success and failure callback as well.
However I keep getting following error:
Expected Promise({ $$state: Object({ status: 0 }) }) to be 'findAll+1'.
Any idea why resolve block keeps returning Promise object as above. First of all it shouldn't be returning a promise since view1Service was resolved. And to my understanding even if resolve block invocation returns a promise doesn't expect statement wait till its resolved? I tried even using .then on invocation call, that didn't work either.
Any help is much appreciated.
You are transitioning to the state, which calls the resolve functions, and then you "resolve()" your deferred. All good here. But then you invoke the init function later, which returns a promise, which is an good. But you want to resolve the deferred after this, now that it's been setup.
Basically, I think you are calling "init" twice, once when you call "$state.go", and again explicitly afterwards.
You should be able to first do a $state.get('view1').resolve.init, to grab the init function that you want to test directly. Otherwise, calling "$state.go" will run it automatically.
Hope that helps!

Angular ui-router get asynchronous data with resolve

I want to display a form with data corresponding to the edited item. I use ui-router for routing. I defined a state:
myapp.config(function($stateProvider) {
$stateProvider.
.state('layout.propertyedit', {
url: "/properties/:propertyId",
views : {
"contentView#": {
templateUrl : 'partials/content2.html',
controller: 'PropertyController'
}
}
});
In PropertyController, I want to set $scope.property with data coming from the following call (Google Cloud Endpoints):
gapi.client.realestate.get(propertyId).execute(function(resp) {
console.log(resp);
});
I don't know if I can use resolve because the data are returned asynchronously. I tried
resolve: {
propertyData: function() {
return gapi.client.realestate.get(propertyId).execute(function(resp) {
console.log(resp);
});
}
}
First issue, the propertyId is undefined. How do you get the propertyId from the url: "/properties/:propertyId"?
Basically I want to set $scope.property in PropertyController to the resp object returned by the async call.
EDIT:
myapp.controller('PropertyController', function($scope, , $stateParams, $q) {
$scope.property = {};
$scope.create = function(property) {
}
$scope.update = function(property) {
}
function loadData() {
var deferred = $q.defer();
gapi.client.realestate.get({'id': '11'}).execute(function(resp) {
deferred.resolve(resp);
});
$scope.property = deferred.promise;
}
});
You need to read the docs for resolve. Resolve functions are injectable, and you can use $stateParams to get the correct value from your routes, like so:
resolve: {
propertyData: function($stateParams, $q) {
// The gapi.client.realestate object should really be wrapped in an
// injectable service for testability...
var deferred = $q.defer();
gapi.client.realestate.get($stateParams.propertyId).execute(function(r) {
deferred.resolve(r);
});
return deferred.promise;
}
}
Finally, the values for resolve functions are injectable in your controller once resolved:
myapp.controller('PropertyController', function($scope, propertyData) {
$scope.property = propertyData;
});
I think your controller function needs $stateParams parameter from which you can get your propertyId. Then you can use $q parameter and create promise to set $scope.property with something like this:
var deferred = $q.defer();
gapi.client.realestate.get(propertyId).execute(function(resp) {
deferred.resolve(resp);
});
$scope.property=deferred.promise;
Here is description of using promises for handling async calls.
Try this easy way to use resolve in proper way
State code:
.state('yourstate', {
url: '/demo/action/:id',
templateUrl: './view/demo.html',
resolve:{
actionData: function(actionData, $q, $stateParams, $http){
return actionData.actionDataJson($stateParams.id);
}
},
controller: "DemoController",
controllerAs : "DemoCtrl"
})
In the above code I am sending parameter data which I am sending in the url,For examples if i send like this /demo/action/5
this number 5 will go to actionData service that service retrieve some json data based on id.Finally that data will store into actionData You can use that in your controller directly by using that name
Following code return some JSON data based on id which iam passing at state level
(function retriveDemoJsonData(){
angular.module('yourModuleName').factory('actionData', function ($q, $http) {
var data={};
data.actionDataJson = function(id){
//The original business logic will apply based on URL Param ID
var defObj = $q.defer();
$http.get('demodata.json')
.then(function(res){
defObj.resolve(res.data[0]);
});
return defObj.promise;
}
return data;
});
})();
How about this:
function PropertyController($scope, $stateParams) {
gapi.client.realestate.get($stateParams.propertyId).execute(function(resp) {
$scope.property = resp;
});
}

Inject service in app.config

I want to inject a service into app.config, so that data can be retrieved before the controller is called. I tried it like this:
Service:
app.service('dbService', function() {
return {
getData: function($q, $http) {
var defer = $q.defer();
$http.get('db.php/score/getData').success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
};
});
Config:
app.config(function ($routeProvider, dbService) {
$routeProvider
.when('/',
{
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
data: dbService.getData(),
}
})
});
But I get this error:
Error: Unknown provider: dbService from EditorApp
How to correct setup and inject this service?
Set up your service as a custom AngularJS Provider
Despite what the Accepted answer says, you actually CAN do what you were intending to do, but you need to set it up as a configurable provider, so that it's available as a service during the configuration phase.. First, change your Service to a provider as shown below. The key difference here is that after setting the value of defer, you set the defer.promise property to the promise object returned by $http.get:
Provider Service: (provider: service recipe)
app.provider('dbService', function dbServiceProvider() {
//the provider recipe for services require you specify a $get function
this.$get= ['dbhost',function dbServiceFactory(dbhost){
// return the factory as a provider
// that is available during the configuration phase
return new DbService(dbhost);
}]
});
function DbService(dbhost){
var status;
this.setUrl = function(url){
dbhost = url;
}
this.getData = function($http) {
return $http.get(dbhost+'db.php/score/getData')
.success(function(data){
// handle any special stuff here, I would suggest the following:
status = 'ok';
status.data = data;
})
.error(function(message){
status = 'error';
status.message = message;
})
.then(function(){
// now we return an object with data or information about error
// for special handling inside your application configuration
return status;
})
}
}
Now, you have a configurable custom Provider, you just need to inject it. Key difference here being the missing "Provider on your injectable".
config:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
dbData: function(DbService, $http) {
/*
*dbServiceProvider returns a dbService instance to your app whenever
* needed, and this instance is setup internally with a promise,
* so you don't need to worry about $q and all that
*/
return DbService('http://dbhost.com').getData();
}
}
})
});
use resolved data in your appCtrl
app.controller('appCtrl',function(dbData, DbService){
$scope.dbData = dbData;
// You can also create and use another instance of the dbService here...
// to do whatever you programmed it to do, by adding functions inside the
// constructor DbService(), the following assumes you added
// a rmUser(userObj) function in the factory
$scope.removeDbUser = function(user){
DbService.rmUser(user);
}
})
Possible Alternatives
The following alternative is a similar approach, but allows definition to occur within the .config, encapsulating the service to within the specific module in the context of your app. Choose the method that right for you. Also see below for notes on a 3rd alternative and helpful links to help you get the hang of all these things
app.config(function($routeProvider, $provide) {
$provide.service('dbService',function(){})
//set up your service inside the module's config.
$routeProvider
.when('/', {
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
data:
}
})
});
A few helpful Resources
John Lindquist has an excellent 5 minute explanation and demonstration of this at egghead.io, and it's one of the free lessons! I basically modified his demonstration by making it $http specific in the context of this request
View the AngularJS Developer guide on Providers
There is also an excellent explanation about factory/service/provider at clevertech.biz.
The provider gives you a bit more configuration over the .service method, which makes it better as an application level provider, but you could also encapsulate this within the config object itself by injecting $provide into config like so:
Alex provided the correct reason for not being able to do what you're trying to do, so +1. But you are encountering this issue because you're not quite using resolves how they're designed.
resolve takes either the string of a service or a function returning a value to be injected. Since you're doing the latter, you need to pass in an actual function:
resolve: {
data: function (dbService) {
return dbService.getData();
}
}
When the framework goes to resolve data, it will inject the dbService into the function so you can freely use it. You don't need to inject into the config block at all to accomplish this.
Bon appetit!
Short answer: you can't. AngularJS won't allow you to inject services into the config because it can't be sure they have been loaded correctly.
See this question and answer:
AngularJS dependency injection of value inside of module.config
A module is a collection of configuration and run blocks which get
applied to the application during the bootstrap process. In its
simplest form the module consist of collection of two kinds of blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants
can be injected into configuration blocks. This is to prevent
accidental instantiation of services before they have been fully
configured.
I don't think you're supposed to be able to do this, but I have successfully injected a service into a config block. (AngularJS v1.0.7)
angular.module('dogmaService', [])
.factory('dogmaCacheBuster', [
function() {
return function(path) {
return path + '?_=' + Date.now();
};
}
]);
angular.module('touch', [
'dogmaForm',
'dogmaValidate',
'dogmaPresentation',
'dogmaController',
'dogmaService',
])
.config([
'$routeProvider',
'dogmaCacheBusterProvider',
function($routeProvider, cacheBuster) {
var bust = cacheBuster.$get[0]();
$routeProvider
.when('/', {
templateUrl: bust('touch/customer'),
controller: 'CustomerCtrl'
})
.when('/screen2', {
templateUrl: bust('touch/screen2'),
controller: 'Screen2Ctrl'
})
.otherwise({
redirectTo: bust('/')
});
}
]);
angular.module('dogmaController', [])
.controller('CustomerCtrl', [
'$scope',
'$http',
'$location',
'dogmaCacheBuster',
function($scope, $http, $location, cacheBuster) {
$scope.submit = function() {
$.ajax({
url: cacheBuster('/customers'), //server script to process data
type: 'POST',
//Ajax events
// Form data
data: formData,
//Options to tell JQuery not to process data or worry about content-type
cache: false,
contentType: false,
processData: false,
success: function() {
$location
.path('/screen2');
$scope.$$phase || $scope.$apply();
}
});
};
}
]);
You can use $inject service to inject a service in you config
app.config(function($provide){
$provide.decorator("$exceptionHandler", function($delegate, $injector){
return function(exception, cause){
var $rootScope = $injector.get("$rootScope");
$rootScope.addError({message:"Exception", reason:exception});
$delegate(exception, cause);
};
});
});
Source: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx
** Explicitly request services from other modules using angular.injector **
Just to elaborate on kim3er's answer, you can provide services, factories, etc without changing them to providers, as long as they are included in other modules...
However, I'm not sure if the *Provider (which is made internally by angular after it processes a service, or factory) will always be available (it may depend on what else loaded first), as angular lazily loads modules.
Note that if you want to re-inject the values that they should be treated as constants.
Here's a more explicit, and probably more reliable way to do it + a working plunker
var base = angular.module('myAppBaseModule', [])
base.factory('Foo', function() {
console.log("Foo");
var Foo = function(name) { this.name = name; };
Foo.prototype.hello = function() {
return "Hello from factory instance " + this.name;
}
return Foo;
})
base.service('serviceFoo', function() {
this.hello = function() {
return "Service says hello";
}
return this;
});
var app = angular.module('appModule', []);
app.config(function($provide) {
var base = angular.injector(['myAppBaseModule']);
$provide.constant('Foo', base.get('Foo'));
$provide.constant('serviceFoo', base.get('serviceFoo'));
});
app.controller('appCtrl', function($scope, Foo, serviceFoo) {
$scope.appHello = (new Foo("app")).hello();
$scope.serviceHello = serviceFoo.hello();
});
Using $injector to call service methods in config
I had a similar issue and resolved it by using the $injector service as shown above. I tried injecting the service directly but ended up with a circular dependency on $http. The service displays a modal with the error and I am using ui-bootstrap modal which also has a dependency on $https.
$httpProvider.interceptors.push(function($injector) {
return {
"responseError": function(response) {
console.log("Error Response status: " + response.status);
if (response.status === 0) {
var myService= $injector.get("myService");
myService.showError("An unexpected error occurred. Please refresh the page.")
}
}
}
A solution very easy to do it
Note : it's only for an asynchrone call, because service isn't initialized on config execution.
You can use run() method. Example :
Your service is called "MyService"
You want to use it for an asynchrone execution on a provider "MyProvider"
Your code :
(function () { //To isolate code TO NEVER HAVE A GLOBAL VARIABLE!
//Store your service into an internal variable
//It's an internal variable because you have wrapped this code with a (function () { --- })();
var theServiceToInject = null;
//Declare your application
var myApp = angular.module("MyApplication", []);
//Set configuration
myApp.config(['MyProvider', function (MyProvider) {
MyProvider.callMyMethod(function () {
theServiceToInject.methodOnService();
});
}]);
//When application is initialized inject your service
myApp.run(['MyService', function (MyService) {
theServiceToInject = MyService;
}]);
});
Well, I struggled a little with this one, but I actually did it.
I don't know if the answers are outdated because of some change in angular, but you can do it this way:
This is your service:
.factory('beerRetrievalService', function ($http, $q, $log) {
return {
getRandomBeer: function() {
var deferred = $q.defer();
var beer = {};
$http.post('beer-detail', {})
.then(function(response) {
beer.beerDetail = response.data;
},
function(err) {
$log.error('Error getting random beer', err);
deferred.reject({});
});
return deferred.promise;
}
};
});
And this is the config
.when('/beer-detail', {
templateUrl : '/beer-detail',
controller : 'productDetailController',
resolve: {
beer: function(beerRetrievalService) {
return beerRetrievalService.getRandomBeer();
}
}
})
Easiest way:
$injector = angular.element(document.body).injector()
Then use that to run invoke() or get()

Resources