26

I need to modify a root scope attribute from within a callback inside a directive. But the directive is in a inner scope created by a switch directive.

HTML

<div ng-app="app" ng-controller='AppController'>
    <p>Selected: {{ selected }}</p>
    <div ng-switch on="selected">
        <div ng-switch-default>
            <p>Item: {{ selected }}</p>
            <custom-tag selected-item="selected" />
        </div>
        <div ng-switch-when="New value">
            <p>Worked</p>
        </div>
    </div>
</div>

JavaScript

angular.module('app', [])    
    .directive("customTag", [function () {
    return {
        restrict: "E",
        replace: true,
        template: "<input type='button' value='Click me' />",

        link: function (scope, element, attrs) {
            element.bind('click', function () {
                scope[attrs.selectedItem] = "New value";
                scope.$apply();
            });
        }
    };
}]);

function AppController($scope) {
    $scope.selected = 'Old value';
}

Fiddle: http://jsfiddle.net/nJ7FQ/

My objective is to be able to display "New value" in the Selected area. How can I accomplish what I am trying to do? What am I doing wrong?

Besides, as I am trying to make a component. Is there a way to do the same but with an isolated scope?

Vivz
  • 6,625
  • 2
  • 17
  • 33
Fernando
  • 2,131
  • 3
  • 27
  • 46

2 Answers2

20

I updated the fiddle, basically had to go to the parent to get the right "selected" variable, also used the isolate scope = to get two way binding between the value passed in and the internal model.

http://jsfiddle.net/nJ7FQ/2/

angular.module('app', [])

    .directive("customTag", [function () {
    return {
        restrict: "E",
        replace: true,
        template: "<input type='button' value='Click me' />",
        scope: {model:'='},

        link: function (scope, element, attrs) {
            element.bind('click', function () {
                scope.model[attrs.selectedItem] = "New value";
                scope.$apply();
            });
        }
    };
}]);

function AppController($scope) {
    $scope.selected = 'Old value';
}

and the HTML

<div ng-app="app" ng-controller='AppController'>
    <p>Selected: {{ selected }}</p>
    <div ng-switch on="selected">
        <div ng-switch-default>
            <p>Item: {{ selected }}</p>
            <custom-tag selected-item="selected" model="$parent" />
        </div>
        <div ng-switch-when="New value">
            <p>Worked</p>
        </div>
    </div>
</div>

Updated the fiddle to use your original reading of the property from the attribute: http://jsfiddle.net/nJ7FQ/4/

shaunhusain
  • 19,630
  • 4
  • 38
  • 51
  • I liked better your first solution (http://jsfiddle.net/nJ7FQ/2/) It seems cleaner somehow. But why should I use $parent.selected when, from what I understand, child scopes inherits all the properties of the parent? – Fernando Jul 10 '13 at 18:02
  • 1
    Well if you change the selected for the one that was inherited it doesn't change the parent scopes selected variable, by going up to the parent and changing the variable on that scope then when the child selected value is read it goes up to the parent. If you change the child scope's selected property it would become a new property on the child scope and no longer inherit. (I know it's confusing) This is Javascript prototypical inheritence if you want to Google it. – shaunhusain Jul 10 '13 at 18:07
  • This does not work. setting `scope:'='` in the directive makes `scope` undefined. – dopatraman Nov 25 '14 at 23:26
  • @dopatraman, yeah having learned more about angular in the last year, I wouldn't agree with my own answer here, that said I'm not sure that the issue you're encountering is related to the problems I would have with this method. If you posted another SO question with your code and problem link it here and I'll take a look. – shaunhusain Nov 26 '14 at 18:51
  • Note that `link` must be used; [this alternative](http://jsfiddle.net/nJ7FQ/429/), exactly the same as shaunhusain's, except for using `controller`, doesn't work. Presumably it's because isolate scope is only set up in time for the `link` function, not the `controller` function. – Nate Anderson Nov 07 '16 at 01:20
15

I improved the jsfiddle a bit:

angular.module('app', [])

    .directive("customTag", ['$parse', function ($parse) {
    return {
        restrict: "E",
        replace: true,
        template: "<input type='button' value='Click me' />",

        link: function (scope, element, attrs) {
            element.bind('click', function () {
                scope.$apply(function () {
                    $parse(attrs.selectedItem).assign(scope.$parent, "New value");
                });
            });
        }
    };
}]);

function AppController($scope) {
    $scope.selected = { 'foo': 'Old value' };
}

http://jsfiddle.net/nJ7FQ/15/

This way, the scope value, you want to change can also be an object property like selected.foo in the example. Also, I removed the scope parameter and told the directive to always use the parent scope. And finally I wrapped the click handler into the $apply callback (see here for example). Better would be, of course, to use ngClick instead of the element.bind().

stofl
  • 2,950
  • 6
  • 35
  • 48