Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Collection attribute watches: $watch('object@attribute') #1093

Closed
IgorMinar opened this issue Jun 24, 2012 · 10 comments
Closed

Collection attribute watches: $watch('object@attribute') #1093

IgorMinar opened this issue Jun 24, 2012 · 10 comments

Comments

@IgorMinar
Copy link
Contributor

Often it is useful to be notified if

Given model:

$scope.users = [
  { id: 1, name: 'foo', age: 33},
  { id: 2, name: 'bar', age: 22},
  { id: 3, name: 'baz', age: 11}];

and watch:

$scope.$watch('users@age', function(newVal, oldVal) {
});

the watch would keep track of age property of each item in the users collection and it would fire if a value of this property on any of the user object changes or when a user object is added or removed added to/from the collection.

the newVal and oldVal would be an array of age values with indexes matching the current order of items in the users array.

so during the first digest the watch would fire with these values:

newVal = [33, 22, 11];
oldVal = NaN;

if a new user with age 44 is appended to the array the watch would then fire with:

newVal = [33, 22, 11, 44]
oldVal = [33, 22, 11];

if the only user with age 44 is then updated to 55 the watch would fire with:

newVal = [33, 22, 11, 55];
oldVal = [33, 22, 11, 44];

if the users array is sorted and items in the array are moved around but user is added/removed/deactivated then no watch fires.

if the first user is then updated to age 99 (after sorting) and this user was previously the last user in the array then the watch should fire with:

newVal = [99, 33, 22, 11];
oldVal = [55, 33, 22, 11];

notice that the oldVal contents were reordered, to match the new order of elements in the users array as well as newVal array. this is necessary for enabling developers to figure out the actual change (newVal[0] vs oldVal[0]).

implementation notes: it should be possible to reuse hashKeys just like what repeater does to implement this watcher.

@ggoodman
Copy link
Contributor

This would really be fantastic. +1

@jonricaurte
Copy link

I agree. Is there a time frame for when this feature will be added to angular? Thanks.

@ggoodman
Copy link
Contributor

ggoodman commented Dec 6, 2012

Another alternative that I often wish was exposed was a way to reuse the code used to resolve changes to object/arrays for ng-repeat.

It would be cool if that bit of code was refactored as a service available outside ng-repeat. The api could be as simple as $list.$observe(list, listChangeWatcher); where the listChangeWatcher has its only argument being an event object with metadata for the event type, the key that was modified, and other goodies.

@mstefaniuk
Copy link

Collection watchers are excellent in terms of ease defining rules. I would prefer calling listener one-by-one for each element in list. This approach is used in long-living W3C standard XForms for <bind> element (check http://en.wikibooks.org/wiki/XForms/Bind).
In many features AngularJS is very similar to XForms (bidi-binding, declarative templates, components, watchers) with main difference - there is no so powerful query language for JSON as XPath for XML world. I know that filters give some possibilities. What you suggest is to introduce kind-of querying constructs and I'm full for that.
I would suggest to add other listeners for $watch similar as in XForms binding. Current listener fits for calculate function. It would good to add constraint listener (as validation dimension).

@omenking
Copy link

I find myself wanting do the following above. I created a helper method in my app since this problem occurs for me frequently.

helper method I wrote (using underscore.js)

AppHelper =
  collection_exp:(scope,name,attr)=>
    eval "collection = scope.#{name}"
    count = collection.length
    exp   = []
    _.each collection, (e,i)=>
      exp.push "#{name}[#{i}].#{attr}"
    exp.join ' + '
window.AppHelper = AppHelper

helper method output eg.:

calendar.tasks[0].data + calendar.tasks[1].data + calendar.tasks[2].data + calendar.tasks[3].data 

just pass it on in

exp = AppHelper.collection_exp @$scope, 'calendar.tasks', 'data'
@$scope.$watch exp, =>
  # make it happen

@davemerrill
Copy link

I agree with what I think mstefaniuk is suggesting, in that I'd like the possibility for a collection watch listener to be called on individual collection items when they change.

One possible API I've been thinking about:
$watchItems(collection, detectorFn, listenerFn)

This would create a new type of watcher that iterates over collection, calculates detectorFn(item, collection, index), and if it's not the same as the previous result for that item, calls listenerFn(item, collection, index). The detectorFn should return a simple value or object representing the pieces of the passed item you want to watch, making it sensitive only to the desired fields, and potentially avoiding a deep equals compare for that item

A proof of concept that creates a deep watch is here: http://plnkr.co/edit/4nPfYtwCd8VfVSyT5OfL?p=preview

Obviously, it relies on the built-in deep watch, and incurs its overhead. so as-is it's only suitable if running the listener is relatively expensive. If $watchItems was built in, a detector that returned a simple value would be dramatically more efficient than a deep watch, with the added benefit of item-specific listener calls.

I'd seriously love this, and I bet others would too. Thoughts?

@davemerrill
Copy link

Also want to point out that the detectorFn model avoids funky syntax and sub-optimum generic execution plans for expressing which attributes you want to watch, particularly ones at some arbitrary path inside each top-level item.

An enhancement to the current API would be to allow passing such a detector function to $watch as the deepEquality argument. That would provide the potential efficiency improvement, but not the per-matched-item listener calls. An additional boolean argument triggering per-item behavior is also possible, but the implementation I think gets to be pretty different by that point, and a new method seems more appropriate.

@petebacondarwin
Copy link
Contributor

Really I think this has to tie in with the proposed changes to ng-repeat:
#1661 (comment)

On 18 February 2013 16:24, Dave Merrill notifications@github.com wrote:

Also want to point out that the detectorFn model avoids funky syntax and
sub-optimum generic execution plans for expressing which attributes you
want to watch, particularly ones at some arbitrary path inside each
top-level item.


Reply to this email directly or view it on GitHubhttps://github.com//issues/1093#issuecomment-13729651.

@davemerrill
Copy link

@petebacondarwin, while I definitely see value in that proposal, I must be thick, again, because don't I see how they're related to this. This isn't about how anything iterates, only how you can specify that a) change detection could be based on a user provided function, and b) individual collection items are separately tracked, so c) the listener function gets passed the changed item, as well as the whole collection and the index of the item.

However, unless I misunderstand how passing a function as the object to watch works, the external object version from the plunk does everything I need, and my thinking about extra overhead from using $watch was wrong too. Unfortunately, plunkr ate the entire contents of app.js, and I don't see any way to get it back. It's not super complicated, so I will do it, but I can't right now.

That external tool seems like something people could make good use of, so I may just put it up on github.

Am I overestimating the value of this strategy?

@btford btford closed this as completed Aug 24, 2013
@btford
Copy link
Contributor

btford commented Aug 24, 2013

As part of our effort to clean out old issues, this issue is being automatically closed since it has been inactivite for over two months.

Please try the newest versions of Angular (1.0.8 and 1.2.0-rc.1), and if the issue persists, comment below so we can discuss it.

Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants