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?

Community
  • 1
  • 1
H W
  • 2,556
  • 3
  • 21
  • 45
  • What actually not working? If u mean that updating 2nd and 3rd field does not update 1st - this looks like usual dot issue. – Petr Averyanov Jan 26 '16 at 15:02
  • Apparently i was simplifying too much in the Plunker. I updated the Plunker here: http://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. – H W Jan 27 '16 at 08:02
  • You are having this issue probably, because you are not using the "." dot. Create an object and use its properies: myobject.valueToBind – eesdil Jan 27 '16 at 08:30
  • Hmm, I was too fast and skipped too much... I can see that you are using the . ... – eesdil Jan 27 '16 at 08:33
  • It probably still comes down to that: I use `` 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? – H W Jan 27 '16 at 08:38
  • you know that you are using very old angular , right? – eesdil Jan 27 '16 at 08:48
  • i just copied the Plunker from the Stackoverflow article linked above. In my angular 1.4 project i have the exact same issue. – H W Jan 27 '16 at 08:55

1 Answers1

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

eesdil
  • 1,931
  • 3
  • 16
  • 25
  • 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. – H W Jan 27 '16 at 10:22
  • 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? – H W Jan 27 '16 at 11:43
  • What I don't understand yet with your answer, is why it didn't work with scopes, but it does with bindToController. – Eduardo Yáñez Parareda May 17 '18 at 13:15