Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular js access `ctrl.$modelView` in main flow of directive

Updated with three methods that work, and the original one that does not

I have made an angular js directive, and I am trying to access the ctrl.$modelValue. It does not work in the main flow.

I have three potential solutions, all of which have drawbacks.

Method 1 does not work as I would hope, and I can't find any other property available in the directive directly accessible in this way.

Method 2 works because it waits until the current flow is complete, and then executes in the next moment. This happens to be after the angular js lifecycle is complete, and the controller seems to be hooked up to the model at this point. This does not seem ideal to me, as it is waiting for all execution to finish. If it is possible, I would prefer to run my code as soon as the controller is linked to the model, and not after all the code in current flow completes.

Method 3 works well, accessing the model from the $scope, and determining what the model is from the string representation accessed on the attrs object. The drawback is that this method uses eval in order to get hold of the addressed value - and as we all know, eval is evil.

Method 4 works, but it seems like an overly complex way to access a simple property. I can't believe that there is not a simpler way than the string manipulation, and while loop. I am not confident that the function for accessing properties is 100% robust. At least I might like to change it to use a for loop.

Which method should I use, or is there a 5th method that has no drawbacks?

DEMO: http://jsfiddle.net/billymoon/VE9dX/9/

HTML:

<div ng-app="myApp">
    <div ng-controller="inControl">
        I like to drink {{drink.type}}<br>
        <input my-dir ng-model="drink.type"></input>
    </div>
</div>

Javascript:

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

app.controller('inControl', function($scope) {
    $scope.drink = {type:'water'};
});

app.directive('myDir', function(){
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function($scope, element, attrs, ctrl) {

            // Method 1
            // logs NaN
            console.log({"method-1": ctrl.$modelValue});

            // Method 2
            // on next tick it is ok
            setTimeout(function(){
                console.log({"method-2": ctrl.$modelValue});
            },0);

            // Method 3
            // using eval to access model on scope is ok
            // eval is necessary in case model is chained
            // like `drink.type`
            console.log({"method-3": eval("$scope."+attrs.ngModel)});

            // Method 4
            // using complex loop to access model on scope
            // which does same thing as eval method, without eval
            var getProperty = function(obj, prop) {
                var parts = prop.split('.'),
                    last = parts.pop(),
                    l = parts.length,
                    i = 1,
                    current = parts[0];
                while((obj = obj[current]) && i < l) {
                    current = parts[i];
                    i++;
                }
                if(obj) {
                    return obj[last];
                }
            }
            console.log({"method-4": getProperty($scope,attrs.ngModel)});

        }
    };
});
like image 909
Billy Moon Avatar asked Jun 08 '26 12:06

Billy Moon


1 Answers

There are quite a few alternatives, some better than others depending on your requirements, e.g. should you be notified if the view value changes, or the model value, or are you happy with the initial value.

Just to know the initial value you can use either of the following:

console.log('$eval ' + $scope.$eval(attrs.ngModel));

console.log('$parse ' + $parse(attrs.ngModel)($scope));

Both $eval and $parse end result is the same, however $eval sits off $scope, where $parse is an Angular service which converts an expression into a function. The returned $parse function can then be invoked and passed a context (usually scope) in order to retrieve the expression's value. In addition, if the expression is assignable the returned $parse function will have an assign property. The assign property is a function that can be used to change the expression's value on the given context. See $parse docs.

If you need to be informed when the model value changes, you could use $watch, however there are better ways when dealing with ngModel. If you need to track changes to the the model value when the change occurs on the model itself, i.e. inside your code, you can use modelCtrl.$formatters:

    ctrl.$formatters.push(function(value){
        console.log('Formatter ' + value);
    });

Note that $formatters are only called when the model value changes from within your code, and NOT when the model changes from user input. You can also use $formatters to alter the model view value, e.g. convert the display text to uppercase without changing the underlying model value.

When you need to be informed of model value changes that occurs from user input, you can use either modelCtrl.$parsers or modelCtrl.$viewChangeListeners. They are called whenever user input changes the underlying model value:

    ctrl.$viewChangeListeners.push(function(){
        console.log('$viewChangeListener ' + ctrl.$modelValue, arguments);
    });

    ctrl.$parsers.push(function(value){
        console.log('$parsers ' + value, arguments);
        return value;
    });

$parsers allows you to change the value from user input to model if you need to, where $viewChangeListeners just lets you know when the input value changed.

To sum up, if you only need the initial value, use either $eval or $parse, if you need to know when the value changes you need a combination of $formatters and $parsers/$viewChangeListeners.

The following fiddle shows all these and more options, based on your original fiddle: http://jsfiddle.net/VE9dX/6/

like image 197
Beyers Avatar answered Jun 10 '26 02:06

Beyers



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!