Lilian A. Moraru
Lilian A. Moraru

Reputation: 1066

AngularJS - the scope is reset in the linking function, what am I doing wrong?

I am a beginner with AngularJS, I've read on compiling and linking but I still can't manage to understand what's happening.

I have this static code(The class attributes are from Bootstrap):

<div ng-controller='AppCtrl'>
...
   <span class='input-group-addon'>{{typeOfCurrency}}</span>
...
   <ul class='nav nav-pills nav-stacked'>
      <li><a href='#', ng-click="typeOfCurrency = 'Bitcoin'">Bitcoin</a></li>
      <li><a href='#', ng-click="typeOfCurrency = 'Litecoin'">Litecoin</a></li>

When I click on a link, as expected, it sets typeOfCurrency in the <span></span> to the one in the link.

Now I have another list that is created dynamically with ngRepeat and I want it to do the same there:

...
   <span class='input-group-addon'>{{typeOfValue}}</span>
...
   <li ng-animate="'animate'" ng-repeat='currency in currencies | filter:search | limitTo:5'>
       <link-button></link-button>

Then I have a directive declared with a link function:

app.directive('linkButton', function () {
    return {
        scope: true,
        restrict: "E",
        replace: true,
        template: "<a href=''>{{currency}}</a>",
        link: function (scope, elem, attrs) {
            elem.bind('click', function() {
                alert(scope.typeOfValue);
                scope.typeOfValue = scope.currency;
                alert(scope.typeOfValue);
            });
        }
    }
});

As you can see, I've put 2 alerts there. Currencies contain something like ["USD", "RON", "ANG", ..., "GBP"]. It creates a list of all the currencies on which I can click.

When I click on an item sadly it doesn't want to change "typeOfValue", I've put alerts so first one says "USD" because "typeOfValue" is set by default to be USD, than scope.typeOfValue = scope.currency; is executed and the second alert comes and says that scope.typeOfValue is now the clicked currency, for example "RON". But when I click again on an element it tells me again "USD", than the other currency(example "GBP")... It doesn't want to change the text in <span></span> nor does it seems to save the new content of typeOfValue.

What am I doing wrong? How can I make this work?

Upvotes: 2

Views: 759

Answers (1)

Michael Kang
Michael Kang

Reputation: 52837

Demo Plunker Here

The problem is related to how scope inheritance works when resolving bindings. You've created a child scope for your directive (scope=true in your directive) which means that scope variables will be resolved using prototypical scope inheritance. If you don't know what this means, the concept is simple: if the scope variable cannot be found on the current scope, it will look for the binding in the parent scope; if it is not in the parent scope, it will look for the binding in the grand parent's scope, etc, until it either finds the scope variable or it doesn't and stops at $rootScope.

The way that scope inheritance works is that if you set a scope variable using a primitive - like you've done here:

scope.typeOfValue = scope.currency;

The rules say that angular will create a shadow-copy of the variable on the child scope, with the same name as the parent scope variable. This effectively breaks the model-binding because there are now two separate models, each on a different scope.

To resolve the issue, instead of scope.typeOfValue, use scope.obj.typeOfValue, and initialize obj in your parent controller:

 scope.obj = { typeOfValue: {} };

HTML:

<ul class='nav nav-pills nav-stacked'>
    <li><a href='#', ng-click="obj.typeOfCurrency = 'Bitcoin'">Bitcoin</a></li>
    <li><a href='#', ng-click="obj.typeOfCurrency = 'Litecoin'">Litecoin</a></li>

By introducing a '.' in the model, angular will lookup the models (to the left of the dot) using scope inheritance. Once it is found in the parent scope, it will assign the 'typeOfValue' property which is just an attribute on the model. This preserves the binding so that when typeOfValue changes in child scope, it updates the same model which is on the parent scope (and vice versa).

[EDIT]

The directive needs to use ng-click (not click) because click will not trigger a digest cycle. Here is the updated directive:

app.directive('linkButton', function () {
    return {
        scope: true,
        restrict: "E",
        replace: true,
        template: "<a href='#' ng-click='setCurrency(currency)'>{{currency}}</a>",
        link: function (scope, elem, attrs) {
            scope.setCurrency = function(currency) {
                //alert(scope.typeOfValue);
                scope.obj.typeOfValue = scope.currency;
                //alert(scope.typeOfValue);
            };
        }
    }
});

Upvotes: 1

Related Questions