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?

3
  • 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). Commented Jul 1, 2016 at 8:00
  • 3
    very important question Commented Sep 5, 2016 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. Commented Jul 13, 2017 at 17:26

10 Answers 10

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

Sign up to request clarification or add additional context in comments.

7 Comments

I made this like you say: fiddle.jshell.net/AladdinMhaimeed/agvTz/8 but it does not work
See this 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 ;)
then what should I do to make good angular practice? separate DOM manipulation into a directive?
Yes, here an example fiddle.jshell.net/agvTz/39 the directive can be used as many times you want with a simple datepicker="scopeKeyThatYouWant" in the input
Just call.. $scope.$digest() to settle. will it slow down ?
|
31

Just use;

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

6 Comments

I used trigger('input') with a datepicker in its onSelect event. It updates the value correctly.
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.
After hours of searching, this was the one that worked! Thank you!
Alhamdulillah, perfect, its working for me. thanks to save my hours
True. I was using $('#selectedDueDate').val(dateText).trigger('change'); which was not working for me. .trigger('input'); works like magic
|
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.

4 Comments

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!
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
Works well for me! I wonder why in the heck it doesn't work the same on the bare scope.
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. stackoverflow.com/questions/14527377/…. I referenced this plunk I made in that post to illustrate plnkr.co/edit/WkCT6aYao3qCmzJ8t5xg?p=preview.
5

Try this

var selectedDueDateField = document.getElementById("selectedDueDate");
var element = angular.element(selectedDueDateField);
element.val('new value here');
element.triggerHandler('input');

1 Comment

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.
3

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

$scope.$apply()

Comments

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.

Comments

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.
      });

Comments

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>

Comments

1

Just use:

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

instead of:

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

Comments

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.