chrismarx chrismarx - 2 months ago 8
AngularJS Question

Angularjs - Cleanest way to add promises to scope, now that Angular deprecated auto unwrapping of promises

I really like the clean (and I think easy to follow) way that promises were autounwrapped:

$scope.myData = DataService.query({something:"etc"}); // done;


And I really don't care for what seems to be the standard way of doing it now, without the automatic unwrapping:

DataService.query({something:"etc"}).$promise.then(function (data){
$scope.myData = data;
});


What I'd like to see is something like this:

$scope.pulseData = $scope.setPromise(CitsciAnalytics.pulse({
projId:"yardmap"
}));


But I can't see how to make that happen. The closest I got was:

$scope.pulseData = $scope.setPromise("pulseData", CitsciAnalytics.pulse({
projId:"yardmap"
}));


Using a function added to the root scope:

.run(["$rootScope", "$log", function ($rootScope, $log) {
//parent method to avoid promise unwrapping boilerplate
$rootScope.setPromise = function (scopeVar, promise) {
if (arguments.length === 2 && promise && promise.$promise) {
var scope = this;
promise.$promise.then(function (data){
scope[scopeVar] = data;
});
} else {
$log.error("$rootScope.setPromise has invalid arguments");
}
};
}]);


but I don't like the unDRY requirement of having to pass the scope variable name as an additional string. Has anyone else tackled this, or see a way to do this more cleanly?

Answer

First of all you don't need to use

DataService.query({something:"etc"}).$promise.then(function(data){
    $scope.myData = data;
});

which clearly refers to a $resource, because $resource will return an empty array or object and fill it with data as they arrive.

So, with a $resource class, you can still use

$scope.myData = DetaService.query(...);

$resource's paradigm is also a good approach to follow in your own "data-fetching" services: Return and empty array and fill it with data as they arrive.

E.g.:

.factory('DataService', function ($http) {
    function query(params) {
        var data = [];
        $http.get('/some/url/with/params').then(function (response) {
            response.data.forEach(function (item) {
                data.push(item);
            });
        });
        return data; 
    }

    return {
        query: query
    };
});

.controller('someCtrl', function ($scope, DataService) {
    $scope.data = DataService.query({...});

If you have to use thrid-party services that return a promise, you can implement your generic function to offer similar functionality:

.run(function ($rootScope) {
    $rootScope.promiseToArray = function (promise) {
        var arr = [];
        promise.then(function (data) {
            data.forEach(function (item) {
                arr.push(item);
            });
        });
        return arr;
    };
});

.controller('someCtrl', function ($scope, ThirdPartyService) {
    $scope.data = $scope.promiseToArray(ThirdPartyService.fetchData());
});

See, also, this short demo.


The above samples are just for illustration purposes and not production-ready code.
I a real-world app, you would need to gracefully handle exceptions, rejections etc.

Comments