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.
Related
I'm using Typescript 2.1(developer version) to transpile async/await to ES5.
I've noticed that after I change any property which is bound to view in my async function the view isn't updated with current value, so each time I have to call $scope.$apply() at the end of function.
Example async code:
async testAsync() {
await this.$timeout(2000);
this.text = "Changed";
//$scope.$apply(); <-- would like to omit this
}
And new text value isn't shown in view after this.
Is there any workaround so I don't have to manually call $scope.$apply() every time?
The answers here are correct in that AngularJS does not know about the method so you need to 'tell' Angular about any values that have been updated.
Personally I'd use $q for asynchronous behaviour instead of using await as its "The Angular way".
You can wrap non Angular methods with $q quite easily i.e. [Note this is how I wrap all Google Maps functions as they all follow this pattern of passing in a callback to be notified of completion]
function doAThing()
{
var defer = $q.defer();
// Note that this method takes a `parameter` and a callback function
someMethod(parameter, (someValue) => {
$q.resolve(someValue)
});
return defer.promise;
}
You can then use it like so
this.doAThing().then(someValue => {
this.memberValue = someValue;
});
However if you do wish to continue with await there is a better way than using $apply, in this case, and that it to use $digest. Like so
async testAsync() {
await this.$timeout(2000);
this.text = "Changed";
$scope.$digest(); <-- This is now much faster :)
}
$scope.$digest is better in this case because $scope.$apply will perform dirty checking (Angulars method for change detection) for all bound values on all scopes, this can be expensive performance wise - especially if you have many bindings. $scope.$digest will, however, only perform checking on bound values within the current $scope making it much more performant.
This can be conveniently done with angular-async-await extension:
class SomeController {
constructor($async) {
this.testAsync = $async(this.testAsync.bind(this));
}
async testAsync() { ... }
}
As it can be seen, all it does is wrapping promise-returning function with a wrapper that calls $rootScope.$apply() afterwards.
There is no reliable way to trigger digest automatically on async function, doing this would result in hacking both the framework and Promise implementation. There is no way to do this for native async function (TypeScript es2017 target), because it relies on internal promise implementation and not Promise global. More importantly, this way would be unacceptable because this is not a behaviour that is expected by default. A developer should have full control over it and assign this behaviour explicitly.
Given that testAsync is being called multiple times, and the only place where it is called is testsAsync, automatic digest in testAsync end would result in digest spam. While a proper way would be to trigger a digest once, after testsAsync.
In this case $async would be applied only to testsAsync and not to testAsync itself:
class SomeController {
constructor($async) {
this.testsAsync = $async(this.testsAsync.bind(this));
}
private async testAsync() { ... }
async testsAsync() {
await Promise.all([this.testAsync(1), this.testAsync(2), ...]);
...
}
}
I have examined the code of angular-async-await and It seems like they are using $rootScope.$apply() to digest the expression after the async promise is resolved.
This is not a good method. You can use AngularJS original $q and with a little trick, you can achieve the best performance.
First, create a function ( e.g., factory, method)
// inject $q ...
const resolver=(asyncFunc)=>{
const deferred = $q.defer();
asyncFunc()
.then(deferred.resolve)
.catch(deferred.reject);
return deferred.promise;
}
Now, you can use it in your for instance services.
getUserInfo=()=>{
return resolver(async()=>{
const userInfo=await fetch(...);
const userAddress= await fetch (...);
return {userInfo,userAddress};
});
};
This is as efficient as using AngularJS $q and with minimal code.
As #basarat said the native ES6 Promise doesn't know about the digest cycle.
What you could do is let Typescript use $q service promise instead of the native ES6 promise.
That way you won't need to invoke $scope.$apply()
angular.module('myApp')
.run(['$window', '$q', ($window, $q) => {
$window.Promise = $q;
}]);
I've set up a fiddle showcasing the desired behavior. It can be seen here: Promises with AngularJS.
Please note that it's using a bunch of Promises which resolve after 1000ms, an async function, and a Promise.race and it still only requires 4 digest cycles (open the console).
I'll reiterate what the desired behavior was:
to allow the usage of async functions just like in native JavaScript; this means no other 3rd party libraries, like $async
to automatically trigger the minimum number of digest cycles
How was this achieved?
In ES6 we've received an awesome featured called Proxy. This object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
This means that we can wrap the Promise into a Proxy which, when the promise gets resolved or rejected, triggers a digest cycle, only if needed. Since we need a way to trigger the digest cycle, this change is added at AngularJS run time.
function($rootScope) {
function triggerDigestIfNeeded() {
// $applyAsync acts as a debounced funciton which is exactly what we need in this case
// in order to get the minimum number of digest cycles fired.
$rootScope.$applyAsync();
};
// This principle can be used with other native JS "features" when we want to integrate
// then with AngularJS; for example, fetch.
Promise = new Proxy(Promise, {
// We are interested only in the constructor function
construct(target, argumentsList) {
return (() => {
const promise = new target(...argumentsList);
// The first thing a promise does when it gets resolved or rejected,
// is to trigger a digest cycle if needed
promise.then((value) => {
triggerDigestIfNeeded();
return value;
}, (reason) => {
triggerDigestIfNeeded();
return reason;
});
return promise;
})();
}
});
}
Since async functions rely on Promises to work, the desired behavior was achieved with just a few lines of code. As an additional feature, one can use native Promises into AngularJS!
Later edit: It's not necessary to use Proxy as this behavior can be replicated with plain JS. Here it is:
Promise = ((Promise) => {
const NewPromise = function(fn) {
const promise = new Promise(fn);
promise.then((value) => {
triggerDigestIfNeeded();
return value;
}, (reason) => {
triggerDigestIfNeeded();
return reason;
});
return promise;
};
// Clone the prototype
NewPromise.prototype = Promise.prototype;
// Clone all writable instance properties
for (const propertyName of Object.getOwnPropertyNames(Promise)) {
const propertyDescription = Object.getOwnPropertyDescriptor(Promise, propertyName);
if (propertyDescription.writable) {
NewPromise[propertyName] = Promise[propertyName];
}
}
return NewPromise;
})(Promise) as any;
In case you're upgrading from AngularJS to Angular using ngUpgrade (see https://angular.io/guide/upgrade#upgrading-with-ngupgrade):
As Zone.js patches native Promises you can start rewriting all $q based AngularJS promises to native Promises, because Angular triggers a $digest automatically when the microtask queue is empty (e.g. when a Promise is resolved).
Even if you don't plan to upgrade to Angular, you can still do the same, by including Zone.js in your project and setting up a similar hook like ngUpgrade does.
Is there any workaround so I don't have to manually call $scope.$apply() every time?
This is because TypeScript uses the browser native Promise implementation and that is not what Angular 1.x knows about. To do its dirty checking all async functions that it does not control must trigger a digest cycle.
As #basarat said the native ES6 Promise doesn't know about the digest cycle. You should to promise
async testAsync() {
await this.$timeout(2000).toPromise()
.then(response => this.text = "Changed");
}
As it already has been described, angular does not know when the native Promise is finished. All async functions create a new Promise.
The possible solution can be this:
window.Promise = $q;
This way TypeScript/Babel will use angular promises instead.
Is it safe? Honestly I'm not sure - still testing this solution.
I would write a converter function, in some generic factory (didnt tested this code, but should be work)
function toNgPromise(promise)
{
var defer = $q.defer();
promise.then((data) => {
$q.resolve(data);
}).catch(response)=> {
$q.reject(response);
});
return defer.promise;
}
This is just to get you started, though I assume conversion in the end will not be as simple as this...
Watching a lot of Egghead.io videos, I noticed that a common pattern is to return a custom promise and resolve it in the callbacks.
.factory('myFact', function($q, $http) {
return {
getData: function() {
var deferred = $q.defer();
$http.get('/path/to/api')
.success(function(data) {
deferred.resolve(data);
});
return deferred.promise;
}
};
});
I would normally write this as:
.factory('myFact', function($http) {
return {
getData: function() {
return $http.get('/path/to/api')
.then(function(res) {
return res.data;
});
}
};
});
Is there any advantage to returning a $q.defer() promise rather than an $http promise? The approaches look identical to me.
No, no advantages, it's the same, In your first code snipped you created a $q.defer() instance then you invoked its resolve() method to create a resolved promise.
That is the process you will need to know and pass throw in angularJs when working with asynchronously functions and future objects that will have different values or new data at some future moment which you will need to know when it happens because interested parties in your app may need to get access to the result of the deferred task when it completes.
Now when working with $http you don't have to do any of that because it will already return a resolved promise that you can directly invoke it's then() method unless you have a different way to do things and you need to implement a different approach.
But not all angularJs services are going to do the work for you, get a look to $resource for example, which wraps $http for use in RESTful web API scenarios. $resource will not return a resolved promise, a promise yes, you are getting one but you'll need to do the last step of resolving it (check this stack question or this and maybe this article about Amber Kaplan's own experience working with Rest).
So the way how you are doing it is good, that is how I'm doing it too when working with $http but the first snippet code is the one that we will be all searching for when we will need to do things differently with $http or forcing other services to 'work with' or 'work like' AJAX.
I want to use the library MIDI.js in my AngularJS app. To initialise it, you call MIDI.loadPlugin which takes a callback that fires when some files are loaded.
I want to make a controller in my app aware of when the files are loaded.
My current approach is a dedicated service which calls MIDI.loadPlugin when it is created, and sends a $rootScope.$broadcast when the callback is fired.
Is this the best approach, particularly in regards to testability?
I would use a promise in your service to get access to the async resource. Then for all functions in the service that need it they just call the get method that returns a promise.
I'll try to post some example code later when on the computer.
Here's the solution I came up with based on Gordon's suggestion.
.factory('midiLoader', ['MIDI', '$q', function (MIDI, $q) {
var service = {};
service.loadFiles = function() {
return $q(function(resolve, reject) {
MIDI.loadPlugin({
soundfontUrl: "bower_components/midi/soundfont/",
instrument: "acoustic_grand_piano",
callback: function () {
resolve(null);
}
});
});
}
return service;
});
I was reading this article: http://eviltrout.com/2013/06/15/ember-vs-angular.html
And it said,
Due to it’s lack of conventions, I wonder how many Angular projects
rely on bad practices such as AJAX calls directly within controllers?
Due to dependency injection, are developers injecting router
parameters into directives? Are novice AngularJS developers going to
structure their code in a way that an experienced AngularJS developer
believes is idiomatic?
I am actually making $http calls from my Angular.js controller. Why is it a bad practice? What is the best practice for making $http calls then? and why?
EDIT: This answer was primarily focus on version 1.0.X. To prevent confusion it's being changed to reflect the best answer for ALL current versions of Angular as of today, 2013-12-05.
The idea is to create a service that returns a promise to the returned data, then call that in your controller and handle the promise there to populate your $scope property.
The Service
module.factory('myService', function($http) {
return {
getFoos: function() {
//return the promise directly.
return $http.get('/foos')
.then(function(result) {
//resolve the promise as the data
return result.data;
});
}
}
});
The Controller:
Handle the promise's then() method and get the data out of it. Set the $scope property, and do whatever else you might need to do.
module.controller('MyCtrl', function($scope, myService) {
myService.getFoos().then(function(foos) {
$scope.foos = foos;
});
});
In-View Promise Resolution (1.0.X only):
In Angular 1.0.X, the target of the original answer here, promises will get special treatment by the View. When they resolve, their resolved value will be bound to the view. This has been deprecated in 1.2.X
module.controller('MyCtrl', function($scope, myService) {
// now you can just call it and stick it in a $scope property.
// it will update the view when it resolves.
$scope.foos = myService.getFoos();
});
The best practise would be to abstract the $http call out into a 'service' that provides data to your controller:
module.factory('WidgetData', function($http){
return {
get : function(params){
return $http.get('url/to/widget/data', {
params : params
});
}
}
});
module.controller('WidgetController', function(WidgetData){
WidgetData.get({
id : '0'
}).then(function(response){
//Do what you will with the data.
})
});
Abstracting the $http call like this will allow you to reuse this code across multiple controllers. This becomes necessary when the code that interacts with this data becomes more complex, perhaps you wish to process the data before using it in your controller, and cache the result of that process so you won't have to spend time re-processing it.
You should think of the 'service' as a representation (or Model) of data your application can use.
The accepted answer was giving me the $http is not defined error so I had to do this:
var policyService = angular.module("PolicyService", []);
policyService.service('PolicyService', ['$http', function ($http) {
return {
foo: "bar",
bar: function (params) {
return $http.get('../Home/Policy_Read', {
params: params
});
}
};
}]);
The main difference being this line:
policyService.service('PolicyService', ['$http', function ($http) {
I put an answer for someone who wanted a totally generic web service in Angular. I'd recommend just plugging it in and it will take care of all your web service calls without needing to code them all yourself. The answer is here:
https://stackoverflow.com/a/38958644/5349719
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.