ディレクティブはAngularJSのng-repeatスコープでスコープを分離します


95

(他の場所でディレクティブを再利用できるように)分離スコープを持つディレクティブがありng-repeat、このディレクティブをで使用すると、機能しません。

このトピックに関するすべてのドキュメントとスタックオーバーフローの回答を読み、問題を理解しました。通常の問題をすべて回避できたと思います。

したがって、ng-repeatディレクティブによって作成されたスコープが原因でコードが失敗することを理解しています。私自身のディレクティブは、isolateスコープを作成し、親スコープ内のオブジェクトに双方向のデータバインディングを行います。私のディレクティブは、このバインドされた変数に新しいオブジェクト値を割り当てます。これは、私のディレクティブを使用しない場合ng-repeat(親変数が正しく更新される場合)に完全に機能します。ただし、ng-repeatでは、割り当てによってng-repeatスコープ内に新しい変数が作成され、親変数には変更が反映されません。これはすべて、私が読んだ内容に基づいて予想どおりです。

また、特定の要素に複数のディレクティブがある場合、1つのスコープのみが作成されることも読みました。そしてpriority、各ディレクティブにa を設定して、ディレクティブが適用される順序を定義できます。ディレクティブは優先度でソートされてから、それらのコンパイル関数が呼び出されます(http://docs.angularjs.org/guide/directiveで優先度という単語を検索してください)。

したがって、優先順位を使用してディレクティブが最初に実行され、最終的に分離スコープが作成されるようにし、ng-repeat実行時に親スコープからプロトタイプを継承するスコープを作成する代わりに分離スコープを再利用できることを望んでいました。ng-repeatドキュメントは、優先レベルでそのディレクティブの実行と述べています10001優先度が高いか低いかは不明です。1ディレクティブで優先度を使用しても違いはないので、試してみました2000。しかし、それは事態をさらに悪化させます。私の双方向のバインディングがなりundefined、私のディレクティブは何も表示しません。

私は自分の問題を示すためのフィドルを作成しましpriorityディレクティブの設定をコメントアウトしました。名前オブジェクトのリストと、名前オブジェクトのname-row姓名フィールドを表示するという名前のディレクティブがあります。表示された名前をクリックselectedすると、メインスコープに変数を設定して欲しい。名前の配列、selected変数はname-row双方向のデータバインディングを使用してディレクティブに渡されます。

メインスコープで関数を呼び出すことでこれを機能させる方法を知っています。また、selectedが別のオブジェクトの内部にあり、外部オブジェクトにバインドすると、問題が発生しないことも知っています。しかし、私は現時点ではそれらの解決策には興味がありません。

代わりに、私が持っている質問は次のとおりです。

  • どうやって防ぐか ng-repeat親スコープからプロトタイプ的に継承するスコープを作成せずに、ディレクティブの分離スコープを使用するにはですか?
  • なぜ優先レベルなのか 2000ディレクティブのしないのですか?
  • バタランを使用して、どのタイプのスコープが使用されているかを知ることは可能ですか?

1
通常、ディレクティブが他のディレクティブと同じ要素で使用される場合は、分離スコープを使用しません。独自のスコーププロパティを作成していて、ng-repeatを使用する必要があるscope: trueため、ディレクティブに使用することをお勧めします。(まだお持ちでない場合)も参照してください。stackoverflow.com/ questions / 14914213 / … また、ディレクティブが複数の場所で使用されるからといって、自動的に分離スコープを使用する必要があるわけではありません。
Mark Rajcok 2013年

私はあなたの答えの多くを読みました(それらは素晴らしいものを超えています、それらを書いてくれてありがとう)。リンク先を読みました。Isolate-scopeディレクティブを他のディレクティブと混在させることはできないようです。そのような指令はコンポーネントであり、したがって他の指令と混合する必要がないという意見に同意します。私にとって(これまでのところ)1つの例外はですng-repeat。スタンドアロンディレクティブとを混在させることができると便利ng-repeatです。続行するには...
Deepak Nulu 2013年

上から続きます...したがって、要素のスコープを持つディレクティブが1つだけの場合は、スコープを持つng-repeatべきではありません。ng-repeatスコープを持つことは、典型的なユースケースでは意味があるので、変更することはお勧めしません。代わりに、アレックスオズボーンの回答でコメントしたようにng-repeat、独自のスコープを作成しないで、それに基づいて繰り返しディレクティブを作成すると思います。これは、独自の分離スコープを持つディレクティブの繰り返しに使用できます。続行するには...
Deepak Nulu 2013年

ディレクティブを繰り返すコードは、使用するng-repeatか、カスタムのスコープレスの繰り返しディレクティブを使用するかを知る必要があります。「呼び出し元」がこれを知っていても大丈夫だと思いますが、「呼び出し先」(指示が繰り返されている)がそれが繰り返されているかどうかを知ることは許されていません。続行するには...
Deepak Nulu 2013年

ここのコメントに少し夢中になります... :-) ngRepeat 独自のスコープを作成する必要があります。ここに分離スコープが必要だと思うのはなぜですか?
Josh David Miller

回答:


203

さて、上記のコメントの多くを通して、私は混乱を発見しました。まず、いくつかの点を明確にします。

  • ngRepeatは選択した分離スコープに影響しません
  • あなたのディレクティブの属性で使用するためにngRepeatに渡されたパラメータはない prototypically-継承されたスコープを使用します
  • ディレクティブが機能しない理由は、分離スコープとは関係ありません

以下は同じコードの例ですが、ディレクティブが削除されています。

<li ng-repeat="name in names"
    ng-class="{ active: $index == selected }"
    ng-click="selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

ここでJSFiddleそれが動作しないことを証明するには。ディレクティブとまったく同じ結果が得られます。

なぜ機能しないのですか?AngularJSのスコープはプロトタイプの継承を使用するためです。selected親スコープの値はプリミティブです。JavaScriptでは、これは、子が同じ値を設定すると上書きされることを意味します。AngularJSスコープにはゴールデンルールがあります。モデルの値には常に a .が必要です。つまり、それらは決してプリミティブであってはなりません。このSOの答えを見てくださいをしてください。


スコープの最初の外観は次のとおりです。

ここに画像の説明を入力してください

最初の項目をクリックすると、スコープは次のようになります。

ここに画像の説明を入力してください

selectedngRepeatスコープに新しいプロパティが作成されたことに注意してください。コントローラースコープ003は変更されませんでした。

2番目の項目をクリックすると、おそらく何が起こるかを推測できます。

ここに画像の説明を入力してください


したがって、あなたの問題は実際にはngRepeatによって引き起こされるのではなく、AngularJSの黄金律を破ることによって引き起こされます。それを修正する方法は、単にオブジェクトプロパティを使用することです。

$scope.state = { selected: undefined };
<li ng-repeat="name in names"
    ng-class="{ active: $index == state.selected }"
    ng-click="state.selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

これが2番目のJSFiddleですあまりにもこの作品を示したが。

スコープの初期状態は次のとおりです。

ここに画像の説明を入力してください

最初のアイテムをクリックした後:

ここに画像の説明を入力してください

ここでは、必要に応じてコントローラーのスコープが影響を受けています。

また、これが分離スコープを持つディレクティブでも機能することを証明するために(これも問題とは関係がないため)、ここにもJSFiddleがあり、ビューはオブジェクトを反映する必要があります。必要な唯一の変更は、プリミティブではなくオブジェクトを使用することでした。

最初のスコープ:

ここに画像の説明を入力してください

最初の項目をクリックした後のスコープ:

ここに画像の説明を入力してください

結論としては、もう一度、問題は分離スコープではなく、ngRepeatの動作方法ではありません。あなたの問題は、あなたがこのまさに問題につながることが知られているルールを破っているということです。AngularJSのモデルには常にが必要.です。


分離スコープと双方向データバインディングを使用したディレクティブの実験により、.ゴールデンルールはプロトタイプの継承を持つスコープにのみ必要であると思いました。分離スコープを使用すると、関心のある変数scope: {}はディレクティブの定義で定義されるため、それらの変数は分離スコープ内に最初から存在します。また、isolate-scopeは親スコープからプロトタイプを継承しないため、親変数をマスクしません。つまり、マスクする「親」スコープはありません。
Deepak Nulu 2013年

1
また言っている必要があります:あなたのディレクティブは、子スコープを作成していないが、それは簡単にできることで使用文脈で必要と子スコープを。ngRepeatは1つのケースです。トランスクルージョンもそうです。私を信じて-を使用してください.
Josh David Miller

1
@JoshDavidMillerが私の混乱をようやく解消できたAngularJS Googleグループに関するディスカッション
Deepak Nulu 2013年

2
これらのクールな図はどこから入手できますか?他のユーザーもこれらを投稿しているのを見ました。
Asad Saeeduddin 2014

3
@アサド、私は最近GitHubにツールを公開しました。これはPeri $ scopeと呼ばれています
Mark Rajcok、2015

6

質問への回答を直接避けようとせずに、代わりに次のフィドルを見てください。

http://jsfiddle.net/dVPLM/

重要な点は、Angularの従来の動作と戦い、変更しようとする代わりに、ng-repeatそれをオーバーライドしようとするのではなく、動作するようにディレクティブを構成できることです。

テンプレートで:

    <name-row 
        in-names-list="names"
        io-selected="selected">
    </name-row>

あなたのディレクティブで:

    template:
'        <ul>' +      
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    {{$index}} - {{name.first}} {{name.last}}' +
'                </a>' +
'            </li>' +
'        </ul>'

あなたの質問に答えて:

  • ng-repeat スコープを作成します。これを変更しようとしてはいけません。
  • ディレクティブの優先順位は実行順序だけではありません-以下を参照してください: AngularJS:HTMLコンパイラはコンパイルの順序をどのように調整しますか?
  • バタランでは、[パフォーマンス]タブを確認すると、各スコープにバインドされた式を確認し、これが期待と一致しているかどうかを確認できます。

迅速な対応ありがとうございました。私がやろうとしていることが木目に反すると、少し悲しくなります(それでも、AngularJSは素晴らしいフレームワークです)。ディレクティブは、再利用可能なコンポーネントであることを意図しています。それが書かれたとき、作者はそれがwithで使われるかどうかを心配する必要はありng-repeatません。多分それが最初に書かれたとき、それはで使用されることはありませんng-repeat。そして、将来的にはで使用される可能性がng-repeatあり、その時点では、書き換えなしで動作するはずです。うまくいけば、AngularJSの将来のリリースでこれが可能になるでしょう。
Deepak

上記のコメントを明確にしたいと思います。一緒に使うかどうかを気にしないディレクティブを書くことは可能だと思いますng-repeat。しかし、ディレクティブ自体のスコープで双方向バインディングを変更するのではなく、親スコープの変数を変更できるように、関数をディレクティブに渡す必要があるようです。双方向のバインディングと再利用可能なディレクティブは、AngularJSの私のお気に入りの2つの点ng-repeatであり、軟膏で飛ぶことが証明されています。ng-repeat独自のスコープを作成しない同等のものを書くことができるかもしれません。
Deepak Nulu 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.