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

$watch doesn't work the way it used to work (regression) #9222

Closed
pocesar opened this issue Sep 22, 2014 · 23 comments
Closed

$watch doesn't work the way it used to work (regression) #9222

pocesar opened this issue Sep 22, 2014 · 23 comments

Comments

@pocesar
Copy link
Contributor

pocesar commented Sep 22, 2014

Clicking the add more buttom should add items everytime the button is pressed, but it doesn't anymore

http://plnkr.co/edit/n9GS0hECnIu2rrYYbu4d?p=preview

Uncomment script tag of beta.19 and try again (watch the items added to the select element, and the console.log messages)

Beta 19:

watch called
refresh called
watch called
(click) add called 
watch called
watch called
refresh called
watch called

rc 2:

watch called
refresh called
watch called
(click) add called 
watch called
watch called
@caitp
Copy link
Contributor

caitp commented Sep 22, 2014

Possibly related to 3f0e642

Can you explain why this matters to you? From the plunker, it looks like the expected behaviour is happening

edit I guess it's probably not that commit if this was apparently working in beta 19

@pocesar
Copy link
Contributor Author

pocesar commented Sep 22, 2014

The built select element is 1 digest behind on rc2 (options wise). The "refresh called" isn't being called immediately after pressing Add more. But it does in beta.19

@caitp
Copy link
Contributor

caitp commented Sep 22, 2014

it doesn't look 1 element behind to me, it looks like it's adding a new option every time the button is clicked...

@caitp
Copy link
Contributor

caitp commented Sep 22, 2014

I am literally watching the DOM update in the debug tools, it looks like it's doing exactly what you're telling it to do.

@pocesar
Copy link
Contributor Author

pocesar commented Sep 22, 2014

Compare both versions, "refresh called" is never called after first press, and subsequently.

Starts out with:

1
2
3
4
5

After first press, the built element should be:

1
2
3
4
5
6

this does happen on beta.19, but not on rc.2. it's not to watch the SELECT DOM element, the problem lies on the $watch never being called.

Because the $watch callback is being called 1 digest behind, the items doesn't match. $('select option').length (the hidden select DOM element) and $('.selectricItems li').length (the built select) after the click. Those numbers match in beta.19

@caitp
Copy link
Contributor

caitp commented Sep 22, 2014

well I mean basically you're trying to run your update after ngOptions does its thing, and this is probably not the right way to do it.

Maybe we could add some method to the selectControl or something, to notify people when the list is re-rendered. That's probably a better solution than trying to watch the DOM

@pocesar
Copy link
Contributor Author

pocesar commented Sep 23, 2014

@caitp although it worked in beta.19 (and all previous versions). selectController is kinda lacking in features right now, and there's no way to watch over the changed current set of options, I already opened an issue #7417 about this lack of feature. relying on setting a scope variable with the result set of a filter feels hacky

@pocesar
Copy link
Contributor Author

pocesar commented Sep 24, 2014

if ng-options could emit for example, the event "$optionsRender" after it finished parsing would solve all the problems, providing the items array as the second parameter.

pseudo-code:

directive('someDirectiveThatDoesSelect', function(){
  return {
     restrict: 'A',
     link: function(scope, el){
          scope.$on('$optionsRender', function(event, items){
             myjQuerySelectPlugin.addItems(items).refresh();
          });
     }
  }
});

@pocesar
Copy link
Contributor Author

pocesar commented Sep 24, 2014

@caitp I'd love to submit a PR (I'm in badly need for this) I just don't want to waste time on a PR that wouldn't be accepted, what is better, an array called $optionsChangeListeners (like $viewChangeListeners in ngModel) or emitting an event?

@lgalfaso
Copy link
Contributor

@pocesar trying to catchup on this. Are you saying that if you have <select ng-model="foo">...</select> and to this ng-model you add a listener to ngModel.$viewChangeListeners, then those listeners will not get called when the value changes?

@pocesar
Copy link
Contributor Author

pocesar commented Sep 24, 2014

@lgalfaso nope, I want to get notified when ng-options is done rendering and be able to know the current set of items (since there's no way to parse the expression on ng-options outside the directive itself). I'm not worried about the current selected select value, but being able to know (anywhere, regardless of scopes) the current list of items, so I can plug the listing to jQuery plugins.

@lgalfaso
Copy link
Contributor

@pocesar ok, got it. We do not emit a lot of events (AFAICT, only ng-include and ng-view do), so other options would be best. That said, the PR would not be simple, so I am not sure how open people would be to the change

@caitp
Copy link
Contributor

caitp commented Sep 24, 2014

An event is probably not what we want to do. But something similar to $viewChangeListeners, why not

@caitp
Copy link
Contributor

caitp commented Sep 24, 2014

The alternative is making DCCD work for everybody, and that's hard to do :[

@pocesar
Copy link
Contributor Author

pocesar commented Sep 24, 2014

@caitp got it

@lgalfaso well, it's a feature, and won't break anything that already exists. I'll take a stab at it

EDIT: just finishing the tests, I thought it would be much harder to achieve this

@pocesar
Copy link
Contributor Author

pocesar commented Sep 26, 2014

@caitp @lgalfaso review to the PR is welcome :)

just tested the modification on the use case on that plunkr and now it's working nicely.

@caitp caitp modified the milestones: Backlog, 1.3.0 Sep 26, 2014
@jeffbcross jeffbcross modified the milestones: Backlog, 1.3.0 Sep 29, 2014
@IgorMinar
Copy link
Contributor

This is because option elements are now rendered in $$postDigest phase which means that the change detection is now active any more and only the next dirtycheck triggered by unreleated event will pick up that the watch has some changes.

The relevant commit is most likely 6f7018d

Watches should almost never read state from the DOM like you are doing here, it's just a bad practice.

I suggest that you create a directive that will register a DOM mutation observer (DMO) on the select element and reexecute the jQuery plugin when DMO fires.

@pocesar
Copy link
Contributor Author

pocesar commented Sep 30, 2014

@IgorMinar MutationEvents were deprecated and Mutation Observer have no good browser support in mobile, why can't angular give a simple way to do this?

@IgorMinar
Copy link
Contributor

browser support is that bad: http://caniuse.com/#feat=mutationobserver

as I said, if we were to give you a hook here, we'd then be asked to give hook in many other places. all these hooks are not needed if you use DMO.

alternatively you can observe the model that will cause the dom to be changed (in your case the model that is bound to ng-options) and when it changes, schedule a $timeout that will trigger the jquery plugin.

@caitp
Copy link
Contributor

caitp commented Sep 30, 2014

I'm not sure there's any reason why we couldn't trigger a DOM event after rendering that people could hook into --- it would be one line of code, and it would have (I think) a pretty minimal perf impact most of the time.

But on the other hand, it is what it is

@pocesar
Copy link
Contributor Author

pocesar commented Sep 30, 2014

@IgorMinar this one get's me, directives are all about DOM, and yet, have minor DOM tooling out-of-the-box. I tried scheduling a timeout, and it didn't work (inside scope.$watch). Having to resort to another array of a subset of elements, pass it to my directive, just to trigger a DOM event is kinda overkill, mainly because a simple $watch was working before.

@caitp When I asked if would be better to emit an event or creating a listeners array, the prefered way was to use the listeners array. maybe a more general solution of events would be more appropriate now seeing what Igor wrote about

@caitp
Copy link
Contributor

caitp commented Sep 30, 2014

jQuery events are basically just listener arrays, but you get at them slightly differently. I think in practice you'd still end up wrapping your event listener in a directive.

Anyways, I don't know if it's something we can do. I do think it would be a pretty minimal footprint, but if we can't do it, we can't do it

@IgorMinar
Copy link
Contributor

The dom event is fired already - the DOM mutation event, but because those are known to create perf issues, there were replaced with DMO. I still don't understand why DMO is good for this scenario. It looks like a perfect fit to me.

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

Successfully merging a pull request may close this issue.

5 participants