Bagikan data antara pengontrol AngularJS


362

Saya mencoba berbagi data di seluruh pengontrol. Use-case adalah bentuk multi-langkah, data yang dimasukkan dalam satu input kemudian digunakan di beberapa lokasi tampilan di luar pengontrol asli. Kode di bawah dan di jsfiddle di sini .

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

Setiap bantuan sangat dihargai.


Jawaban:


481

Solusi sederhana adalah membuat pabrik Anda mengembalikan objek dan membiarkan controller Anda bekerja dengan referensi ke objek yang sama:

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>

Demo: http://jsfiddle.net/HEdJF/

Ketika aplikasi menjadi lebih besar, lebih kompleks dan lebih sulit untuk diuji, Anda mungkin tidak ingin mengekspos seluruh objek dari pabrik dengan cara ini, tetapi sebaliknya memberikan akses terbatas misalnya melalui getter dan setter:

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

    var data = {
        FirstName: ''
    };

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

Dengan pendekatan ini, tergantung pada pengontrol yang mengonsumsi untuk memperbarui pabrik dengan nilai-nilai baru, dan untuk melihat perubahan untuk mendapatkannya:

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>

Demo: http://jsfiddle.net/27mk1n1o/


2
Solusi pertama bekerja paling baik untuk saya - memiliki layanan mempertahankan objek dan pengendali hanya menangani referensi. Terima kasih banyak.
johnkeese

5
metode $ scope. $ watch berfungsi dengan baik untuk melakukan panggilan istirahat dari satu ruang lingkup dan menerapkan hasilnya ke ruang lingkup lain
aclave1

Alih-alih mengembalikan objek seperti return { FirstName: '' };atau mengembalikan objek yang berisi metode yang berinteraksi dengan objek, bukankah lebih disukai untuk mengembalikan turunan kelas ( function) sehingga ketika Anda menggunakan objek Anda, objek memiliki tipe tertentu dan tidak hanya Sebuah Objek{}" ?
RPDeshaies

8
Hanya kepala saja, fungsi pendengar tidak perlu newValue !== oldValuepemeriksaan karena Angular tidak menjalankan fungsi jika oldValue dan newValue sama. Satu-satunya pengecualian untuk ini adalah jika Anda peduli dengan nilai awal. Lihat: docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.

@tasseKATT, Ini berfungsi dengan baik, jika saya tidak menyegarkan halaman. Bisakah Anda menyarankan saya solusi jika saya menyegarkan halaman ???
Nyonya

71

Saya lebih suka tidak menggunakan $watchuntuk ini. Alih-alih menugaskan seluruh layanan ke ruang lingkup pengontrol Anda dapat menetapkan hanya data.

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>

Atau Anda dapat memperbarui data layanan dengan metode langsung.

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

Anda tidak menggunakan menonton secara eksplisit () tetapi secara internal AngularJS tidak memperbarui tampilan dengan nilai-nilai baru dalam variabel $ scope. Dari segi kinerja sama atau tidak?
Mart Dominguez

4
Saya tidak yakin dengan kinerja kedua metode tersebut. Dari apa yang saya pahami, Angular sudah menjalankan loop intisari pada ruang lingkup sehingga menyiapkan ekspresi arloji tambahan mungkin menambah lebih banyak pekerjaan untuk Angular. Anda harus menjalankan tes kinerja untuk benar-benar mengetahuinya. Saya hanya lebih suka transfer data dan berbagi melalui jawaban di atas daripada memperbarui melalui ekspresi $ watch.
bennick

2
Ini bukan alternatif untuk $ menonton. Ini adalah cara berbeda untuk berbagi data antara dua objek Angular, dalam hal ini pengendali, tanpa harus menggunakan $ watch.
bennick

1
IMO solusi ini adalah yang terbaik karena lebih dalam semangat pendekatan deklaratif Angular, dibandingkan dengan menambahkan pernyataan $ watch, yang terasa sedikit lebih jQuery-esque.
JamesG

8
Mengapa ini bukan jawaban yang paling banyak dipilih, saya bertanya-tanya. Setelah semua $ menonton membuat kode lebih kompleks
Shyamal Parikh

11

Ada banyak cara Anda dapat berbagi data antara pengontrol

  1. menggunakan layanan
  2. menggunakan layanan $ state.go
  3. menggunakan stateparams
  4. menggunakan rootscope

Penjelasan dari masing-masing metode:

  1. Saya tidak akan menjelaskan seperti yang sudah dijelaskan oleh seseorang

  2. menggunakan $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
  3. $stateparambekerja dengan cara yang mirip dengan $state.go, Anda meneruskannya sebagai objek dari pengontrol pengirim dan mengumpulkan di pengontrol penerima menggunakan stateparam

  4. menggunakan $rootscope

    (a) mengirim data dari pengontrol anak ke induk

      $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) mengirim data dari induk ke pengendali anak

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

6

Saya telah membuat pabrik yang mengontrol ruang lingkup bersama antara pola jalur rute, sehingga Anda dapat mempertahankan data yang dibagikan tepat saat pengguna menavigasi di jalur induk rute yang sama.

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

Jadi, jika pengguna pergi ke jalur rute lain, misalnya '/ Dukungan', data bersama untuk jalur '/ Pelanggan' akan secara otomatis dihancurkan. Tetapi, jika alih-alih ini, pengguna pergi ke jalur 'anak', seperti '/ Pelanggan / 1' atau '/ Pelanggan / daftar' lingkup tidak akan hancur.

Anda dapat melihat contoh di sini: http://plnkr.co/edit/OL8of9


Gunakan $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })jika Anda memiliki ui.router
Nuno Silva

6

Ada beberapa cara untuk berbagi data antara pengontrol

  • Layanan sudut
  • $ broadcast, metode $ emit
  • Komunikasi pengendali orangtua ke anak
  • $ rootscope

Seperti yang kita tahu $rootscope bukanlah cara yang lebih disukai untuk transfer data atau komunikasi karena ini adalah ruang lingkup global yang tersedia untuk seluruh aplikasi

Untuk berbagi data antara pengontrol Angular Js Layanan Angular adalah praktik terbaik, misalnya. .factory, .service
Untuk referensi

Dalam hal transfer data dari induk ke pengendali anak, Anda dapat langsung mengakses data induk dalam pengendali anak $scope
Jika Anda menggunakan ui-routermaka Anda dapat menggunakan $stateParmasuntuk melewati parameter url seperti id, name,key , dll

$broadcast juga cara yang baik untuk mentransfer data antara pengontrol dari orangtua ke anak dan $emit untuk mentransfer data dari pengontrol anak ke orang tua

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

Lihat tautan yang diberikan untuk tahu lebih banyak tentang$broadcast


4

Solusi paling sederhana:

Saya telah menggunakan layanan AngularJS .

Langkah1: Saya telah membuat layanan AngularJS bernama SharedDataService.

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

    };
    return Person;
});

Langkah2: Buat dua pengontrol dan gunakan layanan yang dibuat di atas.

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

Langkah 3: Cukup gunakan pengontrol yang dibuat dalam tampilan.

<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>

Untuk melihat solusi yang berfungsi untuk masalah ini, silakan tekan tautan di bawah ini

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

file .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
ini adalah contoh yang sangat jelas bagi kita orang baru yang menggunakan angularJs. Bagaimana Anda membagikan / meneruskan data dari hubungan orang tua / anak? FirstCtrlApakah orang tua dan mendapat janji bahwa SecondCtrl(anak) juga perlu? Saya tidak ingin melakukan dua panggilan api. Terima kasih.
Chris22

1

Ada cara lain tanpa menggunakan $ watch, menggunakan 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

Ada beberapa cara untuk melakukan ini.

  1. Acara - sudah dijelaskan dengan baik.

  2. router ui - dijelaskan di atas.

  3. Layanan - dengan metode pembaruan yang ditampilkan di atas
  4. BURUK - Memperhatikan perubahan.
  5. Pendekatan anak orang tua lain daripada memancarkan dan brodcast -

*

<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

Tidak yakin di mana saya mengambil pola ini tetapi untuk berbagi data di seluruh pengontrol dan mengurangi $ rootScope dan $ scope ini berfungsi dengan baik. Ini mengingatkan pada replikasi data di mana Anda memiliki penerbit dan pelanggan. Semoga ini bisa membantu.

Layanan:

(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));

Pengontrol yang mendapatkan data baru, yaitu Penerbit akan melakukan sesuatu seperti ini ..

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

Pengontrol yang juga menggunakan data baru ini, yang disebut Pelanggan akan melakukan sesuatu seperti ini ...

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

Ini akan berfungsi untuk skenario apa pun. Jadi ketika pengendali utama diinisialisasi dan mendapat data itu akan memanggil metode changeData yang kemudian akan menyiarkan itu ke semua pelanggan data itu. Ini mengurangi kopling pengontrol kami satu sama lain.


menggunakan 'model' yang berbagi keadaan antara pengontrol sepertinya opsi yang banyak digunakan. Sayangnya ini tidak mencakup skenario di mana Anda akan menyegarkan pengendali anak. Bagaimana Anda 'mengambil kembali' data dari server dan membuat ulang model? Dan bagaimana Anda menangani pembatalan cache?
Andrei

Saya pikir ide pengendali orang tua dan anak agak berbahaya dan menyebabkan terlalu banyak ketergantungan satu sama lain. Namun, ketika digunakan dalam kombinasi dengan UI-Router, Anda dapat dengan mudah me-refresh data pada "anak" Anda, terutama jika data itu disuntikkan melalui konfigurasi keadaan. Pembatalan Cache terjadi pada penerbit di mana mereka akan bertanggung jawab untuk mengambil data dan meneleponsharedDataEventHub.changeData(newData)
ewahner

Saya pikir ada kesalahpahaman. Maksud saya adalah pengguna benar-benar menekan tombol F5 pada keyboard dan menyebabkan browser melakukan posting kembali. Jika Anda berada di tampilan anak / detail saat ini terjadi, tidak ada yang memanggil "var someData = yourDataService.getSomeData ()". Pertanyaannya adalah, bagaimana Anda menangani skenario ini? siapa yang harus memperbarui model?
Andrei

Bergantung pada bagaimana Anda mengaktifkan pengontrol Anda, ini bisa sangat sederhana. Jika Anda memiliki metode yang diaktifkan maka controller yang bertanggung jawab untuk memuat data pada awalnya, akan memuatnya dan kemudian menggunakan sharedDataEventHub.changeData(newData)anak atau pengendali yang bergantung pada data tersebut yang akan memiliki suatu tempat di tubuh controller mereka sebagai berikut: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });Jika Anda menggunakan UI -Router ini bisa disuntikkan dari konfigurasi negara.
ewahner

Berikut adalah contoh yang menggambarkan masalah yang saya coba uraikan: plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . Jika Anda menghapus plunk, navigasikan ke halaman edit dan segarkan browser, itu akan macet. Siapa yang harus membuat ulang model dalam kasus ini?
Andrei

-3

Lakukan saja secara sederhana (diuji dengan 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
Saya pikir ini turun karena tidak modular. dummybersifat global untuk kedua pengendali daripada dibagi dengan cara yang lebih modular melalui pewarisan dengan $ scope atau controllerAs atau menggunakan layanan.
Chad Johnson
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.