4

PLUNKR example here

I'm using some version of jquery autocomplete as an angularjs direcitve. When the jquery updates the input's value using element.val() angular does no notice the change until after the next digest ( i suppose ).

My first thought was to perform the action on the ng-model post digest using $timeout but as you can see it didn't help.

My second approach was to override the element's val function to trigger an input event but I haven`t managed to make it work.

Try to select a value from the autocomplete list and you'll see that the ng-model above is not updating.

UPDATE

Thanks for the response. I didn't know about the onSelect option.

This is the code based on your recommendations

// clone user provided options 
scope.options = _.extend({}, scope.AutoCompleteOptions());

// Wrap onSelect - Force update before manipulation on ng-model
var fn = _.has(scope.AutoCompleteOptions(), 'onSelect')  ? scope.AutoCompleteOptions().onSelect : _.noop;
scope.options.onSelect = function () { 
  ngModelCtrl.$setViewValue(element.val()); 
  scope.$apply(fn);
};

scope.autocomplete = $(element).autocomplete(scope.options);

This way i maintain the interface of the directive while guarantying that ng-model will be up to date.

Thanks.

4 Answers 4

6

As you already knew, the problem is angular doesn't aware of the update made by jquery plugin. Luckily, you can use the plugin's onSelect callback to update ngModel, like this:

.directive("autoComplete", function() {
    return {
        restrict: "A" , 
        require: 'ngModel', // require ngModel controller
        scope: {
            AutoCompleteOptions : "=autoCompleteOptions", // use '=' instead of '&'
        },
        link: function (scope, element, attrs, ngModelCtrl) {

            // prevent html5/browser auto completion
            attrs.$set('autocomplete','off');

            // add onSelect callback to update ngModel
            scope.AutoCompleteOptions.onSelect = function() {
                scope.$apply(function() {
                    ngModelCtrl.$setViewValue(element.val());
                });
            };

            scope.autocomplete = $(element).autocomplete(scope.AutoCompleteOptions);
        }
    }
});
Sign up to request clarification or add additional context in comments.

Comments

1

The plugin you're using has an onSelect callback, so you can simply modify your autocomplete parameters to include a callback which updates the scope

{
    lookup      : $scope.current,
    width       : 448,
    delimiter   : /,/,
    tabDisabled : true,
    minChars    : 1,
    onSelect: function(v, data) {
        $scope.selected_tags = v.value;
        $scope.$apply();
}

EDIT

To further improve this you can remove the need to use the model name in the callback:

function(v, data) {
    var model = $(this).attr("ng-model");
    $scope[model] = v.value;
    $scope.$apply();
}

Comments

0

Your jQuery plugin is updating the view value of the input, but not the model.

You will have to modify the plugin to have access to your model or scope or you will have to bind an event to update the model from the view value manually. Maybe something like this:

$scope.format_tags = function () { 

  $scope.selected_tags = $('#myInput').val();
  $scope.selected_final = _.omit($scope.selected_tags.split(','),_.isEmpty);

};

Here is an updated version of your code using this.

You could bind an event to the click of the actual suggested tag element for a better effect.

1 Comment

Hey @Nick, In an effort to make this directive DRY and self contained, is there any way i can set ngmodel.$setViewValue(element.val()) from the auto-complete directive on element.val() change ? That would mean i'll have to watch $element.val() which angular currently does not allow - Do you know of a workaround for this ?
0

I don't recommend updating the plugin itself you can change your directive instead to something like this..

app.directive("autoComplete", function() { 
    return {
        restrict : "A" , 
        require  : '^ngModel', 
        scope    : 
        {
            ngModel: '=',
            AutoCompleteOptions : "&autoCompleteOptions"
        },
        link     : function (scope, element, 
                // prevent html5/browser auto 
                attrs.$set('autocomplete','off');
                var options = scope.
                options.onSelect = function(elem) {
                     scope.$apply(function() {
                         scope.ngModel = elem.value;
                     });
                };
            scope.autocomplete = $(element).autocomplete(scope.AutoCompleteOptions());
        }
    }
});

As you've noticed in the scope object initialization of the directive I have selected the ngModel attribute as something to be used by the onSelect event of the AutoComplete Jquery Plugin, where I have updated the model using the scope.$apply() method. Use scope.$apply in a directive so that the scope.$digest() will be invoked and have the model affected directly.

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.