How to get evaluated attributes inside a custom directive

How to get evaluated attributes inside a custom directive

I’m trying to get an evaluated attribute from my custom directive, but I can’t find the right way of doing it.
I’ve created this jsFiddle to elaborate.


myApp.directive(‘myDirective’, function () {
return function (scope, element, attr) {
element.val(“value = “+attr.value);
}
});

What am I missing?

Solutions/Answers:

Solution 1:

Notice: I do update this answer as I find better solutions. I also keep the old answers for future reference as long as they remain related. Latest and best answer comes first.

Better answer:

Directives in angularjs are very powerful, but it takes time to comprehend which processes lie behind them.

While creating directives, angularjs allows you to create an isolated scope with some bindings to the parent scope. These bindings are specified by the attribute you attach the element in DOM and how you define scope property in the directive definition object.

There are 3 types of binding options which you can define in scope and you write those as prefixes related attribute.

angular.module("myApp", []).directive("myDirective", function () {
    return {
        restrict: "A",
        scope: {
            text: "@myText",
            twoWayBind: "=myTwoWayBind",
            oneWayBind: "&myOneWayBind"
        }
    };
}).controller("myController", function ($scope) {
    $scope.foo = {name: "Umur"};
    $scope.bar = "qwe";
});

HTML

<div ng-controller="myController">
    <div my-directive my-text="hello {{ bar }}" my-two-way-bind="foo" my-one-way-bind="bar">
    </div>
</div>

In that case, in the scope of directive (whether it’s in linking function or controller), we can access these properties like this:

/* Directive scope */

in: $scope.text
out: "hello qwe"
// this would automatically update the changes of value in digest
// this is always string as dom attributes values are always strings

in: $scope.twoWayBind
out: {name:"Umur"}
// this would automatically update the changes of value in digest
// changes in this will be reflected in parent scope

// in directive's scope
in: $scope.twoWayBind.name = "John"

//in parent scope
in: $scope.foo.name
out: "John"


in: $scope.oneWayBind() // notice the function call, this binding is read only
out: "qwe"
// any changes here will not reflect in parent, as this only a getter .

“Still OK” Answer:

Since this answer got accepted, but has some issues, I’m going to update it to a better one. Apparently, $parse is a service which does not lie in properties of the current scope, which means it only takes angular expressions and cannot reach scope.
{{,}} expressions are compiled while angularjs initiating which means when we try to access them in our directives postlink method, they are already compiled. ({{1+1}} is 2 in directive already).

This is how you would want to use:

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

myApp.directive('myDirective', function ($parse) {
    return function (scope, element, attr) {
        element.val("value=" + $parse(attr.myDirective)(scope));
    };
});

function MyCtrl($scope) {
    $scope.aaa = 3432;
}​

.

<div ng-controller="MyCtrl">
    <input my-directive="123">
    <input my-directive="1+1">
    <input my-directive="'1+1'">
    <input my-directive="aaa">
</div>​​​​​​​​

One thing you should notice here is that, if you want set the value string, you should wrap it in quotes. (See 3rd input)

Here is the fiddle to play with: http://jsfiddle.net/neuTA/6/

Old Answer:

I’m not removing this for folks who can be misled like me, note that using $eval is perfectly fine the correct way to do it, but $parse has a different behavior, you probably won’t need this to use in most of the cases.

The way to do it is, once again, using scope.$eval. Not only it compiles the angular expression, it has also access to the current scope’s properties.

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

myApp.directive('myDirective', function () {
    return function (scope, element, attr) {
        element.val("value = "+ scope.$eval(attr.value));
    }
});

function MyCtrl($scope) {

}​

What you are missing was $eval.

http://docs.angularjs.org/api/ng.$rootScope.Scope#$eval

Executes the expression on the current scope returning the result. Any exceptions in the expression are propagated (uncaught). This is useful when evaluating angular expressions.

Solution 2:

For an attribute value that needs to be interpolated in a directive that is not using an isolated scope, e.g.,

<input my-directive value="{{1+1}}">

use Attributes’ method $observe:

myApp.directive('myDirective', function () {
  return function (scope, element, attr) {
    attr.$observe('value', function(actual_value) {
      element.val("value = "+ actual_value);
    })
 }
});

From the directive page,

observing interpolated attributes: Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it’s also the only way to easily get the actual value because during the linking phase the interpolation hasn’t been evaluated yet and so the value is at this time set to undefined.

If the attribute value is just a constant, e.g.,

<input my-directive value="123">

you can use $eval if the value is a number or boolean, and you want the correct type:

return function (scope, element, attr) {
   var number = scope.$eval(attr.value);
   console.log(number, number + 1);
});

If the attribute value is a string constant, or you want the value to be string type in your directive, you can access it directly:

return function (scope, element, attr) {
   var str = attr.value;
   console.log(str, str + " more");
});

In your case, however, since you want to support interpolated values and constants, use $observe.

Solution 3:

The other answers here are very much correct, and valuable. But sometimes you just want simple: to get a plain old parsed value at directive instantiation, without needing updates, and without messing with isolate scope. For instance, it can be handy to provide a declarative payload into your directive as an array or hash-object in the form:

my-directive-name="['string1', 'string2']"

In that case, you can cut to the chase and just use a nice basic angular.$eval(attr.attrName).

element.val("value = "+angular.$eval(attr.value));

Working Fiddle.

Solution 4:

For the same solution I was looking for Angularjs directive with ng-Model.

Here is the code that resolve the problem.

    myApp.directive('zipcodeformatter', function () {
    return {
        restrict: 'A', // only activate on element attribute
        require: '?ngModel', // get a hold of NgModelController
        link: function (scope, element, attrs, ngModel) {

            scope.$watch(attrs.ngModel, function (v) {
                if (v) {
                    console.log('value changed, new value is: ' + v + ' ' + v.length);
                    if (v.length > 5) {
                        var newzip = v.replace("-", '');
                        var str = newzip.substring(0, 5) + '-' + newzip.substring(5, newzip.length);
                        element.val(str);

                    } else {
                        element.val(v);
                    }

                }

            });

        }
    };
});

HTML DOM

<input maxlength="10" zipcodeformatter onkeypress="return isNumberKey(event)" placeholder="Zipcode" type="text" ng-readonly="!checked" name="zipcode" id="postal_code" class="form-control input-sm" ng-model="patient.shippingZipcode" required ng-required="true">

My Result is:

92108-2223

Solution 5:

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

myApp .directive('myDirective', function ($timeout) {
    return function (scope, element, attr) {
        $timeout(function(){
            element.val("value = "+attr.value);
        });

    }
});

function MyCtrl($scope) {

}

Use $timeout because directive call after dom load so your changes doesn`’t apply

Solution 6:

Lot of good answers here, but sometimes you just want’s a plain, easy, old, jQuery-ish, solution.

My solution does not handle changes to the DOM. If the attribute might change, do not use my method!

In my case I had an element, and needed to retrieve the value of an attribute (rel).

Template:

<div ng-repeat="elm in array">
    <button class="poi-lines-show" rel="li-{{$index}}">button-text</button>
</div>

In my directive:

var buttons = element[0].querySelectorAll('button');
for (var i=0; i<buttons.length; i++) {
    var target = angular.element(buttons[i]);
    console.log(target.attr('rel')); // Outputs 'li-0', 'li-1', 'li-2' etc 
}