164

I have this simple scenario:

Input element which value is changed by jQuery's val() method.

I am trying to update the angular model with the value that jQuery set. I tried to write a simple directive, but it's not doing what I want.

Here's the directive:

var myApp = angular.module('myApp', []);
myApp.directive('testChange', function() {
    return function(scope, element, attrs) {        
        element.bind('change', function() {
            console.log('value changed');
        })
    }
})

this is the jQuery part:

$(function(){
    $('button').click(function(){
        $('input').val('xxx');
    })
})

and html:

<div ng-app="myApp">
    <div ng-controller="MyCtrl">
        <input test-change ng-model="foo" />
        <span>{{foo}}</span>
    </div>
</div>

<button>clickme</button>

Here is the fiddle with my try:
http://jsfiddle.net/U3pVM/743/

Can someone please point me in the right direction?

Philip Kirkbride
  • 21,381
  • 38
  • 125
  • 225
Michal J.
  • 1,765
  • 3
  • 14
  • 11
  • 2
    Mix AngularJS and jQuery, outside the directives, is often a bad idea. Is jQuery mandatory for what you want to do ? – Blackhole Jun 14 '13 at 13:49
  • Why don't you set the click event with angular to a function in the controller to change the value of the model? – Jimmy Kane Jun 14 '13 at 13:56
  • @Blackhole, I am using jquery upload plugin that do its work and puts the path to uploaded file into the hidden input. All I need is to have access to that path in angular model. So I thought I'll set it as a value of hidden input and after it is changed update the angular model. – Michal J. Jun 14 '13 at 14:00
  • I do not see Jquery call in your fiddle... Can you check and point where you change value with JQ? – Valentyn Shybanov Jun 14 '13 at 14:06
  • Your fiddle links to a Todo sample app, which doesn't relate to this question. – Stewie Jun 14 '13 at 14:12
  • sorry, I updated the fiddle address. – Michal J. Jun 14 '13 at 14:13
  • Try adding something like this inside your click handler (along with the "value changed" console log): scope.foo = element.val(); scope.$apply(); What you want to do is to propagate the change back into your Angular model. Because this is initiated outside of an Angular callback, the "$apply()" call is what will tell Angular to recognize the change and update its models. – blaster Jun 14 '13 at 14:56

12 Answers12

329

ngModel listens for "input" event, so to "fix" your code you'd need to trigger that event after setting the value:

$('button').click(function(){
    var input = $('input');
    input.val('xxx');
    input.trigger('input'); // Use for Chrome/Firefox/Edge
    input.trigger('change'); // Use for Chrome/Firefox/Edge + IE11
});

For the explanation of this particular behaviour check out this answer that I gave a while ago: "How does AngularJS internally catch events like 'onclick', 'onchange'?"


But unfortunately, this is not the only problem you have. As pointed out with other post comments, your jQuery-centric approach is plain wrong. For more info take a look at this post: How do I “think in AngularJS” if I have a jQuery background?).

Community
  • 1
  • 1
Stewie
  • 60,366
  • 20
  • 146
  • 113
  • 5
    Cool! With one exception: why it doesn't work on hidden inputs? When I switch my input type to text, it works as expected. – Karol Sep 25 '13 at 05:44
  • 8
    This solved a major issue when using jquery calendar plugins. By hacking the plugin to trigger('input') after input.val() the ng-change event fires after user selects calendar date. Thanks! – Drone Brain Oct 03 '13 at 20:07
  • 3
    Works perfect on hidden input: element.triggerHandler("change"); – falko Dec 02 '13 at 20:07
  • 2
    this does not work for me. $("#Distance").val(data); $("#Distance").trigger('input'); – Stas Svishov Mar 02 '14 at 22:50
  • @StasSvishov Provide a plunker example, perhaps I can help. – Stewie Mar 03 '14 at 08:35
  • If using for a checkbox (like ``), you can say `$scope["SomeServerGeneratedID"] = false` (or true) to control it. – Arve Systad Sep 23 '14 at 11:04
  • Sometimes you just need to trigger 1 function from a jquery library, there is no error in this approach I think, no need to do fancy stuff with definitions & etc. – Herr Nov 10 '14 at 12:10
  • 10
    With Angular 1.4.3, `input` event doesn't work on IE including IE11. [`input` event works only when `$sniff.hasEvent('input')` is true](https://github.com/angular/angular.js/blob/v1.4.3/src/ng/directive/input.js#L1094) but [`$sniff.hasEvent('input')` is false even on IE11!](https://github.com/angular/angular.js/blob/v1.4.3/src/ng/sniffer.js#L72) We can use [`change` event](https://github.com/angular/angular.js/blob/v1.4.3/src/ng/directive/input.js#L1128) instead. – Shuhei Kagawa Aug 13 '15 at 12:42
  • Wow quite neat , works for my autocomplete , which had lot of other issues when using directive – Rameez Ahmed Sayad Sep 29 '15 at 09:43
  • Really a great solution. Thanks a lot @Stewie. You made my day. :) – Rav's Patel Feb 29 '16 at 11:51
  • @Karol : Alternative you can use `style="display:none"` instead of `type="hidden"`. – zuluk Jul 04 '16 at 21:45
  • thaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaank you. you are a heroooooooooo – Accountant م Nov 04 '16 at 02:47
  • @ShuheiKagawa THANK YOU SO MUCH – Camilo Terevinto Mar 29 '17 at 15:29
61

Hope this is useful for someone.

I was unable to get the jQuery('#myInputElement').trigger('input') event to be picked up my angular app.

I was however, able to get angular.element(jQuery('#myInputElement')).triggerHandler('input') to be picked up.

mauris
  • 42,982
  • 15
  • 99
  • 131
ginman
  • 1,315
  • 10
  • 20
30

The accepted answer which was triggering input event with jQuery didn't work for me. Creating an event and dispatching with native JavaScript did the trick.

$("input")[0].dispatchEvent(new Event("input", { bubbles: true }));
Martins Balodis
  • 1,999
  • 1
  • 17
  • 18
22

I don't think jQuery is required here.

You can use $watch and ng-click instead

<div ng-app="myApp">
  <div ng-controller="MyCtrl">
    <input test-change ng-model="foo" />
    <span>{{foo}}</span>

    <button ng-click=" foo= 'xxx' ">click me</button>
    <!-- this changes foo value, you can also call a function from your controller -->
  </div>
</div>

In your controller :

$scope.$watch('foo', function(newValue, oldValue) {
  console.log(newValue);
  console.log(oldValue);
});
Utopik
  • 3,783
  • 1
  • 21
  • 24
  • 1
    Thank you Utopik, you're right, I shouldn't mix jquery with angular when there is a way how to do the job using angular. – Michal J. Jun 14 '13 at 15:06
18

You have to use the following code in order to update the scope of the specific input model as follows

$('button').on('click', function(){
    var newVal = $(this).data('val');
    $('select').val(newVal).change();

    var scope = angular.element($("select")).scope();
    scope.$apply(function(){
        scope.selectValue = newVal;
    });
});
jayM
  • 602
  • 6
  • 16
  • 1
    `var scope = angular.element($("select")).scope(); scope.$apply(function(){ scope.selectValue = newVal; });` that portion helped me a lot. My issue was updating an angular model after a jquery ajax call in the success function. It was slow with $http because there was duplicate change event listener for the element that was doing something else so i merged it into jQuery. – Emmanuel Mahuni Feb 17 '16 at 19:10
  • 1
    The use of element.scope() is a bad idea, because it works only with logging enabled. If you on a production site you should disable logging on your config with `$compileProvider.debugInfoEnabled(false);` or `$logProvider.debugEnabled(false);` See https://code.angularjs.org/1.4.0/docs/guide/production for more info. – RWAM Apr 01 '16 at 10:02
  • I tried this method for hidden variable and it worked. var li_cuboid_id = $('#li_cuboid_id'); li_cuboid_id.val(json.cuboidId).change(); var scope = angular.element($("#li_cuboid_id")).scope(); scope.$apply(function(){ scope.cuboidId = json.cuboidId; }); the hidden variable is defined as: – Rahul Varadkar Feb 15 '18 at 10:47
7

I made modifications on only controller initialization by adding listener on action button:

$(document).on('click', '#action-button', function () {
        $timeout(function () {
             angular.element($('#input')).triggerHandler('input');
        });
});

Other solutions did not work in my case.

Ebru Yener
  • 674
  • 6
  • 17
4

I know it's a bit late to answer here but maybe I may save some once's day.

I have been dealing with the same problem. A model will not populate once you update the value of input from jQuery. I tried using trigger events but no result.

Here is what I did that may save your day.

Declare a variable within your script tag in HTML.

Like:

<script>
 var inputValue="";

// update that variable using your jQuery function with appropriate value, you want...

</script>

Once you did that by using below service of angular.

$window

Now below getData function called from the same controller scope will give you the value you want.

var myApp = angular.module('myApp', []);

app.controller('imageManagerCtrl',['$scope','$window',function($scope,$window) {

$scope.getData = function () {
    console.log("Window value " + $window.inputValue);
}}]);
Khawaja Asim
  • 1,327
  • 18
  • 38
3

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>
dav_i
  • 27,509
  • 17
  • 104
  • 136
  • Sorry to grave dig this up, but any idea how this would/could work with checkbox or selects since those use .prop instead of .val? This is working perfectly for input .val changes at the very least so thank you! – Austin Feb 14 '20 at 14:58
  • @Austin I'm afraid I don't know. This is a 5 year old question about two technologies that are widely considered outdated, as such I haven't kept the knowledge in my head. Best of luck getting the solution you need though. – dav_i Feb 19 '20 at 13:08
2

If you are using IE, you have to use: input.trigger("change");

William
  • 141
  • 5
2

add .change() after setting the value.

example:('id').val.('value').change();

also don't forget to add onchange or ng-change tag in html

currarpickt
  • 2,290
  • 4
  • 24
  • 39
Azeez
  • 171
  • 2
  • 9
0

I did this to be able to update the value of ngModel from the outside with Vanilla/jQuery:

function getScope(fieldElement) {
    var $scope = angular.element(fieldElement).scope();
    var nameScope;
    var name = fieldElement.getAttribute('name');
    if($scope) {
        if($scope.form) {
            nameScope = $scope.form[name];
        } else if($scope[name]) {
            nameScope = $scope[name];
        }
    }
    return nameScope;
}

function setScopeValue(fieldElement, newValue) {
    var $scope = getScope(fieldElement);
    if($scope) {
        $scope.$setViewValue(newValue);
        $scope.$validate();
        $scope.$render();
    }
}

setScopeValue(document.getElementById("fieldId"), "new value");
anlijudavid
  • 509
  • 6
  • 12
0

Not what OP asked, but for any soul that might be as well writing an userscript that goes through input fields and fills the required details. Nothing (fully) worked for me, but finally managed to get it done this way:

var el = $('#sp_formfield_fw_ip');
el.val("some value");
angular.element(el).triggerHandler('focus');
angular.element(el).triggerHandler('input');
angular.element(el).triggerHandler('change');
angular.element(el).triggerHandler('blur');

Open developer tools, and inspect input field for added events. There I found all of them (in my case): focus, input, change and blur.

Erikas
  • 1,006
  • 11
  • 22