6

It seems like I get confused by isolated scopes in directives and hope you can help me out.

I tried to wrap a piece of code (which contains some custom directives) into a new directive to reduce code duplication. Obviously I needed to give some attributes like ng-model into my new directive as a parameter to make the directive reusable. ng-model does not like expressions though (I tried ng-model="{{myVariableWhichContainsDesiredNgModelString}}" at first) and thus I ended up at this article: AngularJS - Create a directive that uses ng-model.

While the accepted answer seems to work for a simple setup, I edited the plunker from the accepted answer to test out if it would work with nested directives as well: (in my app I need to wrap directives from a third party-library which I can not edit) Plunker. In my code each directive seems to generate its own scope and two-way-databinding by using = in the scope definition does not seem to work out as desired.

EDIT: Since it was not clear what i am asking I edited the Plunker above and will rephrase the question: In the Plunker I have three input-fields which are supposed to bind to the same model-value. This works initially, but as soon as I edit the third input-field it generates its own variable in its isolated scope instead of updating the initial value. Obviously the third input field refers to the new variable from that point on. How can I avoid that behaviour and keep the input linked to $scope.model.name?

Observation: removing the isolated-scope-directive from the template makes everything work as expected...

template: '<div><my-input ng-model="myDirectiveVar"></my-input></div>',

instead of

template: '<div><my-isolated-scope-directive><my-input ng-model="myDirectiveVar"></my-input></my-isolated-scope-directive></div>',

Plunker

HTML:

<!-- this binds to the model which i would like all my inputs to bind to.-->
<input ng-model="name">

<!-- Example 1: This seems to generate a new modelvalue in the isolated-scope directive. Can I avoid this without modifying that directive?-->
<my-isolated-scope-directive><my-input ng-model="name"></my-input></my-isolated-scope-directive>

<!-- Example 2: This is what i would like my code to look like in the end: One directive which uses the code-snippet of Example 1 as template and passes some parameters into that template.-->
<my-wrapper-directive my-directive-var="name"></my-wrapper-directive>

Directives:

my-input contains a modified input-field:

app.directive('myInput', function() {
    return {
        restrict: 'E',
        replace: true,
        require: 'ngModel',
        template: '<input class="some">',
        link: function($scope, elem, attr, ctrl) {
            console.debug($scope);
        }
    };
})

my-isolated-scope-directive is a placeholder-directive with its own isolated scope to simulate the behaviour for nested directives:

.directive('myIsolatedScopeDirective', function() {
    return {
        restrict: 'E',
        transclude: true,
        replace: true,
        scope: {
            something: '='
        },
        template: '<div ng-transclude></div>',
        link: function($scope, elem, attr, ctrl) {
            console.debug($scope);
        }
    };
})

my-wrapper-directive encapsulates both previous directives and accepts a parameter which should be used as ng-model value of the input field:

.directive('myWrapperDirective', function() {
    return {
        restrict: 'E',
        transclude: false,
        replace: true,
        scope: {
          myDirectiveVar: '='
        },
        template: '<div><my-isolated-scope-directive><my-input ng-model="myDirectiveVar"></my-input></my-isolated-scope-directive></div>',
        link: function($scope, elem, attr, ctrl) {
            console.debug($scope);
        }
    };
});

Any suggestions and hints on what I am missing are appreciated. Can I maybe somehow link ng-model to a service-instance without making my directive dependant on that service?

7
  • What actually not working? If u mean that updating 2nd and 3rd field does not update 1st - this looks like usual dot issue. Commented Jan 26, 2016 at 15:02
  • Apparently i was simplifying too much in the Plunker. I updated the Plunker here: plnkr.co/edit/0soT4nLkFqob4lHxyJ1Y?p=preview and i hope the issue is clearer now. The 2nd field works as expected, the third field however generates its own scope-entry as soon as you edit it. Commented Jan 27, 2016 at 8:02
  • You are having this issue probably, because you are not using the "." dot. Create an object and use its properies: myobject.valueToBind Commented Jan 27, 2016 at 8:30
  • Hmm, I was too fast and skipped too much... I can see that you are using the . ... Commented Jan 27, 2016 at 8:33
  • It probably still comes down to that: I use <my-input ng-model="myDirectiveVar"> in the wrapper-directive (no dot here) because myDirectiveVar is indeed a local variable which is supposed to hold the reference "model.name". This resolves properly at first, but as soon as i use the input it sets myDirectiveVar to "myNewInput" instead of updating "model.name" to myNewInput. Since ng-model does not allow expressions, how to i pass the reference to model.name in there? Commented Jan 27, 2016 at 8:38

1 Answer 1

2

I wouldn't do it like this as it is old notation using scopes... I would use controllerAs and bindToController

script:

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

app.controller('MainCtrl', function() {
  this.model = { name: 'World' };
  this.name = "Felipe";
});

app.directive('myInput', function() {
  return {
    restrict: 'E',
    replace: true,
    // controllerAs: 'app',
    require: 'ngModel',
    template: '<input class="some">',
    controller: function(){

    }
  };
})
.directive('myIsolatedScopeDirective', function() {
  return {
    restrict: 'E',
    transclude: true,
    controllerAs: 'app1',
    bindToController: {
      something: '='
    },
    template: '<div ng-transclude></div>',
    controller: function(){

    }
  };
})
.directive('myWrapperDirective', function() {
  return {
    restrict: 'E',
    transclude: false,
    controllerAs: 'app2',
    bindToController: {
      myDirectiveVar: '='
    },
    template: '<div><my-isolated-scope-directive>'+
      '<my-input ng-model="app2.myDirectiveVar"></my-input>'+
      '</my-isolated-scope-directive></div>',
    controller: function(){

    }

  };
});

index:

<!doctype html>
<html ng-app="plunker" >
<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <link rel="stylesheet" href="style.css">
  <script>document.write("<base href=\"" + document.location + "\" />");</script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
  <script src="app.js"></script>
</head>
<body ng-controller="MainCtrl as main">
  This scope value 
  <input ng-model="main.model.name">
  <my-isolated-scope-directive>
    <my-input ng-model="main.model.name"></my-input>
  </my-isolated-scope-directive>
  <my-wrapper-directive my-directive-var="main.model.name">
  </my-wrapper-directive>
</body>
</html>

See the plunker: http://plnkr.co/edit/VD0wXO1jivQc3JvfQFTh?p=preview

UPDATE yes, good point, so if you want to use controllerAs, you need angular 1.2 as minimum, for bindToController you need angular 1.3

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks a lot! This actually requires angular 1.4.8 btw, so if anyone runs into trouble when using this, try updating your angular version.
another question to the ControllerAs syntax: Go to your plunker and use the my-wrapper-directive twice with different variables. The my-directive-var of the second directive will overwrite the first. Why is the controller-scope shared and can i make it isolated per directive again?
What I don't understand yet with your answer, is why it didn't work with scopes, but it does with bindToController.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.