AngularJSコントローラー間でデータを共有する


362

コントローラ間でデータを共有しようとしています。ユースケースはマルチステップ形式であり、1つの入力に入力されたデータは、後で元のコントローラーの外部の複数の表示場所で使用されます。以下とjsfiddleのコードはここにあります

HTML

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="FirstName"><!-- Input entered here -->
    <br>Input is : <strong>{{FirstName}}</strong><!-- Successfully updates here -->
</div>

<hr>

<div ng-controller="SecondCtrl">
    Input should also be here: {{FirstName}}<!-- How do I automatically updated it here? -->
</div>

JS

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// make a factory to share data between controllers
myApp.factory('Data', function(){
    // I know this doesn't work, but what will?
    var FirstName = '';
    return FirstName;
});

// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

どんな助けでも大歓迎です。


回答:


481

簡単な解決策は、ファクトリーにオブジェクトを返させ、コントローラーに同じオブジェクトへの参照を機能させることです。

JS:

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// Create the factory that share the Fact
myApp.factory('Fact', function(){
  return { Field: '' };
});

// Two controllers sharing an object that has a string in it
myApp.controller('FirstCtrl', function( $scope, Fact ){
  $scope.Alpha = Fact;
});

myApp.controller('SecondCtrl', function( $scope, Fact ){
  $scope.Beta = Fact;
});

HTML:

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="Alpha.Field">
    First {{Alpha.Field}}
</div>

<div ng-controller="SecondCtrl">
<input type="text" ng-model="Beta.Field">
    Second {{Beta.Field}}
</div>

デモ: http //jsfiddle.net/HEdJF/

アプリケーションが大きくなり、より複雑でテストが困難になった場合、この方法でファクトリーからオブジェクト全体を公開するのではなく、たとえばゲッターやセッターなどを介して制限されたアクセスを許可します。

myApp.factory('Data', function () {

    var data = {
        FirstName: ''
    };

    return {
        getFirstName: function () {
            return data.FirstName;
        },
        setFirstName: function (firstName) {
            data.FirstName = firstName;
        }
    };
});

このアプローチでは、新しい値でファクトリを更新し、それらを取得するための変更を監視するのは、消費するコントローラー次第です。

myApp.controller('FirstCtrl', function ($scope, Data) {

    $scope.firstName = '';

    $scope.$watch('firstName', function (newValue, oldValue) {
        if (newValue !== oldValue) Data.setFirstName(newValue);
    });
});

myApp.controller('SecondCtrl', function ($scope, Data) {

    $scope.$watch(function () { return Data.getFirstName(); }, function (newValue, oldValue) {
        if (newValue !== oldValue) $scope.firstName = newValue;
    });
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="firstName">
  <br>Input is : <strong>{{firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{firstName}}
</div>

デモ: http : //jsfiddle.net/27mk1n1o/


2
最初の解決策は私にとって最もうまくいきました-サービスがオブジェクトを維持し、コントローラーが参照で処理するだけでした ムチョありがとう。
johnkeese 14

5
$ scope。$ watchメソッドは、1つのスコープから
REST

オブジェクトのようにreturn { FirstName: '' };オブジェクトを返すか、オブジェクトとやり取りするメソッドを含むオブジェクトを返す代わりに、クラス(function)のインスタンスを返すことは好ましくないので、オブジェクトを使用すると、特定の型があり、それだけではありません「オブジェクト{}」?
RPDeshaies 2015年

8
ヘッドアップするだけで、newValue !== oldValueoldValueとnewValueが同じ場合、Angularは関数を実行しないため、リスナー関数はチェックを必要としません。これの唯一の例外は、初期値を気にする場合です。参照:docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.

@tasseKATT、ページを更新しなければ問題なく動作します。ページを更新した場合、解決策を教えていただけますか???
2015

71

使用したくない $watchはこれします。サービス全体をコントローラーのスコープに割り当てる代わりに、データのみを割り当てることができます。

JS:

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

myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    }
    // Other methods or objects can go here
  };
});

myApp.controller('FirstCtrl', function($scope, MyService){
  $scope.data = MyService.data;
});

myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="data.firstName">
  <br>Input is : <strong>{{data.firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{data.firstName}}
</div>

または、直接的な方法でサービスデータを更新することもできます。

JS:

// A new factory with an update method
myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    },
    update: function(first, last) {
      // Improve this method as needed
      this.data.firstName = first;
      this.data.lastName = last;
    }
  };
});

// Your controller can use the service's update method
myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;

   $scope.updateData = function(first, last) {
     MyService.update(first, last);
   }
});

明示的にwatch()を使用しませんが、内部的にAngularJSは$ scope変数の新しい値でビューを更新します。パフォーマンスに関しては同じかどうか?
Mart Dominguez、2015年

4
どちらの方法のパフォーマンスもわかりません。私が理解しているところによると、Angularは既にスコープでダイジェストループを実行しているため、追加のウォッチ式を設定すると、Angularの作業が増える可能性があります。実際に調べるには、パフォーマンステストを実行する必要があります。$ watch式による更新よりも、上記の回答によるデータ転送と共有を好むだけです。
ベニック

2
これは$ watchの代わりにはなりません。これは、$ watchを使用せずに2つのAngularオブジェクト(この場合はコントローラー)間でデータを共有する別の方法です。
ベニック2015

1
IMOこのソリューションは、$ watchステートメントを追加するのではなく、Angularの宣言型アプローチの精神に基づいているため、最良のソリューションです。
JamesG、2015

8
なぜこれが最も投票された答えではないのでしょうか。結局、$ watchはコードをより複雑にします
Shyamal Parikh

11

コントローラ間でデータを共有する方法はたくさんあります

  1. サービスの使用
  2. $ state.goサービスの使用
  3. stateparamsの使用
  4. ルートスコープを使用する

各方法の説明:

  1. 誰かがすでに説明したので、説明しません

  2. を使用して $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
  3. $stateparamと同様に機能し、$state.go送信者コントローラからオブジェクトとして渡し、stateparamを使用して受信者コントローラに収集します

  4. を使用して $rootscope

    (a)子から親コントローラーへのデータの送信

      $scope.Save(Obj,function(data) {
          $scope.$emit('savedata',data); 
          //pass the data as the second parameter
      });
    
      $scope.$on('savedata',function(event,data) {
          //receive the data as second parameter
      }); 

    (b)親から子コントローラーへのデータの送信

      $scope.SaveDB(Obj,function(data){
          $scope.$broadcast('savedata',data);
      });
    
      $scope.SaveDB(Obj,function(data){`enter code here`
          $rootScope.$broadcast('saveCallback',data);
      });

6

ルートパスのパターン間の共有スコープを制御するファクトリを作成したので、ユーザーが同じルートの親パスに移動しているときにも共有データを維持できます。

.controller('CadastroController', ['$scope', 'RouteSharedScope',
    function($scope, routeSharedScope) {
      var customerScope = routeSharedScope.scopeFor('/Customer');
      //var indexScope = routeSharedScope.scopeFor('/');
    }
 ])

そのため、ユーザーが「/ Support」などの別のルートパスにアクセスすると、パス「/ Customer」の共有データは自動的に破棄されます。ただし、これの代わりに、ユーザーが「/ Customer / 1」や「/ Customer / list」などの「子」パスに移動した場合、スコープは破棄されません。

ここでサンプルを見ることができます:http : //plnkr.co/edit/OL8of9


$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })ui.routerがある場合に使用
Nuno Silva

6

コントローラ間でデータを共有する方法は複数あります

  • 角度サービス
  • $ broadcast、$ emitメソッド
  • 親から子へのコントローラー通信
  • $ rootscope

みなさんご存じのとおり $rootscope、それは全体のアプリケーションのために利用可能であるグローバルスコープであるので、データ転送または通信のために好ましい方法ではありません

Angular Jsコントローラー間のデータ共有の場合、Angularサービスはベストプラクティスです。.factory.service
について参照

子コントローラへの親からのデータ転送の場合では、子コントローラで直接アクセス親データをすることができて$scope
、あなたが使用している場合はui-router、あなたが使用できる$stateParmasようにURLパラメータを渡すとidnamekey、など

$broadcast 親から子にコントローラ間でデータを転送するための良い方法であり、 $emitデータを転送したり、子から親コントローラーにデータを転送しするのに

HTML

<div ng-controller="FirstCtrl">
   <input type="text" ng-model="FirstName">
   <br>Input is : <strong>{{FirstName}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
   Input should also be here: {{FirstName}}
</div>

JS

myApp.controller('FirstCtrl', function( $rootScope, Data ){
    $rootScope.$broadcast('myData', {'FirstName': 'Peter'})
});

myApp.controller('SecondCtrl', function( $rootScope, Data ){
    $rootScope.$on('myData', function(event, data) {
       $scope.FirstName = data;
       console.log(data); // Check in console how data is coming
    });
});

詳細については、所定のリンクを参照してください$broadcast


4

最も簡単なソリューション:

AngularJSサービスを使用しました。

ステップ1: SharedDataServiceという名前のAngularJSサービスを作成しました。

myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

ステップ2: 2つのコントローラーを作成し、上記で作成したサービスを使用します。

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

ステップ3:作成したコントローラーをビューで使用するだけです。

<body ng-app="myApp">

<div ng-controller="FirstCtrl">
<input type="text" ng-model="Person.name">
<br>Input is : <strong>{{Person.name}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
Input should also be here: {{Person.name}}
</div>

</body>

この問題の解決策を確認するには、下のリンクをクリックしてください

https://codepen.io/wins/pen/bmoYLr

.htmlファイル:

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="myApp">

  <div ng-controller="FirstCtrl">
    <input type="text" ng-model="Person.name">
    <br>Input is : <strong>{{Person.name}}</strong>
   </div>

<hr>

  <div ng-controller="SecondCtrl">
    Input should also be here: {{Person.name}}
  </div>

//Script starts from here

<script>

var myApp = angular.module("myApp",[]);
//create SharedDataService
myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
    }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
}]);

</script>


</body>
</html>

1
これは、angularJを使用する新しい人々にとって非常に明確な例です。親子関係のデータをどのように共有/渡しますか? FirstCtrl親であり、SecondCtrl(子供)も必要になるという約束を得る?2つのAPI呼び出しを行いたくありません。ありがとう。
Chris22、19年

1

$ watchを使用せずにangular.copyを使用する別の方法があります。

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

myApp.factory('Data', function(){

    var service = {
        FirstName: '',
        setFirstName: function(name) {
            // this is the trick to sync the data
            // so no need for a $watch function
            // call this from anywhere when you need to update FirstName
            angular.copy(name, service.FirstName); 
        }
    };
    return service;
});


// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

1

これには複数の方法があります。

  1. イベント-すでによく説明されています。

  2. uiルーター-上記で説明。

  3. サービス-更新メソッドが上に表示されています
  4. 悪い -変更を監視しています。
  5. 放って放映するのではなく、別の親子アプローチ-

*

<superhero flight speed strength> Superman is here! </superhero>
<superhero speed> Flash is here! </superhero>

*

app.directive('superhero', function(){
    return {
        restrict: 'E',
        scope:{}, // IMPORTANT - to make the scope isolated else we will pollute it in case of a multiple components.
        controller: function($scope){
            $scope.abilities = [];
            this.addStrength = function(){
                $scope.abilities.push("strength");
            }
            this.addSpeed = function(){
                $scope.abilities.push("speed");
            }
            this.addFlight = function(){
                $scope.abilities.push("flight");
            }
        },
        link: function(scope, element, attrs){
            element.addClass('button');
            element.on('mouseenter', function(){
               console.log(scope.abilities);
            })
        }
    }
});
app.directive('strength', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addStrength();
        }
    }
});
app.directive('speed', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addSpeed();
        }
    }
});
app.directive('flight', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addFlight();
        }
    }
});

0

このパターンをどこで見つけたのかはわかりませんが、コントローラー間でデータを共有し、$ rootScopeと$ scopeを削減するために、これはうまく機能します。これは、パブリッシャーとサブスクライバーがあるデータレプリケーションを連想させます。それが役に立てば幸い。

サービス:

(function(app) {
    "use strict";
    app.factory("sharedDataEventHub", sharedDataEventHub);

    sharedDataEventHub.$inject = ["$rootScope"];

    function sharedDataEventHub($rootScope) {
        var DATA_CHANGE = "DATA_CHANGE_EVENT";
        var service = {
            changeData: changeData,
            onChangeData: onChangeData
        };
        return service;

        function changeData(obj) {
            $rootScope.$broadcast(DATA_CHANGE, obj);
        }

        function onChangeData($scope, handler) {
            $scope.$on(DATA_CHANGE, function(event, obj) {
                handler(obj);
            });
        }
    }
}(app));

新しいデータを取得するコントローラー、つまりパブリッシャーは、次のようなことを行います。

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

サブスクライバーと呼ばれるこの新しいデータも使用しているコントローラーは、次のようになります...

sharedDataEventHub.onChangeData($scope, function(data) {
    vm.localData.Property1 = data.Property1;
    vm.localData.Property2 = data.Property2;
});

これはどのシナリオでも機能します。したがって、プライマリコントローラーが初期化されてデータを取得すると、changeDataメソッドが呼び出され、そのデータのすべてのサブスクライバーにブロードキャストされます。これにより、コントローラー同士の結合が減少します。


コントローラー間で状態を共有する「モデル」を使用することは、主に使用されるオプションのようです。残念ながら、これは子コントローラーを更新するシナリオには対応していません。サーバーからデータを「再フェッチ」してモデルを再作成するにはどうすればよいですか?そして、キャッシュの無効化にどのように対処しますか?
Andrei

親コントローラーと子コントローラーの考え方は少し危険で、相互依存が多すぎると思います。ただし、UI-Routerと組み合わせて使用​​すると、特に「データ」が状態設定を介して注入される場合、「子」のデータを簡単に更新できます。キャッシュの無効化はパブリッシャーで発生し、データの取得と呼び出しを担当しますsharedDataEventHub.changeData(newData)
ewahner

誤解があると思います。リフレッシュとは、ユーザーが文字通りキーボードのF5キーを押し、ブラウザにポストバックを行わせることを意味します。これが発生したときに子/詳細ビューが表示されている場合は、「var someData = yourDataService.getSomeData()」を呼び出すユーザーがいません。問題は、このシナリオにどのように対処するかということです。誰がモデルを更新する必要がありますか?
Andrei

コントローラーのアクティブ化方法によっては、これは非常に簡単な場合があります。アクティブ化するメソッドがある場合、最初にデータをロードするsharedDataEventHub.changeData(newData)コントローラーがそれをロードしてから、そのデータに依存するすべての子またはコントローラーをコントローラー本体のどこかに配置しsharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });ます。UIを使用した場合-Routerこれは状態の設定から注入される可能性があります。
ewahner、2015年

ここに私が説明しようとしている問題を説明する例があります:plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview。プランクをポップし、編集ページに移動してブラウザーを更新すると、クラッシュします。この場合、誰がモデルを再作成する必要がありますか?
Andrei

-3

単純にしてください(v1.3.15でテスト済み):

<article ng-controller="ctrl1 as c1">
    <label>Change name here:</label>
    <input ng-model="c1.sData.name" />
    <h1>Control 1: {{c1.sData.name}}, {{c1.sData.age}}</h1>
</article>
<article ng-controller="ctrl2 as c2">
    <label>Change age here:</label>
    <input ng-model="c2.sData.age" />
    <h1>Control 2: {{c2.sData.name}}, {{c2.sData.age}}</h1>
</article>

<script>
    var app = angular.module("MyApp", []);

    var dummy = {name: "Joe", age: 25};

    app.controller("ctrl1", function () {
        this.sData = dummy;
    });

    app.controller("ctrl2", function () {
        this.sData = dummy;
    });
</script>


5
これはモジュール式ではないため、反対票が投じられたと思います。dummy$ scopeやcontrollerAsを継承したり、サービスを使用したりすることで、よりモジュラーな方法で共有するのではなく、両方のコントローラーに対してグローバルです。
Chad Johnson、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.