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

2

5

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?

Cleanlimbed answered 1/7, 2014 at 19:50 Comment(0)
M
10

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.

Minuscule answered 1/7, 2014 at 20:31 Comment(3)
ah, thanks for clarifying, also what pixelbits suggested, nice to have the official way to go forward with this-Cleanlimbed
so, for people using $resource, this doesn't really affect anything? github.com/angular/angular.js/commit/…Cleanlimbed
Exactly ! According to the docs: "It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. [...] This means that in most cases one never has to write a callback function for the action methods."Minuscule
H
0

We can re-enable unwrapping if we really want to by setting the option to true in a .config() function:

.config(function($parseProvider) {
   $parseProvider.unwrapPromises(true) ;
});
Halutz answered 5/10, 2016 at 5:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.