I have a problem binding data from an external HTTP API. I tried to follow best practices and tried several approaches (reading the Internet and SO), but the "magic" of data-binding doesn't work and I can't find/understand why. Here's the code :
angular.module('myApp', [])
.service('externalAPI', ['$q', function($q) {
var data = [], deferred = $q.defer();
// The HTTP calls are made through the provided SDK lib
// It uses promises too
var pullData = function() {
externalLib.get('somePath').then(function(newData) {
// I expect this assignment to help the data-binding
data = newData
deferred.resolve(newData)
})
return deferred.promise;
};
return {
data: data,
pullData: pullData
}
}])
.controller('myController', ['$scope', 'externalAPI', function($scope, externalAPI) {
// Here I tried a lot of approaches to make the "magic" of databinding works, with no luck
// 1. Bind the whole service, so that externalAPI.data is always updated in templates
$scope.externalAPI = externalAPI;
externalAPI.pullData()
// 2. Bind and expose just the data
$scope.data = externalAPI.data;
externalAPI.pullData()
// 3. Do #1 + $watch the whole externalAPI object
// Note: I put console.log() calls inside the first function, it always return the initial version
$scope.externalAPI = externalAPI;
$scope.$watch(function() { return externalAPI }, function(newVersion) {
$scope.externalAPI = newValue;
});
externalAPI.pullData()
// 4. Do #2 + $watch just the data of the externalAPI
$scope.data = externalAPI.data;
$scope.$watch(function() { return externalAPI.data }, function(newData) {
$scope.data = newData;
});
externalAPI.pullData()
// 5. Only thing that works fine : manually wire $scope data from the call
$scope.data = externalAPI.data
externalAPI.pullData().then(function(newData) {
$scope.data = newData
})
}])
Manually wiring as in #5 defeats the purpose of data-binding, since I will be using the externalAPI service and pulling data from other parts of the app, and I want this data to always be fresh and reflected in the UI. What did I do wrong ?
Note: I'm using Angularsjs 1.2.5, no jQuery or other js lib, just the SDK lib to access data (I have to, it does several unrelated but necessary things in the background).
Beware : since 1.2.0 Angular promises are not automatically unwrapped in the templates. So you can't directly display a promise in the template anymore. this is due to this breaking change: https://github.com/angular/angular.js/commit/5dc35b527b3c99f6544b8cb52e93c6510d3ac577
This change has been made to remove some 'black magic' and keep a symmetrical behaviour in both templates or regular javascript.
So your pullData
method should update service.data
directly, then you can use externalAPI.data
as in your #1.
The problem in your actual #1 is that you update the data
variable but service.data
still points to the old value.
You can use this pattern in your service instead :
function pullData() {
service.data = 'newValue';
}
var service = {
data: null,
pullData:pullData
}
return service;
So now we update service.data
directly, not another variable, and your templates should automagically update :)
Another point, if for some reason you use a 3rd party lib and not the builtin $http, then you need to kick a $digest cycle manually, and you can do so wrapping your callback code in a scope.$apply()
. $apply
is prefered to $digest
as it handles some corner cases and catch exceptions:
function pullData() {
// this comes from an external lib so we must
// manually tell angular we've modified some data
$rootScope.$apply(function() {
service.data = 'newValue';
});
}