Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - Wrapping directives

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?

like image 748
H W Avatar asked Jan 26 '16 13:01

H W


People also ask

What are the directives in AngularJS?

AngularJS directives are extended HTML attributes with the prefix ng- . The ng-app directive initializes an AngularJS application. The ng-init directive initializes application data. The ng-model directive binds the value of HTML controls (input, select, textarea) to application data.

What are the types of directives in Angular?

The three types of directives in Angular are attribute directives, structural directives, and components.

What is custom directive in AngularJS?

Custom directives are used in AngularJS to extend the functionality of HTML. Custom directives are defined using "directive" function. A custom directive simply replaces the element for which it is activated.

What is the difference between controller and link in directives?

In my opinion, we use controller when we need to share or reuse $scope data or we want directive interactive with each other. link is used for DOM manipulation tasks. This article compare the difference of controller and link in AngularJS directive with example, you could refer to it.


1 Answers

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

like image 114
eesdil Avatar answered Oct 03 '22 23:10

eesdil