How to access parent scope from within a custom directive *with own scope* in AngularJS?

后端 未结 6 607
终归单人心
终归单人心 2020-11-22 13:50

I\'m looking for any manner of accessing the \"parent\" scope within a directive. Any combination of scope, transclude, require, passing in variables (or the scope itself)

相关标签:
6条回答
  • 2020-11-22 14:02

    See What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

    To summarize: the way a directive accesses its parent ($parent) scope depends on the type of scope the directive creates:

    1. default (scope: false) - the directive does not create a new scope, so there is no inheritance here. The directive's scope is the same scope as the parent/container. In the link function, use the first parameter (typically scope).

    2. scope: true - the directive creates a new child scope that prototypically inherits from the parent scope. Properties that are defined on the parent scope are available to the directive scope (because of prototypal inheritance). Just beware of writing to a primitive scope property -- that will create a new property on the directive scope (that hides/shadows the parent scope property of the same name).

    3. scope: { ... } - the directive creates a new isolate/isolated scope. It does not prototypically inherit the parent scope. You can still access the parent scope using $parent, but this is not normally recommended. Instead, you should specify which parent scope properties (and/or function) the directive needs via additional attributes on the same element where the directive is used, using the =, @, and & notation.

    4. transclude: true - the directive creates a new "transcluded" child scope, which prototypically inherits from the parent scope. If the directive also creates an isolate scope, the transcluded and the isolate scopes are siblings. The $parent property of each scope references the same parent scope.
      Angular v1.3 update: If the directive also creates an isolate scope, the transcluded scope is now a child of the isolate scope. The transcluded and isolate scopes are no longer siblings. The $parent property of the transcluded scope now references the isolate scope.

    The above link has examples and pictures of all 4 types.

    You cannot access the scope in the directive's compile function (as mentioned here: https://github.com/angular/angular.js/wiki/Dev-Guide:-Understanding-Directives). You can access the directive's scope in the link function.

    Watching:

    For 1. and 2. above: normally you specify which parent property the directive needs via an attribute, then $watch it:

    <div my-dir attr1="prop1"></div>
    
    scope.$watch(attrs.attr1, function() { ... });
    

    If you are watching an object property, you'll need to use $parse:

    <div my-dir attr2="obj.prop2"></div>
    
    var model = $parse(attrs.attr2);
    scope.$watch(model, function() { ... });
    

    For 3. above (isolate scope), watch the name you give the directive property using the @ or = notation:

    <div my-dir attr3="{{prop3}}" attr4="obj.prop4"></div>
    
    scope: {
      localName3: '@attr3',
      attr4:      '='  // here, using the same name as the attribute
    },
    link: function(scope, element, attrs) {
       scope.$watch('localName3', function() { ... });
       scope.$watch('attr4',      function() { ... });
    
    0 讨论(0)
  • 2020-11-22 14:07

    If you are using ES6 Classes and ControllerAs syntax, you need to do something slightly different.

    See the snippet below and note that vm is the ControllerAs value of the parent Controller as used in the parent HTML

    myApp.directive('name', function() {
      return {
        // no scope definition
        link : function(scope, element, attrs, ngModel) {
    
            scope.vm.func(...)
    
    0 讨论(0)
  • 2020-11-22 14:09
     scope: false
     transclude: false
    

    and you will have the same scope(with parent element)

    $scope.$watch(...
    

    There are a lot of ways how to access parent scope depending on this two options scope& transclude.

    0 讨论(0)
  • 2020-11-22 14:16

    Accessing controller method means accessing a method on parent scope from directive controller/link/scope.

    If the directive is sharing/inheriting the parent scope then it is quite straight forward to just invoke a parent scope method.

    Little more work is required when you want to access parent scope method from Isolated directive scope.

    There are few options (may be more than listed below) to invoke a parent scope method from isolated directives scope or watch parent scope variables (option#6 specially).

    Note that I used link function in these examples but you can use a directive controller as well based on requirement.

    Option#1. Through Object literal and from directive html template

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </head>
    
      <body ng-controller="MainCtrl">
        <p>Hello {{name}}!</p>
    
        <p> Directive Content</p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>
    
    
        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>
    
      </body>
    
    </html>
    

    itemfilterTemplate.html

    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChanged({selectedItems:selectedItems})" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>
    

    app.js

    var app = angular.module('plunker', []);
    
    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=',
          selectedItemsChanged: '&'
        },
        templateUrl: "itemfilterTemplate.html"
      }
    })
    
    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';
    
      $scope.selectedItems = ["allItems"];
    
      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnedFromDirective = selectedItems1;
      }
    
      $scope.items = [{
        "id": "allItems",
        "name": "All Items",
        "order": 0
      }, {
        "id": "CaseItem",
        "name": "Case Item",
        "model": "PredefinedModel"
      }, {
        "id": "Application",
        "name": "Application",
        "model": "Bank"
        }]
    
    });
    

    working plnkr: http://plnkr.co/edit/rgKUsYGDo9O3tewL6xgr?p=preview

    Option#2. Through Object literal and from directive link/scope

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </head>
    
      <body ng-controller="MainCtrl">
        <p>Hello {{name}}!</p>
    
        <p> Directive Content</p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>
    
    
        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>
    
      </body>
    
    </html>
    

    itemfilterTemplate.html

    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" 
     ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>
    

    app.js

    var app = angular.module('plunker', []);
    
    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=',
          selectedItemsChanged: '&'
        },
        templateUrl: "itemfilterTemplate.html",
        link: function (scope, element, attrs){
          scope.selectedItemsChangedDir = function(){
            scope.selectedItemsChanged({selectedItems:scope.selectedItems});  
          }
        }
      }
    })
    
    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';
    
      $scope.selectedItems = ["allItems"];
    
      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnedFromDirective = selectedItems1;
      }
    
      $scope.items = [{
        "id": "allItems",
        "name": "All Items",
        "order": 0
      }, {
        "id": "CaseItem",
        "name": "Case Item",
        "model": "PredefinedModel"
      }, {
        "id": "Application",
        "name": "Application",
        "model": "Bank"
        }]
    });
    

    working plnkr: http://plnkr.co/edit/BRvYm2SpSpBK9uxNIcTa?p=preview

    Option#3. Through Function reference and from directive html template

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </head>
    
      <body ng-controller="MainCtrl">
        <p>Hello {{name}}!</p>
    
        <p> Directive Content</p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>
    
    
        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnFromDirective}} </p>
    
      </body>
    
    </html>
    

    itemfilterTemplate.html

    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" 
     ng-change="selectedItemsChanged()(selectedItems)" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>
    

    app.js

    var app = angular.module('plunker', []);
    
    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems:'=',
          selectedItemsChanged: '&'
        },
        templateUrl: "itemfilterTemplate.html"
      }
    })
    
    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';
    
      $scope.selectedItems = ["allItems"];
    
      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnFromDirective = selectedItems1;
      }
    
      $scope.items = [{
        "id": "allItems",
        "name": "All Items",
        "order": 0
      }, {
        "id": "CaseItem",
        "name": "Case Item",
        "model": "PredefinedModel"
      }, {
        "id": "Application",
        "name": "Application",
        "model": "Bank"
        }]
    });
    

    working plnkr: http://plnkr.co/edit/Jo6FcYfVXCCg3vH42BIz?p=preview

    Option#4. Through Function reference and from directive link/scope

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </head>
    
      <body ng-controller="MainCtrl">
        <p>Hello {{name}}!</p>
    
        <p> Directive Content</p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>
    
    
        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>
    
      </body>
    
    </html>
    

    itemfilterTemplate.html

    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>
    

    app.js

    var app = angular.module('plunker', []);
    
    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=',
          selectedItemsChanged: '&'
        },
        templateUrl: "itemfilterTemplate.html",
        link: function (scope, element, attrs){
          scope.selectedItemsChangedDir = function(){
            scope.selectedItemsChanged()(scope.selectedItems);  
          }
        }
      }
    })
    
    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';
    
      $scope.selectedItems = ["allItems"];
    
      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnedFromDirective = selectedItems1;
      }
    
      $scope.items = [{
        "id": "allItems",
        "name": "All Items",
        "order": 0
      }, {
        "id": "CaseItem",
        "name": "Case Item",
        "model": "PredefinedModel"
      }, {
        "id": "Application",
        "name": "Application",
        "model": "Bank"
        }]
    
    });
    

    working plnkr: http://plnkr.co/edit/BSqx2J1yCY86IJwAnQF1?p=preview

    Option#5: Through ng-model and two way binding, you can update parent scope variables.. So, you may not require to invoke parent scope functions in some cases.

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </head>
    
      <body ng-controller="MainCtrl">
        <p>Hello {{name}}!</p>
    
        <p> Directive Content</p>
        <sd-items-filter ng-model="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>
    
    
        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}} </p>
    
      </body>
    
    </html>
    

    itemfilterTemplate.html

    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" 
     ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>
    

    app.js

    var app = angular.module('plunker', []);
    
    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=ngModel'
        },
        templateUrl: "itemfilterTemplate.html"
      }
    })
    
    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';
    
      $scope.selectedItems = ["allItems"];
    
      $scope.items = [{
        "id": "allItems",
        "name": "All Items",
        "order": 0
      }, {
        "id": "CaseItem",
        "name": "Case Item",
        "model": "PredefinedModel"
      }, {
        "id": "Application",
        "name": "Application",
        "model": "Bank"
        }]
    });
    

    working plnkr: http://plnkr.co/edit/hNui3xgzdTnfcdzljihY?p=preview

    Option#6: Through $watch and $watchCollection It is two way binding for items in all above examples, if items are modified in parent scope, items in directive would also reflect the changes.

    If you want to watch other attributes or objects from parent scope, you can do that using $watch and $watchCollection as given below

    html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
    <head>
      <meta charset="utf-8" />
      <title>AngularJS Plunker</title>
      <script>
        document.write('<base href="' + document.location + '" />');
      </script>
      <link rel="stylesheet" href="style.css" />
      <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
      <script src="app.js"></script>
    </head>
    
    <body ng-controller="MainCtrl">
      <p>Hello {{user}}!</p>
      <p>directive is watching name and current item</p>
      <table>
        <tr>
          <td>Id:</td>
          <td>
            <input type="text" ng-model="id" />
          </td>
        </tr>
        <tr>
          <td>Name:</td>
          <td>
            <input type="text" ng-model="name" />
          </td>
        </tr>
        <tr>
          <td>Model:</td>
          <td>
            <input type="text" ng-model="model" />
          </td>
        </tr>
      </table>
    
      <button style="margin-left:50px" type="buttun" ng-click="addItem()">Add Item</button>
    
      <p>Directive Contents</p>
      <sd-items-filter ng-model="selectedItems" current-item="currentItem" name="{{name}}" selected-items-changed="selectedItemsChanged" items="items"></sd-items-filter>
    
      <P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}}</p>
    </body>
    
    </html>
    

    script app.js

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

    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          name: '@',
          currentItem: '=',
          items: '=',
          selectedItems: '=ngModel'
        },
        template: '<select ng-model="selectedItems" multiple="multiple" style="height: 140px; width: 250px;"' +
          'ng-options="item.id as item.name group by item.model for item in items | orderBy:\'name\'">' +
          '<option>--</option> </select>',
        link: function(scope, element, attrs) {
          scope.$watchCollection('currentItem', function() {
            console.log(JSON.stringify(scope.currentItem));
          });
          scope.$watch('name', function() {
            console.log(JSON.stringify(scope.name));
          });
        }
      }
    })
    
     app.controller('MainCtrl', function($scope) {
      $scope.user = 'World';
    
      $scope.addItem = function() {
        $scope.items.push({
          id: $scope.id,
          name: $scope.name,
          model: $scope.model
        });
        $scope.currentItem = {};
        $scope.currentItem.id = $scope.id;
        $scope.currentItem.name = $scope.name;
        $scope.currentItem.model = $scope.model;
      }
    
      $scope.selectedItems = ["allItems"];
    
      $scope.items = [{
        "id": "allItems",
        "name": "All Items",
        "order": 0
      }, {
        "id": "CaseItem",
        "name": "Case Item",
        "model": "PredefinedModel"
      }, {
        "id": "Application",
        "name": "Application",
        "model": "Bank"
      }]
    });
    

    You can always refer AngularJs documentation for detailed explanations about directives.

    0 讨论(0)
  • 2020-11-22 14:19

    Here's a trick I used once: create a "dummy" directive to hold the parent scope and place it somewhere outside the desired directive. Something like:

    module.directive('myDirectiveContainer', function () {
        return {
            controller: function ($scope) {
                this.scope = $scope;
            }
        };
    });
    
    module.directive('myDirective', function () {
        return {
            require: '^myDirectiveContainer',
            link: function (scope, element, attrs, containerController) {
                // use containerController.scope here...
            }
        };
    });
    

    and then

    <div my-directive-container="">
        <div my-directive="">
        </div>
    </div>
    

    Maybe not the most graceful solution, but it got the job done.

    0 讨论(0)
  • 2020-11-22 14:20

    Having tried everything, I finally came up with a solution.

    Just place the following in your template:

    {{currentDirective.attr = parentDirective.attr; ''}}

    It just writes the parent scope attribute/variable you want to access to the current scope.

    Also notice the ; '' at the end of the statement, it's to make sure there's no output in your template. (Angular evaluates every statement, but only outputs the last one).

    It's a bit hacky, but after a few hours of trial and error, it does the job.

    0 讨论(0)
提交回复
热议问题