Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update AngularJS model from directive linking function?

What I am trying to achieve is to add extra interface for input fields to be able to increace and decrease numeric value in them by clicking + and - buttons.

(In essence it is what input[type=number] fields have on chrome, but I want this to be cross-broswer compatible and also have full control of presentation accross all browsers).

Code in view:

<input data-ng-model="session.amountChosen" type="text" min="1" class="form-control input-small" data-number-input>

Directive code:

app.directive('numberInput', function() {
return {
    require: 'ngModel',
    scope: true,
    link: function(scope, elm, attrs, ctrl) {

        var currValue = parseInt(scope.$eval(attrs.ngModel)),
            minValue = attrs.min || 0,
            maxValue = attrs.max || Infinity,
            newValue;

        //puts a wrap around the input and adds + and - buttons
        elm.wrap('<div class="number-input-wrap"></div>').parent().append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>');

        //finds the buttons ands binds a click event to them where the model increase/decrease should happen
        elm.parent().find('a').bind('click',function(e){

            if(this.text=='+' && currValue<maxValue) {
                newValue = currValue+1;    
            } else if (this.text=='-' && currValue>minValue) {
                newValue = currValue-1;    
            }

            scope.$apply(function(){
                scope.ngModel = newValue;
            });

            e.preventDefault();
        });


    }
  };

})

This is able to retrieve the current model value via scope.$eval(attrs.ngModel), but fails to set the new value.

Aftermath edit: this is the code that now works (in case you wan't to see the solution for this problem)

app.directive('numberInput', function() {
  return {
    require: 'ngModel',
    scope: true,
    link: function(scope, elm, attrs, ctrl) {

        var minValue = attrs.min || 0,
            maxValue = attrs.max || Infinity;

        elm.wrap('<div class="number-input-wrap"></div>').parent().append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>');
        elm.parent().find('a').bind('click',function(e){

            var currValue = parseInt(scope.$eval(attrs.ngModel)),
                newValue = currValue;

            if(this.text=='+' && currValue<maxValue) {
                newValue = currValue+1;    
            } else if (this.text=='-' && currValue>minValue) {
                newValue = currValue-1;    
            }

            scope.$eval(attrs.ngModel + "=" + newValue);
            scope.$apply();            

            e.preventDefault();
        });
    }
  };
})
like image 245
Harijs Deksnis Avatar asked Aug 15 '13 09:08

Harijs Deksnis


People also ask

What is Link function in AngularJS directive?

Link: The link function deals with linking scope to the DOM. Using Code for Compile. While defining a custom directive we have the option to define a link against which either we can define a function or we have the option to assign an object which will have pre & post function.

Is NG-model an AngularJS directive?

ngModel is a directive which binds input, select and textarea, and stores the required user value in a variable and we can use that variable whenever we require that value.

Which directive do we use to inform AngularJS about the parts controlled by it?

The ngRef attribute tells AngularJS to assign the controller of a component (or a directive) to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM element to the scope. The ngRepeat directive instantiates a template once per item from a collection.

What is the difference between controller and link in directives in AngularJS?

The link option is just a shortcut to setting up a post-link function. controller: The directive controller can be passed to another directive linking/compiling phase. It can be injected into other directices as a mean to use in inter-directive communication.


1 Answers

ngModelController methods should be used instead of $eval() to get and set the ng-model property's value.

parseInt() is not required when evaluating an attribute with a numeric value, because $eval will convert the value to a number. $eval should be used to set variables minValue and maxValue.

There is no need for the directive to create a child scope.

$apply() is not needed because the ngModelController methods ($render() in particular) will automatically update the view. However, as @Harijs notes in a comment below, $apply() is needed if other parts of the app also need to be updated.

app.directive('numberInput', function ($parse) {
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ctrl) {
            var minValue = scope.$eval(attrs.min) || 0,
                maxValue = scope.$eval(attrs.max) || Infinity;
            elm.wrap('<div class="number-input-wrap"></div>').parent()
                .append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>');
            elm.parent().find('a').bind('click', function (e) {
                var currValue = ctrl.$modelValue,
                    newValue;
                if (this.text === '+' && currValue < maxValue) {
                    newValue = currValue + 1;
                } else if (this.text === '-' && currValue > minValue) {
                    newValue = currValue - 1;
                }
                ctrl.$setViewValue(newValue);
                ctrl.$render();
                e.preventDefault();
                scope.$apply(); // needed if other parts of the app need to be updated
            });
        }
    };
});

fiddle

like image 125
Mark Rajcok Avatar answered Oct 22 '22 21:10

Mark Rajcok