AngularJS 1.3 $watchが監視できるものは?

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,745

Takahashi-Ei

score 52

はじめてお世話になります。

親コントローラを用いずに、$watchを使って、コントローラ間の値を受け渡す実験をしています。

<!DOCTYPE html>
<html lang="ja" ng-app="app">
<head>
    <meta charset="utf-8" />
    <title>テスト</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.20/angular.js"></script>
    <script>
        var app = angular.module('app', []);
        app.value('myValue', '');
        app.controller('InputController', ['$scope', 'myValue', function($scope, myValue){
            $scope.onInputChange = function(){
                myValue = $scope.myInput;
            }
        }]);
        app.controller('OutputController', ['$scope', 'myValue', function($scope, myValue){
            $scope.$watch(function(){
                return myValue;
            }, function(){
                $scope.myOutput = myValue;
            });
        }]);
    </script>
</head>
<body>
    <div ng-controller="InputController">
        <input ng-model="myInput" ng-change="onInputChange()" type="text" />
    </div>
    <div ng-controller="OutputController">
        {{ myOutput }}
    </div>
</body>
</html>


Module#value経由で、InputControllerのプロパティの値をOutputControllerのプロパティに受け渡したいのですが、上記のように、Module#valueの値が生の文字列では、$watchのリスナーが発火せず、うまくいきませんでした。

そこで、scriptタグ間を下記のように書き換えて、

var app = angular.module('app', []);
app.value('myValue', {value: ''});
app.controller('InputController', ['$scope', 'myValue', function($scope, myValue){
    $scope.onInputChange = function(){
        myValue.value = $scope.myInput;
    }
}]);
app.controller('OutputController', ['$scope', 'myValue', function($scope, myValue){
    $scope.$watch(function(){
        return myValue.value;
    }, function(){
        $scope.myOutput = myValue.value;
    });
}]);


Module#valueの値をオブジェクトにして、そのメンバを媒介にしてやるとうまくいきました。

やってることは一緒だと思うのですが、一体なぜこのようなことが起こるのでしょうか?
$watchの最初の引数である無名関数を、逐一検査しているわけではないのですか?

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

+1

(このコメントには、明確な回答は記載されておりません。。。 ごめんなさい)

下記箇所にて値を確認してみると、今回の問題の原因が見えてくるのではないでしょうか。

  1. $scope.onInputChange の myValue をセットした後の myValue値
  2. $scope.myOutput にセットした後の myValue 値
var app = angular.module('app', []);
app.value('myValue', '');         // 値を設定した場合
app.value('myObject', {value:""});  // オブジェクトを設定した場合
app.controller('InputController', ['$scope', 'myValue', 'myObject', function($scope, myValue, myObject){
    $scope.onInputChange = function(){
        myValue = $scope.myInput;
        myObject.value = $scope.myInput;
        console.log("InputController:", myValue, myObject)
    }
}]);
app.controller('OutputController', ['$scope', 'myValue', 'myObject', function($scope, myValue, myObject){

    // myValue が変更されたかどうかを監視する処理
    $scope.$watch(function(){
        return myValue;
    }, function(){
      // myValue, myHash の値を確認
      console.log("OutputController 1:", myValue, myObject)
    });

    // myHash が変更されたかどうかを監視する処理
    $scope.$watch(function(){
      return myObject.value;
    }, function(){
      // myValue, myHash の値を確認
      console.log("OutputController 2:", myValue, myObject)
    });
}]);
// "a" を入力
"InputController:" "a" Object { value: "a" } test.html:15:16
"OutputController 2:" "" Object { value: "a" } test.html:28:14
// "a" を入力 (2回目)
"InputController:" "aa" Object { value: "aa" } test.html:15:16
"OutputController 2:" "" Object { value: "aa" } test.html:28:14
// "a" を入力 (3回目)
"InputController:" "aaa" Object { value: "aaa" } test.html:15:16
"OutputController 2:" "" Object { value: "aaa" } test.html:

結果の通り、InputController: myValue は変更されているにもかかわらず、OutputController: myValue の値は変更されていないようですね。
→ InputController:myValue と OutputController:myValue は正しくシェアリングできておらず、別物となっているようです。

$watchの最初の引数である無名関数を、逐一検査しているわけではないのですか?

正確には、何らかの変更が発生した際、第一引数(の要素?)が変更されたかどうかを確認しているのです。


でわ、何故こんなこと(正しくシェアリングができていない)になったのかと聞かれると、非常に説明しにくいところなのです。。。

関数の呼び出し時に引数を渡した場合、「プリミティブ型は値渡し」「オブジェクト型は参照渡し」(Javascript)となるのはご存知でしょうか。
おそらくはこの辺りが原因ではないかと考えております。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/15 14:01

    ご回答ありがとうございます。

    > 正確には、何らかの変更が発生した際、第一引数(の要素?)が変更されたかどうかを確認しているのです。

    つまり、プリミティブ型は監視の対象にならないということでしょうか?

    > 関数の呼び出し時に引数を渡した場合、「プリミティブ型は値渡し」「オブジェクト型は参照渡し」(Javascript)となるのはご存知でしょうか。
    おそらくはこの辺りが原因ではないかと考えております。

    そうかもしれませんね。そう書いてあるソースがあればいいのですが…。

    キャンセル

  • 2016/02/16 09:07

    > つまり、プリミティブ型は監視の対象にならないということでしょうか?

    これもまた、混乱させる原因なのですが、、、同一コントローラー内での watch は正しく監視されます。
    InputController 内で $scope.watch(myValue, function() {}) は正しく判断されます。

    「正しく $watch 出来ない」というよりかは、「app.value で定義した変数が正しく共有化できていない」と考えたほうがよさそうです。

    つまり、異なるコントローラー間でデータ共有する場合、質問者様のお題に合った通り、プリミティブ型ではなくオブジェクト型を利用したほうが安全ということです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる