364

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.

<div ng-controller="MyCtrl">
    <input my-directive value="123">
    <input my-directive value="{{1+1}}">
</div>

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

What am I missing?

ndequeker
  • 7,336
  • 6
  • 54
  • 89
Shlomi Schwartz
  • 11,238
  • 25
  • 93
  • 155
  • You can follow below link for better understanding about directives. https://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/ – Prasanna Sep 05 '17 at 17:16

5 Answers5

575

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.

Community
  • 1
  • 1
Umur Kontacı
  • 35,099
  • 7
  • 69
  • 94
  • Thanks for the reply, however this is not the solution. I've updated the fiddle with your code. http://jsfiddle.net/neuTA/3/ – Shlomi Schwartz Sep 12 '12 at 07:19
  • In Chrome I get this error when trying to use scope.$parse: Object # has no method '$parse'. If I inject the $parse service -- function($parse) { return function (scope ... -- then try: "value = " + $parse(attr.value) -- that doesn't seem to work for me either. – Mark Rajcok Sep 12 '12 at 20:11
  • @Mark you are right, strange it works in the fiddle example (http://jsfiddle.net/neuTA/4/) but not in the code I have ... angular versions? – Shlomi Schwartz Sep 13 '12 at 10:41
  • @fastreload - do you have another idea? – Shlomi Schwartz Sep 13 '12 at 10:42
  • I found out that a lot had changed since I started to work with "NG". so I guess this issue was resolved in the current version. I'll accept the answer and upgrade my angular version. thanks again – Shlomi Schwartz Sep 13 '12 at 10:54
  • @Shlomi, in the fiddle with scope.$parse(), http://jsfiddle.net/neuTA/4/, I still get Chrome errors showing up in the console (hit F12 to see them). So, at least on my PC, although it appears to somewhat work (123 and 2 appear in the input fields), I don't think the linking function is executing -- which explains why I don't see, say, "value = 123" in the first input field. Rather, I only see "123". – Mark Rajcok Sep 13 '12 at 17:28
  • 2
    In the "Better answer" section, `$scope.text` will be undefined in the linking function. The way the answer is currently worded, it sounds like it would not be undefined. You have to use $observe() (or $watch() will actually work here too) to asynchronously see the interpolated value. See my answer and also http://stackoverflow.com/questions/14876112/difference-between-observers-and-watchers/14907826#14907826 – Mark Rajcok Feb 19 '13 at 16:27
  • Actually, it would be undefined in the initial state of the linking process, after that it would reflect the value it is supposed to reflect. It did not go into details of $watch and $observer for the sake of simplicity in the answer as its focus is on somewhere else; but yes, in order to follow value in the correct way, one is supposed to use either `$watch` or `$observe` depending on the circumstances. – Umur Kontacı Feb 28 '13 at 06:49
  • Keep in mind that only **one** directive per element can request scope, so it may be best to avoid isolate scope except when absolutely necessary, in order to maximize the interoperability and reusability of your directives. – rintaun May 24 '13 at 00:20
  • That depends on what do you want to do with that directive, it might be better to use isolate scope if you want to have a self-contained, pluggable directive. – Umur Kontacı May 24 '13 at 05:39
  • 1
    In *"Still OK" Answer* it seems the `$parse` service is injected and then never used. Am I missing something? – superjos Nov 05 '13 at 22:13
  • Can you please provide a working example on jsfiddle, codepen, or plunker? Thanks! (For you latest example.) – trusktr Apr 04 '14 at 23:59
  • The "Still OK" solution is indeed wrong: the example doesn't event use $parse, and the jsfiddle is broken too. $parse(expression) returns a function, which you need to call with a context and another optional values. The usual use case is $parse(expression)(scope). – floribon Aug 16 '14 at 18:52
  • The Angular team should add this to the 'directive' documentation. Great job! – Robin van Baalen Aug 28 '14 at 16:14
  • "Still OK" answer seems the only solution in case we need to evaluate variable parameters in case we can't use multiple isolated scope. As I know if we have multiple directive on the same element we __can't__ have multiple isolated scope, so this appears to be the only solution – Leonardo Oct 14 '14 at 10:51
  • For the "Still OK" why do you talk about `scope.$eval` and then never use it? Am I missing something implicit here? – Leonardo Oct 14 '14 at 10:56
  • @UmurKontacı, is there a suggestion for using directive type 'A' to pass value directly through it? Or there is no point and better way is to use separate parameter like you suggest in your **Better answer**? – Eugene Nov 24 '14 at 09:42
  • If you mean by `restrict` by directive type, the only thing that changes if from where you can define the directive. `A` stands for attribute so you can only init a directive like `
    `, if it was `E` for element, you could init like ``.
    – Umur Kontacı Nov 24 '14 at 11:33
  • I've seen varying answers to similar questions - this is due, in part, to differences between the current vs. previous angular versions - this video helped solidify my understanding: http://vimeo.com/95961018 – Jordan Dec 16 '14 at 14:50
  • The problem with "Better answer" is that in some cases You won't be able to create additional isolate scopes for that element if one already exists – Vytautas Butkus Jan 24 '15 at 10:45
  • Is the @ / & / = thing documented anywhere? – Gavin Haynes Oct 31 '16 at 18:23
  • Your "Better Answer" works only when your ATTRIBUTE restricted directive is used directly on a HTML element, once two different directives can not request an isolated scope on the same element, you wont be able to use this on a ELEMENT restricted directive. Which makes this pointless in any medium-to-major applications. – Guilherme Ferreira Dec 24 '16 at 19:19
83

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.

Mark Rajcok
  • 348,511
  • 112
  • 482
  • 482
4

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.

XML
  • 18,278
  • 7
  • 60
  • 64
  • I don't know if you used an old angular version or what not, but all your code samples are either invalid javascript(my-directive-name=) or invalid angular (angular.$eval doesn't exist), so -1 – BiAiB May 13 '15 at 13:58
  • Ummm... given that this post is more than a year old, it wouldn't be at all surprising if something were since deprecated. However, a 10-second Google search would find you plenty of material on $eval, including [right here at SO](http://stackoverflow.com/questions/15671471/angular-js-how-does-eval-work-and-why-is-it-different-from-vanilla-eval). And the other example you cite is an invocation in HTML, not Javascript. – XML May 15 '15 at 09:23
  • $scope.$eval(attr.val) works in angular 1.4. Requires $scope to be injected into the directive link function. – Martin Connell Jul 09 '16 at 14:07
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
Satish Singh
  • 1,390
  • 2
  • 14
  • 25
2
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

user1693371
  • 95
  • 1
  • 3