18

I have have a directive inside an ng-repeater that should set a scope property. Please see the fiddle here: http://jsfiddle.net/paos/CSbRB/

The problem is that the scope property is given as an attribute value like this:

<button ng-update1="inputdata.title">click me</button>

The directive is supposed to set the scope property inputdata.title to some string. This does not work:

app.directive('ngUpdate1', function() {
    return function(scope, element, attrs) {
        element.bind('click', function() {
            scope.$apply(function() {
                scope[ attrs.ngUpdate1 ] = "Button 1";
            });
        });
    };
});

However, assigning directly works:

scope["inputdata"]["title"] = "Button 1";

Can you please tell me how I can set a scope property with . notation in its name from a directive?

PS: The reason the fiddle is using a repeater is because it makes the directives be in child scopes. When they are in a child scope, you can't write to scope properties that are primitives. That's why I need an object property with "." in the name. See the long explanation here: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Thank you

Community
  • 1
  • 1
user681814
  • 799
  • 1
  • 8
  • 19

2 Answers2

37

$parse will solve your problem.

<button ng-update1="inputdata.title">
app.directive('ngUpdate1', function($parse) {
    return function(scope, element, attrs) {
        var model = $parse(attrs.ngUpdate1);
        console.log(model(scope));  // logs "test"
        element.bind('click', function() {
           model.assign(scope, "Button 1");
           scope.$apply();
        });
    };
});

Fiddle

Whenever a directive does not use an isolate scope and you specify a scope property using an attribute, and you want to modify the value, use $parse.

If you don't need to modify the value, you can use $eval instead:

console.log(scope.$eval(attrs.ngUpdate1));
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Can you do the same thing using ng-model as the html attribute instead of a custom one assuming your html element is a directive and you've got the code you presented here in a link: function? – sonicblis Apr 17 '14 at 17:08
  • @sonicblis, I suggest posting a new question with a fiddle, since you are asking about a very different scenario. The answer will also depend on the type of scope the directive is using. This might help: http://stackoverflow.com/questions/11896732/ngmodel-and-component-with-isolated-scope – Mark Rajcok Apr 18 '14 at 16:35
  • @MarkRajcok, I appreciate it, but I figured it out. [This fiddle](http://jsfiddle.net/sonicblis/99r7v/) shows what I was asking about. – sonicblis Apr 18 '14 at 16:44
2

Not sure what overall objective is but one way is to create 2 attributes, one for the target object and the other for the property of that object:

<button ng-update1  obj="inputdata" prop="title">
app.directive('ngUpdate1', function() {
    return function(scope, element, attrs) {
        element.bind('click', function() {
            scope.$apply(function() {                
                scope[ attrs.obj ][attrs.prop] = "Button 1";               

            });
        });
    };
});

DEMO:http://jsfiddle.net/CSbRB/9/

Alternatively using existing format you could split() value of your current ng-update1 attribute and use result array for object and property in notation

 element.bind('click', function() {
           var target=attrs.ngUpdate1.split('.');
            scope.$apply(function() {                
                scope[ target[0] ][target[1]] = "Button 1";               

            });
        });

DEMO with both approaches: http://jsfiddle.net/CSbRB/10/

One more approach where you create an isolated scope in directive and can pass in the reference to inputdata object and pull property name from attribute(same markup as second version):

app.directive('ngUpdate3', function () {
    return {
        scope: {
           targetObject: '=obj'
        },
        link: function (scope, element, attrs) {
            element.bind('click', function () {  
               scope.$apply(function () {
                    scope.targetObject[attrs.prop]='Button 3';

                });
            });
        }
    }
});

http://jsfiddle.net/CSbRB/11/

charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Instead of two attributes or `split()`, use `$parse` instead (see my answer). Also, with an isolate scope, you can bind to the object property directly if you want/need to: ` – Mark Rajcok Mar 31 '13 at 04:57
  • @MarkRajcok thanks.. was trying to bind to property in isolated scope earlier and not sure why it wasn't working.. docs for $parse are terrible...your comments help. Seems like there should more intuitive approaches than needing to use `$parse` then `assign` – charlietfl Mar 31 '13 at 06:29
  • I had problems binding to a property in an isolated scope in your fiddle too. I think the issue is the older version of Angular (1.0.0) in your fiddle. – Mark Rajcok Apr 01 '13 at 15:29
  • 3 different approaches make my mind open outside the box. Thank you very much for useful explanations. – Thomas.Benz Mar 23 '16 at 13:17