AngularJSコントローラーの 'this'と$ scope


1027

AngularJSのホームページ「Create Components」セクションに、次の例があります。

controller: function($scope, $element) {
  var panes = $scope.panes = [];
  $scope.select = function(pane) {
    angular.forEach(panes, function(pane) {
      pane.selected = false;
    });
    pane.selected = true;
  }
  this.addPane = function(pane) {
    if (panes.length == 0) $scope.select(pane);
    panes.push(pane);
  }
}

selectメソッドがに追加されている$scopeが、addPaneメソッドがに追加されていることに注意してくださいthis。に変更すると$scope.addPane、コードが壊れます。

ドキュメントには実際には違いがあると書かれていますが、違いが何であるかについては触れられていません:

Angularの以前のバージョン(1.0 RC以前)thisでは、$scopeメソッドと互換的に使用できましたが、これは当てはまりません。スコープに定義されたメソッドの内部this$scope(角セットは互換性がありますthis$scopeではなく、それ以外の場合は、あなたのコントローラのコンストラクタの内部で、)。

どのようにthisして$scopeAngularJSのコントローラで動作しますか?


これもまた混乱します。ビューがコントローラーを指定する場合(例:ng-controller = '...')、ビューは$ scopeプロパティにアクセスできるため、そのコントローラーに関連付けられた$ scopeがそれに付随するように見えます。しかし、ディレクティブが別のコントローラーを「必要とする」(そしてそれをそのリンク関数で使用する)とき、その他のコントローラーに関連付けられている$ scopeはそれに付属していませんか?
Mark Rajcok 2012

「以前のバージョン...」についての紛らわしい引用は、今では削除されていますか?その後、おそらく更新が行われますか?
ドミトリザイツェフ2015

単体テストの場合、「$ scope」の代わりに「this」を使用すると、モックされたスコープでコントローラーを注入できないため、単体テストを実行できません。「これ」を使うのは良い習慣ではないと思います。
アベンタン

回答:


999

「どのようにthisして$scopeAngularJSコントローラで動作しますか?」

短い答え

  • this
    • コントローラーコンストラクター関数が呼び出されるthisと、コントローラーになります。
    • $scopeオブジェクトで定義されたthis関数が呼び出されると、「関数が呼び出されたときに有効なスコープ」になります。これは$scope、関数が定義されている場合とそうでない場合があります。だから、関数の内部、thisおよび$scopeかもしれないと同じこと。
  • $scope
    • すべてのコントローラには、関連付けられた$scopeオブジェクトがあります。
    • コントローラー(コンストラクター)関数は、それに関連付けられたのモデルプロパティと関数/動作を設定します$scope
    • $scopeHTML /ビューからアクセスできるのは、このオブジェクト(およびプロトタイプ継承が機能している場合は親スコープオブジェクト)で定義されているメソッドだけです。たとえば、ng-clickフィルタ、などから

長い答え

コントローラ関数はJavaScriptコンストラクタ関数です。コンストラクター関数が実行されるとき(たとえば、ビューが読み込まれるとき)、this(つまり、「関数コンテキスト」)がコントローラーオブジェクトに設定されます。したがって、「タブ」コントローラーコンストラクター関数では、addPane関数が作成されます。

this.addPane = function(pane) { ... }

$ scopeではなく、コントローラーオブジェクト上に作成されます。ビューはaddPane関数を見ることができません-ビューは$ scopeで定義された関数にのみアクセスできます。つまり、HTMLでは、これは機能しません。

<a ng-click="addPane(newPane)">won't work</a>

「タブ」コントローラーコンストラクター関数が実行されると、次のようになります。

タブコントローラコンストラクタ関数の後

黒い破線は、プロトタイプの継承を示しています。分離スコープは、プロトタイプとしてスコープから継承します。(HTMLでディレクティブが検出された有効なスコープからプロトタイプを継承しません。)

現在、ペインディレクティブのリンク関数は、タブディレクティブと通信する必要があります(これは、タブに影響を与える必要があるため、$ scopeを何らかの方法で分離する必要があります)。イベントを使用することもできますが、別のメカニズムは、ペインディレクティブをrequireタブコントローラーにすることです。(requireタブ$ scope へのペインディレクティブにはメカニズムがないようです。)

したがって、これは問題を引き起こします:タブコントローラーへのアクセスしか持っていない場合、どのようにしてタブへのアクセスを取得して$ scopeを分離しますか(これは本当に必要なものです)?

さて、赤い点線が答えです。addPane()関数の「スコープ」(ここではJavaScriptの関数スコープ/クロージャーを指します)は、関数にタブ分離$ scopeへのアクセスを提供します。つまり、addPane()が定義されたときに作成されたクロージャが原因で、addPane()は上の図の「タブIsolateScope」にアクセスできます。(代わりに、タブ$ scopeオブジェクトにaddPane()を定義した場合、ペインディレクティブはこの関数にアクセスできないため、タブ$ scopeと通信する方法がありません。)

あなたの質問の他の部分に答えるにはhow does $scope work in controllers?

$ scopeで定義された関数内でthisは、「関数が呼び出された場所/ときに有効な$ scope」に設定されます。次のHTMLがあるとします。

<div ng-controller="ParentCtrl">
   <a ng-click="logThisAndScope()">log "this" and $scope</a> - parent scope
   <div ng-controller="ChildCtrl">
      <a ng-click="logThisAndScope()">log "this" and $scope</a> - child scope
   </div>
</div>

そしてParentCtrl(単独で)持っています

$scope.logThisAndScope = function() {
    console.log(this, $scope)
}

最初のリンクをクリックすると、そのが表示されますthisし、$scope「以来、同じ関数が呼び出された効果でスコープに関連付けられている範囲です」ParentCtrl

第2のリンクをクリックすると、明らかであろうthis$scopeされていない「ので、同じ関数が呼び出された効果に範囲が」に関連付けられている範囲ですChildCtrl。そこでここでは、thisに設定されているChildCtrl$scope。メソッドの内部には、$scopeまだParentCtrlの$ scopeがあります。

フィドル

this特にng-repeat、ng-include、ng-switch、およびディレクティブがすべて独自の子スコープを作成できることを考えると、影響を受ける$ scopeが混乱するため、$ scopeで定義された関数内では使用しないようにします。


6
@tamakisquare、私が引用した太字のテキストは、コントローラーコンストラクター関数が呼び出されたとき、つまりコントローラーが作成されたとき= $ scopeに関連付けられたときに適用されると思います。後で、任意のJavaScriptコードが$ scopeオブジェクトで定義されたメソッドを呼び出すときに適用されません。
Mark Rajcok 2013

79
コントローラに「MyController as myctrl」、次にmyctrl.addPane()という名前を付けて、テンプレートで直接addPane()関数を呼び出すことができるようになりました。docs.angularjs.org/guide/concepts#controller
Christophe

81
固有の複雑さ。
Inanc Gumus 2014

11
これは非常に有益な回答ですが、実用的な問題(「this」を使用して定義されたコントローラーメソッドで$ scope。$ apply()を呼び出す方法)が戻ってきたときは、解決できませんでした。したがって、これは依然として有用な答えですが、「固有の複雑さ」という不可解なものを見つけています。
ダンブルダッド2014

11
Javascript-たくさんのロープ[ぶら下げ]。
AlikElzin-kilaka 2015年

55

これに「addPane」が割り当てられる理由は、<pane>ディレクティブが原因です。

paneディレクティブはないrequire: '^tabs'リンク機能に、親の指示からタブコントローラオブジェクトを入れました。

addPaneリンク関数がそれを見ることができるthisように割り当てられpaneます。次に、paneリンク関数でaddPaneは、これは単にtabsコントローラーのプロパティであり、それは単なるtabsControllerObject.addPaneです。そのため、ペインディレクティブのリンク関数はタブコントローラオブジェクトにアクセスできるため、addPaneメソッドにアクセスできます。

私の説明が十分に明確であることを願っています。説明するのはちょっと難しいです。


3
説明ありがとう。ドキュメントは、コントローラーがスコープを設定する単なる関数であるように見せています。すべてのアクションがスコープ内で発生した場合、コントローラーがオブジェクトのように扱われるのはなぜですか?なぜ親スコープをリンク関数に渡さないのですか?編集:この質問をより適切に表現するには、コントローラーメソッドとスコープメソッドの両方が同じデータ構造(スコープ)で動作する場合、それらすべてを1つの場所に配置しないのはなぜですか?
アレクセイボロニン2012

「誤って親スコープのデータを読み取ったり変更したりしてはならない再利用可能なコンポーネント」をサポートしたいため、親スコープはlnk funcに渡されないようです。しかし、ディレクティブが本当に(「ペイン」ディレクティブのように)親スコープ内の特定のデータを読み取りまたは変更する必要がある場合は、いくらかの作業が必要です。特定のデータにアクセスするには、そのコントローラーのメソッド($ scopeではなく 'this'を使用)を使用します。必要な親スコープがlnk funcに挿入されていないので、これが唯一の方法だと思います。
Mark Rajcok 2012

1
ちょっとマーク、ディレクティブのスコープを変更する方が実際には簡単です。リンク関数jsfiddle.net/TuNyjを
Andrew Joslin

3
フィドルをありがとう@Andy フィドルでは、ディレクティブは新しいスコープを作成しないので、リンク関数がコントローラーのスコープに直接アクセスできる方法をここで確認できます(スコープが1つしかないため)。タブとペインのディレクティブは分離スコープを使用します(つまり、親スコープからプロトタイプを継承しない新しい子スコープが作成されます)。スコープの分離の場合、別のディレクティブが他の(分離された)スコープに(間接的に)アクセスできるようにする唯一の方法は、コントローラーでメソッドを定義する( 'this'を使用する)ようです。
Mark Rajcok、2013年

27

2つの違いに関するかなり興味深い説明と、モデルをコントローラーにアタッチし、コントローラーにエイリアスを設定してモデルをビューにバインドする傾向が高まっています。http://toddmotto.com/digging-into-angulars-controller-as-syntax/が記事です。
彼はそれについて言及していませんが、ディレクティブを定義するときに、複数のディレクティブ間で何かを共有する必要があり、サービスを望まない場合(サービスが煩わしい正当なケースがあります)、データを親ディレクティブのコントローラーにアタッチします。

この$scopeサービスは多くの便利な機能を提供します$watchが、最も明白ですが、データをビューにバインドする必要がある場合は、テンプレートでプレーンコントローラーと 'controller as'を使用することをお勧めします。


20

次の投稿を読むことをお勧めします: AngularJS: "Controller as"または "$ scope"?

「Controller as」を使用して「$ scope」よりも変数を公開することの利点を非常によく説明しています。

変数ではなくメソッドについて具体的に質問したのは知っていますが、1つの手法に固執し、それと一貫している方が良いと思います。

したがって、私の意見では、この投稿で説明されている変数の問題のため、 "Controller as"テクニックを使用し、それをメソッドに適用することをお勧めします。


16

このコース(https://www.codeschool.com/courses/shaping-up-with-angular-js)では、「これ」などの多くの使い方を説明しています。

「this」メソッドを介してコントローラにメソッドを追加する場合は、プロパティまたはメソッドのコントローラ名「ドット」を使用して、ビューでそれを呼び出す必要があります。

たとえば、ビューでコントローラーを使用すると、次のようなコードが表示されます。

    <div data-ng-controller="YourController as aliasOfYourController">

       Your first pane is {{aliasOfYourController.panes[0]}}

    </div>

6
コースを終えた後、すぐにを使用したコードで混乱$scopeしました。
Matt Montag、2014

16
そのコースでは$ scopeについてはまったく触れられていません。使用asしているだけなthisので、違いを説明するにはどうすればよいでしょうか。
dumbledad 2014

10
Angularとの最初の接触は、前述のコースからのものであり、言及され$scopeたことがないようthisに、コントローラーだけで使用することを学びました。問題は、コントローラーでpromiseを処理する必要が生じたときに、多くの参照に関する問題が発生し、promiseのreturn関数内からモデルを参照するthisなどの処理var me = thisを開始する必要があることthisです。だから、そのため、私はまだ非常に私が使用し、すべき方法について混乱しています$scopethis
Bruno Finger

@BrunoFinger残念ながら、Promise やその他のクロージャーを多用するものが必要になるvar me = thisか、.bind(this)いつでも必要になります。Angularとは何の関係もありません。
Dzmitry Lazerka 2016年

1
重要なことは、それ自体がコントローラー自体ng-controller="MyCtrl as MC"を配置することと同じであることを理解することです。$scope.MC = thisテンプレートで使用するスコープでMyCtrlのインスタンス(this)を定義します{{ MC.foo }}
William B

3

Angularの以前のバージョン(1.0 RC以前)では、これを$ scopeメソッドと交換可能に使用できましたが、これは当てはまりません。スコープで定義されたメソッドの内部では、thisと$ scopeは交換可能です(角度によりthisが$ scopeに設定されます)。

この動作を元に戻すには(変更された理由を誰かが知っていますか?)、次のように追加できます。

return angular.extend($scope, this);

コントローラー関数の最後($ scopeがこのコントローラー関数に挿入されている場合)。

これには、コントローラーオブジェクトを介して親スコープにアクセスできるという素晴らしい効果があります。 require: '^myParentDirective'


7
この記事では、これと$ scopeが異なる理由を説明しています。
Robert Martin

1

$ scopeの 'this'とコントローラーは 'this'が異なります。したがって、コントローラー内にconsole.log(this)を配置すると、オブジェクト(コントローラー)が提供され、this.addPane()によってコントローラーオブジェクトにaddPaneメソッドが追加されます。しかし、$ scopeには異なるスコープがあり、そのスコープ内のすべてのメソッドは$ scope.methodName()でアクセスする必要があります。 this.methodName()内部コントローラは、コントローラオブジェクト内にメソッドを追加することを意味します。$scope.functionName()HTML内にあり

$scope.functionName(){
    this.name="Name";
    //or
    $scope.myname="myname"//are same}

このコードをエディターに貼り付け、コンソールを開いて確認してください...

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>this $sope vs controller</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
    <script>
        var app=angular.module("myApp",[]);
app.controller("ctrlExample",function($scope){
          console.log("ctrl 'this'",this);
          //this(object) of controller different then $scope
          $scope.firstName="Andy";
          $scope.lastName="Bot";
          this.nickName="ABot";
          this.controllerMethod=function(){

            console.log("controllerMethod ",this);
          }
          $scope.show=function(){
              console.log("$scope 'this",this);
              //this of $scope
              $scope.message="Welcome User";
          }

        });
</script>
</head>
<body ng-app="myApp" >
<div ng-controller="ctrlExample">
       Comming From $SCOPE :{{firstName}}
       <br><br>
       Comming from $SCOPE:{{lastName}}
       <br><br>
       Should Come From Controller:{{nickName}}
       <p>
            Blank nickName is because nickName is attached to 
           'this' of controller.
       </p>

       <br><br>
       <button ng-click="controllerMethod()">Controller Method</button>

       <br><br>
       <button ng-click="show()">Show</button>
       <p>{{message}}</p>

   </div>

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