103

This is my HTML:

<input id="selectedDueDate" type="text" ng-model="selectedDate" />

When I type into the box, the model is updated via the 2-way-binding mechanism. Sweet.

However when I do this via JQuery...

$('#selectedDueDate').val(dateText);

It doesn't update the model. Why?

John Slegers
  • 45,213
  • 22
  • 199
  • 169
Greg
  • 31,180
  • 18
  • 65
  • 85
  • 2
    Why would you do the second one to begin with ? You're using a framework and then deciding to bypass it and set the value through DOM manipulation. Best advice one can give with angular to beginners: forget jquery exists. in 90% of cases angular will be enough and the remaining 10% can be achieved through jqlite inside a directive's link (element is actually a jqlite wrapped element ready to be manipulated). – Void Jul 01 '16 at 08:00
  • 3
    very important question – Syed Md. Kamruzzaman Sep 05 '16 at 19:14
  • 2
    there are very good reasons why you might want to change angular models with dom manipulation, maybe you're a dev working with an A/B testing tool and want to fill in forms behind the scenes, or you're working on a greasemonkey script to autofill forms. – Shadaez Jul 13 '17 at 17:26

10 Answers10

136

Angular doesn't know about that change. For this you should call $scope.$digest() or make the change inside of $scope.$apply():

$scope.$apply(function() { 
   // every changes goes here
   $('#selectedDueDate').val(dateText); 
});

See this to better understand dirty-checking

UPDATE: Here is an example

Community
  • 1
  • 1
Renan Tomal Fernandes
  • 10,978
  • 4
  • 48
  • 31
  • 1
    I made this like you say: http://fiddle.jshell.net/AladdinMhaimeed/agvTz/8/ but it does not work – Aladdin Mhemed Oct 16 '12 at 05:58
  • 2
    See this http://fiddle.jshell.net/agvTz/38/ You should call the `function` $scope.$apply passing a function as argument. And $apply should be call when you will make the changes on $scope, so, inside of onSelect. Ah, I expect that you put DOM manipulation inside of controller just for the example, in a real app this is wrong ;) – Renan Tomal Fernandes Oct 16 '12 at 15:05
  • 1
    then what should I do to make good angular practice? separate DOM manipulation into a directive? – Aladdin Mhemed Oct 17 '12 at 03:16
  • 3
    Yes, here an example http://fiddle.jshell.net/agvTz/39/ the directive can be used as many times you want with a simple `datepicker="scopeKeyThatYouWant"` in the input – Renan Tomal Fernandes Oct 17 '12 at 03:45
  • 1
    Just call.. $scope.$digest() to settle. will it slow down ? – Thant Zin Jun 04 '13 at 02:52
  • This is perfect. To add, this is also the case for reverse scenario. When you change angular value in javascript also use '$scope.$digest()' to see the update in its input textbox. – Wilson Sep 25 '14 at 15:21
  • there is a problem if we add second element. check this out http://fiddle.jshell.net/agvTz/378/ – Amin May 17 '15 at 14:33
31

Just use;

$('#selectedDueDate').val(dateText).trigger('input');
Jan Kuri
  • 927
  • 12
  • 17
  • I used `trigger('input')` with a datepicker in its `onSelect` event. It updates the value correctly. – iman Mar 17 '15 at 19:55
  • This did the trick for me. I was updating the value of a bound input from a directive test and wrapping the `.val('something'` call in an `$apply` (or calling `$digest` afterward) didn't work. – Tom Seldon Apr 29 '15 at 11:38
  • 2
    After hours of searching, this was the one that worked! Thank you! – Dan Oct 28 '15 at 18:24
  • Alhamdulillah, perfect, its working for me. thanks to save my hours – Syed Md. Kamruzzaman Sep 05 '16 at 19:13
  • True. I was using `$('#selectedDueDate').val(dateText).trigger('change');` which was not working for me. `.trigger('input');` works like magic – jafarbtech Dec 09 '19 at 12:18
  • If you want to get rid of jQuery just use ```const event = new Event('input', {bubbles: true, cancelable: true}); element.dispatchEvent(event);``` – maxshuty Nov 14 '21 at 22:14
17

I have found that if you don't put the variable directly against the scope it updates more reliably.

Try using some "dateObj.selectedDate" and in the controller add the selectedDate to a dateObj object as so:

$scope.dateObj = {selectedDate: new Date()}

This worked for me.

Ben Taliadoros
  • 7,003
  • 15
  • 60
  • 97
  • 4
    This worked for me as well. I'd wasted over 2 hours trying to figure out why two-way binding wasn't working. It worked fine on the page, but the scope in the controller was not being updated. Then I tried your approach out of desperation (because it made no sense to me that this should be true) and hot damn, it worked! Thanks! – TMc May 25 '14 at 01:47
  • 3
    Same here - can any angularJs guru explain why this works? It's like the view has it's own nested scope and sometimes you get stuck updating just the view-scope, not the controller-scope – Tom Carver Sep 15 '14 at 16:13
  • 1
    Works well for me! I wonder why in the heck it doesn't work the same on the bare scope. – Stephen Nov 19 '14 at 03:40
  • 1
    It has little to do with angular, and a lot to do with how javascript works. When you assign the scope variable to an object, you are assigning it by reference, as opposed to by value as done when a var is set equal to a primitive value. I talked about about it in my post here. http://stackoverflow.com/questions/14527377/handling-data-binding-in-angularjs-services/35821086#35821086. I referenced this plunk I made in that post to illustrate https://plnkr.co/edit/WkCT6aYao3qCmzJ8t5xg?p=preview. – Nick Brady Mar 10 '16 at 06:41
5

Try this

var selectedDueDateField = document.getElementById("selectedDueDate");
var element = angular.element(selectedDueDateField);
element.val('new value here');
element.triggerHandler('input');
Ray
  • 59
  • 2
  • 2
  • It is usable when you don't have the access to the angular's controller $scope. When you write a script with a Tampermonkey, for example. – wassertim Oct 08 '15 at 09:13
3

Whatever happens outside the Scope of Angular, Angular will never know that.

Digest cycle put the changes from the model -> controller and then from controller -> model.

If you need to see the latest Model, you need to trigger the digest cycle

But there is a chance of a digest cycle in progress, so we need to check and init the cycle.

Preferably, always perform a safe apply.

       $scope.safeApply = function(fn) {
            if (this.$root) {
                var phase = this.$root.$$phase;
                if (phase == '$apply' || phase == '$digest') {
                    if (fn && (typeof (fn) === 'function')) {
                        fn();
                    }
                } else {
                    this.$apply(fn);
                }
            }
        };


      $scope.safeApply(function(){
          // your function here.
      });
Rajendra kumar Vankadari
  • 2,247
  • 1
  • 16
  • 16
3

Just run the following line at the end of your function:

$scope.$apply()

Rohan M Nabar
  • 83
  • 1
  • 1
  • 5
3

You have to trigger the change event of the input element because ng-model listens to input events and the scope will be updated. However, the regular jQuery trigger didn't work for me. But here is what works like a charm

$("#myInput")[0].dispatchEvent(new Event("input", { bubbles: true })); //Works

Following didn't work

$("#myInput").trigger("change"); // Did't work for me

You can read more about creating and dispatching synthetic events.

Hari Das
  • 10,145
  • 7
  • 62
  • 59
1

AngularJS pass string, numbers and booleans by value while it passes arrays and objects by reference. So you can create an empty object and make your date a property of that object. In that way angular will detect model changes.

In controller

app.module('yourModule').controller('yourController',function($scope){
$scope.vm={selectedDate:''}
});

In html

<div ng-controller="yourController">
<input id="selectedDueDate" type="text" ng-model="vm.selectedDate" />
</div>
Himanshu Mittal
  • 794
  • 6
  • 19
1

Just use:

$('#selectedDueDate').val(dateText).trigger('input');

instead of:

$('#selectedDueDate').val(dateText);
Wekerle Tibor
  • 457
  • 2
  • 7
  • 19
0

I've written this little plugin for jQuery which will make all calls to .val(value) update the angular element if present:

(function($, ng) {
  'use strict';

  var $val = $.fn.val; // save original jQuery function

  // override jQuery function
  $.fn.val = function (value) {
    // if getter, just return original
    if (!arguments.length) {
      return $val.call(this);
    }

    // get result of original function
    var result = $val.call(this, value);

    // trigger angular input (this[0] is the DOM object)
    ng.element(this[0]).triggerHandler('input');

    // return the original result
    return result; 
  }
})(window.jQuery, window.angular);

Just pop this script in after jQuery and angular.js and val(value) updates should now play nice.


Minified version:

!function(n,t){"use strict";var r=n.fn.val;n.fn.val=function(n){if(!arguments.length)return r.call(this);var e=r.call(this,n);return t.element(this[0]).triggerHandler("input"),e}}(window.jQuery,window.angular);

Example:

// the function
(function($, ng) {
  'use strict';
  
  var $val = $.fn.val;
  
  $.fn.val = function (value) {
    if (!arguments.length) {
      return $val.call(this);
    }
    
    var result = $val.call(this, value);
    
    ng.element(this[0]).triggerHandler('input');
    
    return result;
    
  }
})(window.jQuery, window.angular);

(function(ng){ 
  ng.module('example', [])
    .controller('ExampleController', function($scope) {
      $scope.output = "output";
      
      $scope.change = function() {
        $scope.output = "" + $scope.input;
      }
    });
})(window.angular);

(function($){  
  $(function() {
    var button = $('#button');
  
    if (button.length)
      console.log('hello, button');
    
    button.click(function() {
      var input = $('#input');
      
      var value = parseInt(input.val());
      value = isNaN(value) ? 0 : value;
      
      input.val(value + 1);
    });
  });
})(window.jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="example" ng-controller="ExampleController">
  <input type="number" id="input" ng-model="input" ng-change="change()" />
  <span>{{output}}</span>
  <button id="button">+</button>
</div>

This answer was copied verbatim from my answer to another similar question.

Community
  • 1
  • 1
dav_i
  • 27,509
  • 17
  • 104
  • 136