Before a state is loaded, I wish to resolve a factory using the data from another factory.
My situation:
"taskVariables": function (getTask, getTaskVariables, $stateParams) {
getTask.get({'task': $stateParams.taskId}).success(function(data) {
console.log("I'm here");
return getTaskVariables.get({'processInstanceId': data.processInstanceId});
});
return false;
console.log("I have failed you master")
},
Factory:
.factory('getTask', function ($resource) {
return $resource('api/task/get/:task', {'user': '#user', 'task': '#task'}, {
'get': {
method: 'GET',
transformResponse: function (data) {
data = angular.fromJson(data);
return data;
}
}
})
})
The thing is that I think that one factory is loading faster than the other, thus the data will be undefined on page load, but that is just me speculating.
Is there a way to achieve this with maybe a promise or some kind?
Thanks in advance,
Because I return a $resource I cannot declare a success- nor a failure function, so you need to change it by giving it a $promise, followed by a then:
"taskVariables": function (getTask, getTaskVariables, $stateParams) {
return getTask.get({'task': $stateParams.taskId}).$promise.then(function(data) {
return getTaskVariables.get({'processInstanceId': data.processInstanceId});
});
}
reffering to this question/answer
You're forgetting to return the initial getTask promise.
"taskVariables": function (getTask, getTaskVariables, $stateParams) {
return getTask.get({'task': $stateParams.taskId}).success(function(data) {
console.log("I'm here");
return getTaskVariables.get({'processInstanceId': data.processInstanceId});
});
},
Related
Hi I have a $stateProvider like below.
$stateProvider .state('team.details', {
url: '/:teamId',
views: {
'#': {
controller: 'teamDetailsCtrl',
templateUrl: 'src/modules/team/details/teamDetails.html',
resolve: {
user: function ($stateParams, TeamResource) {
return TeamResource.get({ teamid: $stateParams.teamId }).$promise;
}
The TeamResource.get is calling a rest api and everything is working fine if the data is sent from the rest api(teamid present in DB). But if the data is not present then I have to route it to a "home". How can i accomplish it ?
You resolve could handle that thing, if data is there then return that data using chain promise or else redirect the to different state.
Code
resolve: {
user: function($stateParams, TeamResource, $state) {
return TeamResource.get({
teamid: $stateParams.teamId
}).$promise.then(function(data) {
if (!data.length > 0) { //here might be you need to use something else instead of data
$state.go('home')
return
} else {
return data;
}
});
}
What kind of response does your API return? My suggestion is that you set up a global http interceptor for your app which could listen to 404 responses. And in that case redirect users to your home state.
By putting the logic in an interceptor instead of the resolve, you can avoid redundant code if you have several states which may benefit from this kind of check.
This is an example interceptor, taken from this SO post.
Note that this may only be wanted for a specific endpoint (eg the teams). You can check which url got called by accessing the url property of response. Like so: response.url
var interceptor = ['$q', '$state', function ($q, $state) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
var url = response.url;
if (status == 404) {
$state.go('home')
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
Angular $http.get() returns a promise itself. if case of any error, you just need to reject the same with $q service.
In resolve:
resolve : {
user : function ($stateParams, TeamResource) {
return TeamResource.get({
teamid : $stateParams.teamId
});
}
}
Under TeamResource.get:
return {
get : function (teamObj) {
$http.get("/url", teamObj).then(function (resp) {
if (resp.length) {
return resp;
} else {
return $q.reject(resp);
}
}, function (err) {
return $q.reject(resp);
});
}
}
In case of return in promise() with $q.reject(), the promise chain sends it to error callback to the next chain and if you just return with something, it sends it to success function in next promise chain.
I have multiple controllers on a page that use the same service, for the sake of example we will call the service USER.
The first time the USER.getUser() is called it does an $http request to GET data on the user. After the call is completed it stores the data in USER.data. If another call is made to USER.getUser() it checks if there is data in USER.data and if there is data it returns that instead of making the call.
My problem is that the calls to USER.getUser() happen so quickly that USER.data does not have any data so it fires the $http call again.
Here is what I have for the user factory right now:
.factory("user", function($http, $q){
return {
getUser: function(){
var that = this;
var deferred = $q.defer();
if(that.data){
deferred.resolve(that.data);
} else {
$http.get("/my/url")
.success(function(res){
that.data = res;
deferred.resolve(that.data);
});
}
return deferred.promise;
}
}
});
I hope my question makes sense. Any help would be much appreciated.
Does this work for you?
.factory("user", function($http, $q) {
var userPromise;
return {
getUser: function () {
if (userPromise) {
return userPromise;
}
userPromise = $http
.get("/my/url")
.then(function(res) {
return res.data;
});
return userPromise;
}
}
})
$http already returns the promise for you, even more, in 2 useful types: success & error. So basically, the option that #kfis offered does NOT catch errors and not flexible.
You could write something simple:
.factory('user', function($http) {
return {
getUser: function() {
$http.get('/my/url/')
.success(function(data) {
return data;
})
.error(function(err) {
// error handler
})
}
}
})
Is it true that I can't use resolved values from the parent to state and inject into child state with tempalteProvider? I read some article, people said it can't be done. Resolved values won't be available until it reaches to controller. I need some values to determine which template I need to load.
What you're saying is indeed true. But you can circumvent it by using a service. Let's say you have a resolve which fetches it results from a service:
'resolve': {
'myData': ['MyService', function (MyService) {
return MyService.getData();
}]
}
In that service you can cache/store your results:
angular.module('app').factory('MyService', ['$http', function ($http) {
var data;
return {
getData: function () {
// No data yet, fetch it
if (!data) {
return $http({method: 'GET', url: 'a.json'}).then(
function (result) {
// Store for future use
data = result.data;
// Return data
return data;
});
// Data already present
} else {
// Return data
return data;
}
}
}
}]);
Now in your childstate, you can use the service which has the data ready in your templateProvider:
'templateProvider': ['$http', 'MyService', function ($http, MyService) {
var data = MyService.getData();
return $http.get(data.type + '.html').then(
function (response) {
return response.data;
}
);
}]
Here's a quick 'n dirty example on Plunker: http://plnkr.co/edit/hUGhmGSowV4XEdXfdS0E?p=preview
I have this service:
angular.module('autotestApp').factory('GroupService', ['$http', function ($http) {
var groups = [];
return{
list: function () {
return groups;
},
retrieve: function () {
$http({
method: "get",
url: "/enterprises/_groups"
}).success(function (response) {
groups = response;
}).error(function () {
console.log("failed")
});
}
}
}]);
and this is my controller:
angular.module('autotestApp').controller('GroupsController', function($scope, $http, GroupService) {
GroupService.retrieve();
$scope.items = GroupService.list();
});
So, in my controller, I am first getting the result from the API so that the variable groups(in the service) gets assigned, then I am using the list() method to assign the values to $scope.items.
I am sure that API is returning results. But the
groups = response
part is not working correctly. When I changed it to
groups.push(response)
it is working but the result is a list inside a list, which I dont't want: [[ Object, Object, Object ]]
I want this: [ Object, Object, Object ]
How to fix this?
The reason
groups = response
is not working is because you're sending an async request that will replace the groups reference after you've already retrieved the old reference via the list function. The reason it works with the push modification is because Angular creates a watch that notices that the collection has changed and updates your view. However, your code is now working, but you don't understand why it works, and it's prone to breaking unexpectedly.
When dealing with asynchronous code, the best way to deal with it is to write your own code to take that into account. Here's a version that does the whole thing in an async fashion:
angular.module('autotestApp').factory('GroupService', ['$http', function ($http) {
var groupsResult;
return{
retrieve: function () {
if (groupsResult) { return groupsResult; }
return groupsResult = $http({
method: "get",
url: "/enterprises/_groups"
}).then(function (response) {
return response.data;
}, function () {
console.log("failed")
});
}
}
}]);
angular.module('autotestApp').controller('GroupsController',
['$scope', 'GroupService', function($scope, GroupService) {
GroupService.retrieve().then(function (groups) {
$scope.items = groups;
});
}]);
One of the fixes you could use is:
for (var i = 0; i < response.length; i++) {
groups.push(response[i]);
});
That way you would have [ Object, Object, Object ]
EDIT:
One thing you could try is the following, change your retrieve function to return your promise:
return{
list: function () {
return groups;
},
retrieve: function () {
var promise = $http({
method: "get",
url: "/enterprises/_groups"
}).success(function (response) {
groups = response;
}).error(function () {
console.log("failed")
});
return promise;
}
}
and then in your controller:
angular.module('autotestApp').controller('GroupsController', function($scope, $http, GroupService) {
GroupService.retrieve().finally(function () {
$scope.items = GroupService.list();
});
});
I think your groups = response is working, but when you do $scope.items = GroupService.list() the request isn't finished yet.
do groups.concat(response)
this will flatten the items & add it to parent list rather than appending a single element.
I have coded the following service:
angular.module('App')
.factory('TestService', ['$http', '$q', '$resource', function (
$http, $q, $resource) {
var TestResource = $resource('/api/Tests', {}, {
saveData: { method: 'PUT' },
deleteData: { method: "DELETE", params: { TestId: 0 } }
});
var factory = {
query: function (selectedSubject) {
var deferred = $q.defer();
TestResource.query({ subjectId: selectedSubject },
function (resp) {
deferred.resolve(resp);
}
);
return deferred.promise;
//return Test.query({ subjectId: selectedSubject });
}
}
return factory;
}]);
In my controller I am calling it this way:
TestService.query($scope.selectedSubject)
.then(function (result) {
$scope.gridData = result;
}, function (result) {
alert("Error: No data returned");
});
Is there a way that I could cut out a few lines of code by not having $q in my service. Is there a way that I could return a promise from the $resource? I have tried the commented out code but that gives me an error saying there is no ".then" method.
$resource can't return a promise in the current stable version of Angular (1.0.8), but it looks like it'll come in version 1.2. It was added in v1.1.3 (v 1.1 is the unstable branch):
$resource: expose promise based api via $then and $resolved (dba6bc73)