Reputation: 3400
What is the proper way to bind view to data from a service? I have seen that $watch is discouraged.
I have a view that renders items from an array that come from the service.
The service will periodically refresh its data from the server
$http({...some params...})
.success(function(data) {
cache[key] = data;
});
In the view controller the data is exposed :
this.items = SomeFactory.cache[key]
View is binding :
ng-repeat="item in $ctrl.items"
my understanding is that since cache[key]
is being assigned to a new array then the view is still binding to the old address and thus not updated.
What is the proper way to do this?
It does work if I bind it using a function (eg: ng-repeat="item in $ctrl.items()"
but then how do i modify item? also performance wise i dont know if this is less efficient than binding directly?)
Upvotes: 2
Views: 82
Reputation: 312
There are a few ways of doing this which I have used quite successfully before:
Assume we have the same factory structure:
angular.module('someApp').factory('SomeFactory', function($http) {
var cache = {};
$http({...some params...})
.success(function(data) {
// ...
cache[key] = data;
});
return {
cache: cache
};
});
Instead of setting items
to be the cache[key]
directly, we can bind to the factory itself. After all, a factory (or a service) is just an object:
this.cache = SomeFactory.cache // bind to container which is going to persist
Then in the template you just use ng-repeat="item in $ctrl.cache[key]"
and it will automatically get the latest changes. You can even go the extra step and just bind your service to the scope directly, as this.SomeFactory = SomeFactory
and then use it in your views as $ctrl.SomeFactory.cache[key]
. It works well for data structures which are loaded once per app, or for services made specially for a specific controller.
There's a potential problem with this approach depending on how you use it. You mentioned modifying the array but since we override the reference asynchronously, it might be difficult to make sure those changes persist.
Another way of solving this is to modify our factory a little bit:
angular.module('someApp').factory('SomeFactory', function($http) {
var cache = {};
cache[key] = []; // initialise an array which we'll use everywhere
$http({...some params...})
.success(function(data) {
// ...
cache[key].push.apply(cache[key], data); // essentially a pushAll
// we could also set cache[key].length = 0 to clear previous data or do something else entirely
});
return {
cache: cache
};
});
As you can see, instead of replacing the reference, we're operating on the same array that the items
property is referencing. The benefit here is that if we manipulate the array in any way (i.e. add extra items), we have a bit more flexibility about what to do with them once we receive new asynchronous data.
This is in fact similar to the approach ngResource uses - collection query methods return an array which is filled out later on, once the data is actually fetched.
The final way which I've used before is to just expose a promise for the data:
angular.module('someApp').factory('SomeFactory', function($http) {
var cache = {};
var whenReady = $http({...some params...})
.then(function(data) {
// ...
cache[key] = data;
return data;
});
return {
cache: cache,
whenReady: whenReady
};
});
Then in the controller you just assign it once it resolves:
SomeFactory.whenReady.then(items => {
this.items = items; // or this.items = SomeFactory.cache[key];
});
All of these avoid an extra $watch
but it depends on what's most convenient for your app.
Upvotes: 1