AngularJSの*独自のスコープを持つ*カスタムディレクティブ内から親スコープにアクセスする方法は?


327

ディレクティブ内の「親」スコープにアクセスする方法を探しています。スコープ、トランスクルード、要求、変数(またはスコープ自体)を上から渡すなど、どのような組み合わせでもかまいません。完全に逆方向に曲がってもかまいませんが、完全にハックされたものやメンテナンス不可能なものは避けたいです。たとえば$scope、preLinkパラメータからを取得し、その$siblingスコープを反復して概念的な「親」を見つけることで、今すぐ実行できることを知っています。

私が本当に望んでいるのは$watch、親スコープで式を表現できるようにすることです。私がそれを行うことができる場合、私はここで私がやろうとしていることを達成できます: AngularJS-変数を使用してパーシャルをレンダリングする方法?

重要な注意点は、ディレクティブは同じ親スコープ内で再利用可能でなければならないということです。したがって、デフォルトの動作(スコープ:false)は機能しません。ディレクティブのインスタンスごとに個別のスコープが必要です。次に$watch、親スコープに存在する変数が必要です。

コードサンプルは1000ワードに相当するので、

app.directive('watchingMyParentScope', function() {
    return {
        require: /* ? */,
        scope: /* ? */,
        transclude: /* ? */,
        controller: /* ? */,
        compile: function(el,attr,trans) {
            // Can I get the $parent from the transclusion function somehow?
            return {
                pre: function($s, $e, $a, parentControl) {
                    // Can I get the $parent from the parent controller?
                    // By setting this.$scope = $scope from within that controller?

                    // Can I get the $parent from the current $scope?

                    // Can I pass the $parent scope in as an attribute and define
                    // it as part of this directive's scope definition?

                    // What don't I understand about how directives work and
                    // how their scope is related to their parent?
                },
                post: function($s, $e, $a, parentControl) {
                    // Has my situation improved by the time the postLink is called?
                }
            }
        }
    };
});

回答:


644

AngularJSのスコーププロトタイプ/プロトタイプ継承のニュアンスは何ですか?を参照してください

要約すると、ディレクティブが親($parent)スコープにアクセスする方法は、ディレクティブが作成するスコープのタイプによって異なります。

  1. デフォルト(scope: false)-ディレクティブは新しいスコープを作成しないため、ここでは継承はありません。ディレクティブのスコープは、親/コンテナと同じスコープです。リンク関数では、最初のパラメーター(通常はscope)を使用します。

  2. scope: true-ディレクティブは、親スコープからプロトタイプ的に継承する新しい子スコープを作成します。親スコープで定義されたプロパティは、ディレクティブで使用できますscope(プロトタイプの継承のため)。プリミティブスコーププロパティへの書き込みに注意してください。これにより、ディレクティブスコープに新しいプロパティが作成されます(同じ名前の親スコーププロパティを非表示/シャドウにします)。

  3. scope: { ... }-ディレクティブは新しい分離/分離スコープを作成します。親スコープをプロトタイプ的に継承することはありません。を使用して親スコープにアクセスできます$parentが、これは通常は推奨されません。代わりに、あなたはどの親スコープのプロパティ(および/または機能)ディレクティブを使用して、使用されているのと同じ要素に追加の属性を経由して指令ニーズ指定する必要があります=@&表記を。

  4. transclude: true-ディレクティブは、新しい「トランスクルードされた」子スコープを作成します。子スコープは、典型的には親スコープから継承します。ディレクティブが分離スコープも作成する場合、変換されたスコープと分離スコープは兄弟です。$parent各スコープのプロパティは、同じ親スコープを参照します。
    Angular v1.3 update:ディレクティブが分離スコープも作成する場合、トランスクルードされたスコープは分離スコープの子になります。変換されたスコープと分離されたスコープは、兄弟ではなくなりました。$parent変換されたスコープのプロパティは、分離スコープを参照するようになりました。

上記のリンクには、4つのタイプすべての例と写真があります。

ディレクティブのコンパイル関数でスコープにアクセスすることはできません(https://github.com/angular/angular.js/wiki/Understanding-Directivesで説明されています)。リンク関数でディレクティブのスコープにアクセスできます。

監視:

上記の1.および2.の場合:通常、ディレクティブを介して属性に必要な親プロパティを指定してから、$ watchを実行します。

<div my-dir attr1="prop1"></div>

scope.$watch(attrs.attr1, function() { ... });

オブジェクトプロパティを監視している場合は、$ parseを使用する必要があります。

<div my-dir attr2="obj.prop2"></div>

var model = $parse(attrs.attr2);
scope.$watch(model, function() { ... });

上記3.(分離スコープ)の場合は、@or =表記を使用してディレクティブプロパティに付ける名前を確認します。

<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() { ... });

1
ありがとう、マーク。変数を使用してパーシャルをレンダリングする方法に投稿した解決策は実際には非常に美しく機能します。私をリンクするために本当に必要なのは、「HTMLを記述し、要素がng-controller内にネストされていないことを認識することのニュアンス」と呼ばれるものでした。うわー...新人間違い。しかし、これはスコープを説明する他の(はるかに長い)回答への便利な追加です。
colllin 2013

@collinさん、すばらしいです。他の(現在は削除されている)コメントへの応答方法がよくわからなかったので、問題を解決してくれてうれしいです。
Mark Rajcok 2013

内部で実行できる/実行すべきことscope.$watch('localName3', function() { ...[?? WHAT TO DO HERE for example?] });
Junaid Qadir 2014年

1
@Andy 、: fiddle$parseと一緒に使用しないでください。 非分離スコープでのみ必要です。=$parse
Mark Rajcok、2015年

1
これは非常に完全な素晴らしい答えです。また、AngularJSでの作業が嫌いな理由も示しています。
John Trichereau、2015年

51

コントローラーメソッドへのアクセスとは、ディレクティブコントローラー/リンク/スコープから親スコープのメソッドにアクセスすることを意味します。

ディレクティブが親スコープを共有/継承している場合は、親スコープメソッドを呼び出すだけです。

分離されたディレクティブスコープから親スコープメソッドにアクセスする場合は、もう少し作業が必要です。

分離されたディレクティブスコープから親スコープメソッドを呼び出すか、親スコープ変数を監視するためのオプションはいくつかあります(以下にリストされているよりも多い場合があります)(特別なオプション#6)。

私が使用したlink functionこれらの例ではなく、あなたが使用できるdirective controllerだけでなく要件に基づいて。

オプション1。 オブジェクトリテラルとディレクティブHTMLテンプレートから

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"
    }]

});

作業中のplnkr:http ://plnkr.co/edit/rgKUsYGDo9O3tewL6xgr?p=preview

オプション#2。オブジェクトリテラルとディレクティブリンク/スコープから

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"
    }]
});

作業中のplnkr:http ://plnkr.co/edit/BRvYm2SpSpBK9uxNIcTa?p=preview

オプション#3。関数参照とディレクティブHTMLテンプレートから

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"
    }]
});

作業中のplnkr:http ://plnkr.co/edit/Jo6FcYfVXCCg3vH42BIz?p=preview

オプション#4。関数参照およびディレクティブリンク/スコープから

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"
    }]

});

作業中のplnkr:http ://plnkr.co/edit/BSqx2J1yCY86IJwAnQF1?p=preview

オプション#5:ng-modelと双方向バインディングにより、親スコープ変数を更新できます。。したがって、場合によっては、親スコープ関数を呼び出す必要がない場合があります。

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"
    }]
});

作業中のplnkr:http ://plnkr.co/edit/hNui3xgzdTnfcdzljihY?p=preview

オプション#6:Through $watchおよび$watchCollection Itはitems、上記のすべての例での双方向バインディングです。アイテムが親スコープで変更された場合、ディレクティブのアイテムも変更を反映します。

あなたが親スコープから他の属性やオブジェクトを見たい場合は、使用していることを行うことができます$watchし、$watchCollection下記のよう

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>

スクリプト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"
  }]
});

ディレクティブに関する詳細な説明については、AngularJのドキュメントをいつでも参照できます。


10
彼は担当者のために一生懸命働いています...担当者のために一生懸命働いています...彼は担当者のために一生懸命働いています。
スリム

7
downvoted -解答内のいずれかの貴重な情報は、その長さにアクセスできない
救済

2
私は明確に分離されたすべての利用可能な選択肢で質問に答えました。私の意見では、あなたの前に全体像が見えるまで、短い答えは必ずしも役に立たない。
Yogesh Manware 2016年

@YogeshManware:長いマークアップを使用せず、スタイルシートなどの無関係なものを省略し、「group by」などを使用しないように例を簡略化することで、大幅に短縮できます。各例。
2016年

これは反対票を投じる理由ではありません。人々はこの特権を乱用します
Winnemucca

11
 scope: false
 transclude: false

そして、あなたは同じスコープを持っています(親要素を持つ)

$scope.$watch(...

この2つのオプションscope&transcludeに応じて、親スコープにアクセスする方法はたくさんあります。


はい、短くて甘い、そして正しい。彼らは親要素とまったく同じスコープを共有しているようです...同じスコープで再利用することを不可能にします。 jsfiddle.net/collindo/xqytH
colllin 2013

2
解決策はそれほど単純ではありませんので、我々は、再利用可能なコンポーネントを書き、我々は孤立範囲を必要とする多くの回
イヴォン・フイン・

8

これが私が一度使用したトリックです。「ダミー」ディレクティブを作成して親スコープを保持し、それを目的のディレクティブの外のどこかに配置します。何かのようなもの:

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...
        }
    };
});

その後

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

多分最も優雅な解決策ではないかもしれませんが、それは仕事を成し遂げました。


4

ES6のクラスとControllerAs構文を使用している場合は、少し異なる操作を行う必要があります

以下のスニペットを参照してください。これvmControllerAs、親HTMLで使用される親コントローラーの値です。

myApp.directive('name', function() {
  return {
    // no scope definition
    link : function(scope, element, attrs, ngModel) {

        scope.vm.func(...)

0

すべてを試してみて、ようやく解決策を思いつきました。

テンプレートに以下を配置するだけです:

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

現在のスコープにアクセスする親スコープの属性/変数を書き込むだけです。

また; ''、ステートメントの終わりに注意してください。これは、テンプレートに出力がないことを確認するためです。(Angularはすべてのステートメントを評価しますが、最後のステートメントのみを出力します)。

それは少しハックですが、数時間の試行錯誤の後で、それは仕事をします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.