Let's say I have the following...
myService = Restangular.all('things');
myService.getList().then(
// success
function(things) {
$scope.things = things;
},
// failure
function(things) {
// do whatever, stuff failed
}
)
Now I have $scope.things which is a collection of things from the api, all well and good.
I want to post a new thing, and return the promise so I can deal with the pass/fail elsewhere
return $scope.things.post(newThing) // A promise...
However, doing things this way DOESN'T automatically add my new thing to the $scope.things collection. Why not? I've seen questions that link to the enhanced promises section of restangular docs and mention the "push" method, but that doesn't help me because $scope.things has no "push" method.
What's going on here? Where am I getting confused.
As mentionned in Restangular docs:
Because Restangular returns promises, we can then call methods on the returned data on promises so that we can run a function after the promise has completed. For instance, after we update a collection, we can then refresh the collection on our scope:
messages.post(newMessage).then(function(newMsg) {
$scope.messages = messages.getList();
}, function error(reason) {
// An error has occurred
});
Related
I have a method in my angular 1.5 controller, as shown below but I wanted to refactor the ajax call into the factory itself but I'm having problems with promises.. I'm trying to get to a point where in my controller I can just call the method like I've shown below. Is this possible? I'm trying to avoid having the ...success(function(...) in the controller code.
Any help much appreciated.
Trying to move to
vm.member = someFactory.getMember(vm.id);
Existing working controller code
vm.myMethod = myMethod;
...
function myMethod() {
someFactory.getMember(vm.id).success(function(response) {
vm.member = response;
});
}
When I move the getMethod line into the factory the response is populated obviously but as soon as I come back to the controller, even with the return value from the factory being the response the result is undefined. I know this is because of promises but is there a design pattern I'm missing or a clean way of doing this. Using my currently approach my controller is littered with .success(function()...)
Many thanks!
The procedure is called promise unwrapping.
Besides the fact that success is deprecated and should be replaced with then,
someFactory.getMember(vm.id).then(function(response) {
var data = res.data;
...
});
it is totally ok to have this in controller.
The alternative to this pattern is to return self-filling object (something that ngResource $resource does):
function getMember(...) {
var data = {};
$http(...).then(function (response) {
// considering that response data is JSON object,
// it can replace existing data object
angular.copy(data, response.data);
});
return data;
}
In this case controller can get a reference to the object instantly, and the bindings of object properties {{ vm.member.someProperty }} will be updated in view on response.
The pattern is limited to objects (and arrays), scalar values should be wrapped with objects.
Introduction:
A third-party navigation directive for an application sidebar, recommends loading data during the configuration phase. The menu items are dynamic, and are resolved on the server based on user credentials.
Problem Statment:
While the data loads fine, the then() clause defined in the code below is never executed, and as a result, the menus are not populated.
Question:
Can anyone suggest a way of resolving the promise in the configuration phase.
What responses are helpful
An approach that resolves the above questions without radically altering the code, or
An approach which radically changes the code, with an explanation, e.g. more elegant way, far shorter way, current code is an anti-pattern, or something I did not think of.
What responses are not helpful
Use another component library, or library poorly designed (this is what I'm using, I'm looking for suggestions how to get it to work)
or why are you doing x, y or z (I will gladly listen to suggestions)
Update: Added 'what has not worked' at the end.
Thank you,
PS. I doubt it is relevant, but just in-case, the libraries in use are:
Data: js-data
Navigation: eeh-navigation
The Code
Service:
.service('init', function initFactory(DS) {
return DS.defineResource('init', {
name: 'init',
idAttribute: 'id',
endpoint: '/init',
basePath: 'http://localhost:63342/api'
}).findAll({idAttribute: 'id'});
})
Provider
.provider("initProvider", ['DSProvider', function (DSProvider) {
this.$get = ["init", function initFactory(init) {
return new init(init);
}]
}])
Config
.config(["initProvider", 'navProvider', function(initProvider, navProvider) {
initProvider.$get().then(function (init) {
angular.forEach(init, function (value, key) {
navProvider.sidebarMenuItem(value.name, {
text: value.text,
iconClass: value.iconClass,
href: value.url
});
})
})
}])
What has not worked (thus far)
#Bricktop suggested using a promise to (i think) the provider, this is the change, but ;( unfortunately it does not work, the .then is never reached.
Provider:
var promise = $q(function(resolve, reject) {
setTimeout(function() {
if (new init(init)) {
resolve(init);
} else {
reject('failed');
}
}, 1000);
});
return promise;
I am not overly familiar with the use of providers, as they aren't used all that often, but I think I have an Idea that resolves your problem.
The .then you are using requires a promise to work, it is called whenever the previous promise is resolved and will execute a function on success or failure. I don't think however that your provider returns a promise but instead just returns a service.
To return a promise you should be able to use $q documented here. With a correct promise setup your then should be executed. I hope this helps to fix your problem, I will try to improve my answer if it doesn't.
Here is a snippet showing what I mean:
this.$get = ["init", function initFactory(init) {
var deferred = $q.defer();
deferred.resolve(new init(init));
return deferred.promise;
}]
In my meanjs app created using meanjs generator and auto generated CRUD-module, for income below, I want to customize the promise returned by the get method so that I can call methods and do transformations on the retrieved data. So i attempt to change it in my controller:
// Find existing Income
$scope.findOne = function() {
Incomes.get({
incomeId: $stateParams.incomeId
}).then(function(income){
$scope.income = income;
$scope.incomeDollar= income*$scope.dollarRate;
});
This only gives me the error : undefined is not a function, and pointing on then.
What am i doing wrong here? How can I do transformations on the data retrieved from the get method above?
The pattern i am trying to use is in the end of the article here. I want to switch from the short version to the long so that i can do more stuff in my callback.
// LONG:
myModule.controller('HelloCtrl', function($scope, HelloWorld) {
HelloWorld.getMessages().then(function(messages) {
$scope.messages = messages;
});
});
To simplify this, the writer of the article place the promise returned by ‘getMessages’ on the scope:
// SHORTER:
myModule.controller('HelloCtrl', function($scope, HelloWorld) {
$scope.messages = HelloWorld.getMessages();
});
The promise object can be accessed using $promise. From the doc
The Resource instances and collection have these additional
properties:
$promise: the promise of the original server interaction that created
this instance or collection.
On success, the promise is resolved with the same resource instance or
collection object, updated with data from server...
So, your code should look like:
// Find existing Income
$scope.findOne = function() {
Incomes.get({
incomeId: $stateParams.incomeId
}).$promise.then(function(income){
$scope.income = income;
$scope.incomeDollar= income*$scope.dollarRate;
});
I'm curious if there are any alternatives to fetching meta data from breeze when using Angular. Since $q and Q promises don't play nice with each other I decided to only use Q.js promises and not $q. In my controller I'm checking if the metaDataStore is empty:
if (mainDataService.manager.metadataStore.isEmpty())
{
$log.info('metadataStore is empty') ;
mainDataService.getMetaData()
.then(function ()
{
EnableCreateButton();
});
}
The basic idea if I don't have meta data is to go fetch it and then enable the buttons that will allow me to create a new record.
To accomplish this I define a Q Promise in my data service then after breeze resolves the promise I resolve my promise.
function getMetaData()
{
var myMetaDataPromise = Q.defer();
Q.delay(0).then(function () {
manager.metadataStore.fetchMetadata(serviceName).then(function () {
myMetaDataPromise.resolve();
});
});
return myMetaDataPromise.promise;
}
Any suggestions to make this cleaner would be great. One thought I had was to define the promise in the controller and have the data service just return the breeze promise. I just thought that would clutter the controller up and not separate out the concerns.
Take a look at the new to$q extension in breeze labs on github which have been using pretty extensively to good effect.
We haven't documented it yet but the comments explain how to "install" it and use it.
Your mainDataService.getMetaData() could then be:
return manager.metadataStore.fetchMetadata(serviceName).to$q();
and subsequently you do as you intended:
mainDataService.getMetaData()
.then(function() { EnableCreateButton(); });
You are now in the land of $q. Angular will $apply your EnableCreateButton automatically.
I'm writing a service that will retrieve data asynchronously ($http or $resource). I can hide the fact that it is asynchronous by returning an array that will initially be empty, but that will eventually get populated:
.factory('NewsfeedService1', ['$http', function($http) {
var posts = [];
var server_queried = false;
return {
posts: function() {
if(!server_queried) {
$http.get('json1.txt').success(
function(data) {
server_queried = true;
angular.copy(data, posts);
});
}
return posts;
}
};
}])
.controller('Ctrl1', ['$scope','NewsfeedService1',
function($scope, NewsfeedService1) {
$scope.posts = NewsfeedService1.posts();
}])
Or I can expose the asynchronicity by returning a promise:
.factory('NewsfeedService2', ['$http', function($http) {
var posts = [];
var server_queried = false;
var promise;
return {
posts_async: function() {
if(!promise || !server_queried) {
promise = $http.get('json2.txt').then(
function(response) {
server_queried = true;
posts = response.data;
return posts;
});
}
return promise;
}
};
}])
.controller('Ctrl2', ['$scope','NewsfeedService2',
function($scope, NewsfeedService2) {
NewsfeedService2.posts_async().then(
function(posts) {
$scope.posts = posts;
});
// or take advantage of the fact that $q promises are
// recognized by Angular's templating engine:
// (note that Peter and Pawel's AngularJS book recommends against this, p. 100)
$scope.posts2 = NewsfeedService2.posts_async();
}]);
(Plunker - if someone wants to play around with the above two implementations.)
One potential advantage of exposing the asychronicity would be that I can deal with errors in the controller by adding an error handler to the then() method. However, I'll likely be catching and dealing with $http errors in an application-wide interceptor.
So, when should a service's asynchronicity be exposed?
My guess is that you'll find people on both sides of this fence. Personally, I feel that you should always expose the asynchronicity of a library or function (or more correctly: I feel that you should never hide the asynchronicity of a library or function). The main reason is transparency; for example, will this work?
app.controller('MyController', function(NewsfeedService) {
$scope.posts = NewsfeedService.posts();
doSomethingWithPosts($scope.posts); // <-- will this work?
});
If you're using the first method (e.g. $resource), it won't, even though $scope.posts is technically an array. If doSomethingWithPosts has its own asynchronous operations, you could end up with a race condition. Instead, you have to use asynchronous code anyway:
app.controller('MyController', function(NewsfeedService) {
$scope.posts = NewsfeedService.posts(function() {
doSomethingWithPosts($scope.posts);
});
});
(Of course, you can make the callback accept the posts as an argument, but I still think it's confusing and non-standard.)
Luckily, we have promises, and the very purpose of a promise is to represent the future value of an operation. Furthermore, since promises created with Angular's $q libraries can be bound to views, there's nothing wrong with this:
app.controller('MyController', function(NewsfeedService) {
$scope.posts = NewsfeedService.posts();
// $scope.posts is a promise, but when it resolves
// the AngularJS view will work as intended.
});
[Update: you can no longer bind promises directly to the view; you must wait for the promise to be resolved and assign a scope property manually.]
As an aside, Restangular, a popular alternative to $resource, uses promises, and AngularJS' own $resource will be supporting them in 1.2 (they may already support them in the latest 1.1.x's).
I would always go with async option since i don't like hiding the async nature of the underlying framework.
The sync version may look more clean while consuming it, but it inadvertently leads to bug where the developer does not realize that the call is async in nature and tries to access data after making a call.
SO is filled with questions where people make this mistake with $resource considering it sync in nature, and expecting a response. $resource also takes similar approach to option 1, where results are filled after the call is complete, but still $resource exposes a success and failure function.
AngularJS tries to hide the complexities of async calls if promises are returned, so binding directly to a promise feels like one is doing a sync call.
I say no, because it makes it harder to work with multiple services built this way. With promises, you can use $q.all() to make multiple request and respond when all of them complete, or you can chain operations together by passing the promise around.
There would be no intuitive way to do this for the synchronous style service.