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

Onchange is getting triggered before the model update for isolate scope #4558

Closed
vinayk406 opened this issue Oct 21, 2013 · 11 comments
Closed

Comments

@vinayk406
Copy link

Onchange event for the elements having isolate scope is getting triggered before the model update.

Example:

<!doctype html>
<html ng-app="App">
    <head>

        <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />

        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"></script>
        <script src="http://code.angularjs.org/1.0.6/angular.js"></script>
        <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>

        <script type="text/javascript">
            "use strict"

            var App = angular.module('App', []);
            App.controller('Ctrl', function ($scope,$timeout) {

                $scope.change1 = function () {
                    console.warn("inside change", $scope.y);
                }

                $scope.change2 = function () {
                    console.warn("inside change", $scope.x);    
                }

            });

            App.directive("wminput", function () {
                return {
                    restrict: "E",
                    replace: true,
                    scope: {
                        "onChange":"&",
                        "value": "="
                    },
                    template: '<input type="text" data-ng-model="value" data-ng-change="onChange();">'
                }
            });

        </script>

    </head>
    <body>
        <div data-ng-controller="Ctrl">
            "Onchange will be triggered after the model update"
            <input data-ng-change="change1()" data-ng-model="y">
            <br><br>
            "Onchange will be triggered before the model update"
            <wminput on-change="change2()" value="x"></wminput>
        </div>

    </body>
</html>
@Narretz
Copy link
Contributor

Narretz commented Jun 25, 2014

Interesting corner case ... http://plnkr.co/edit/lsrga8qMcbgNWATrVht3?p=preview

This happens only when the onChange function is delegated to the parent scope width the '&' binding.

@Narretz Narretz added this to the Backlog milestone Jun 25, 2014
@vinayk406
Copy link
Author

We have been working on a RAD tool based on angularjs.
This was a major bug as every widget in the application has isolateScope.
We have resolved the problem by creating a proxy method for onChange.

Have a look at : https://studio.wavemaker.com/

Regards, Vinay

@Narretz
Copy link
Contributor

Narretz commented Jun 25, 2014

Could you post an example of this proxy and how it is used? Might help in debugging this.

@vinayk406
Copy link
Author

See the example at: http://plnkr.co/edit/Xe6ErQrg7WL6aAIoM9Gd?p=preview

@Yankovsky
Copy link

Will it be fixed anytime soon? It is sooooo confusing.

@Narretz
Copy link
Contributor

Narretz commented Feb 20, 2016

After further consideration, I have to say that this is expected behavior.. In the plnkr example, the value of the ngModel is a two bound string between the controller and the directive scope. Now when you are changing the value in the directive, ngModel will update the isolate scope first, and fire the ngChange in the same digest. Since the two-way binding hasn't yet picked up a change to the directive scope, the ngChange in the parent scope gets the "old" value of x. Now the two-way binding watcher needs at least two digest cycles to stabilize the value, so the controller scope is only updated after the ngChange has been called.
I agree that this is confusing, but that's how it works. You can workaround this by binding an object to the directive which holds the value the you bind to ngModel. That way, the value is set by reference in both the isolate and the controller scope at the same time and available when ngChange is executed: http://plnkr.co/edit/fs7S6yX1a5aeo1Ese522?p=preview

@Suamere
Copy link

Suamere commented Oct 31, 2016

Narretz, I strongly disagree. There is a contract made with the developer consuming the code that the two-way binding takes precedence. When a change is made anywhere along the chain of binding, the entire chain should be updated before continuing. So even if another directive inherits from this directive, and something further down binds to a binding of a binding... if that model is updated, the original controller scope model at the bottom of the stack should be updated in the same digest cycle before continuing on to other functions. It's what is expected from the consumer.

@jakobadam
Copy link

jakobadam commented Apr 18, 2017

Slightly related, and likewise confusing. When using ngChange to trigger validation on another field that shares a ngModel, the change is triggered before the $modelValue is updated. Making validation convoluted.

Using $timeout as a workaround. Still confused why that's needed.

Example:
http://codepen.io/jakobadam/pen/qmBdrJ?editors=1010

regards, Jakob

@bombsite
Copy link

Are there any plans to revisit this issue? Perhaps reopen?

I ran into this issue and reading through the comments, while the way it currently works due to the isolate scope makes sense; the outcome that comes from expecting an updated model when the on-change call fires and getting the model one iteration previous definitely is not the behavior you expect as a developer.

@nahueld
Copy link

nahueld commented May 6, 2017

I adhere to the question, is this gonna be revisited?

@gkalpak
Copy link
Member

gkalpak commented May 6, 2017

I don't think so. The current behavior is expected and although it is not desirable in some cases, there are at least as many cases where it is. Changing it now would (a) be a breaking change and (b) make many usecases unsupported (e.g. where you want ngChange to modify the value before it updates the parent). It's one of those cases where you can't have everyone happy, so we have to stick with the version that:

  • Makes sense.
  • Does not break existing users.
  • Supports more specific/advanced usecases, even if at the cost of little extra code (see below).

The current implementation of ngChange is tightly bound to the concept of ngModel. Directive bindings is a totally independent concept. The two can work together nicey, but should not affect the implementation/inner workings of one another.

As already mentioned, if your specific callback needs access to the updated value, there are a few options:

  1. Pass the updated value as argument to the callback.

  2. If you need it to be run asynchronously (i.e. after the digest is over and all bindings have been propagated), you can always use $timeout inside the callback.

  3. If you want an "async" version of ngChange, it is also trivial to implement as a custom directive. (You could also overwrite the built-in directive, but I would definitely not recommend that.) E.g.:

.directive('myAsyncChange', function($timeout) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attr, ctrl) {
      ctrl.$viewChangeListeners.push(function() {
        $timeout(function() {
          scope.$eval(attr.myAsyncChange);
        });
      });
    }
  };
});
<input ng-model="foo" my-async-change="onChange()" />

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

9 participants