Condividi i dati tra i controller AngularJS


362

Sto cercando di condividere i dati tra i controller. Il caso d'uso è un modulo in più passaggi, i dati immessi in un input vengono successivamente utilizzati in più posizioni di visualizzazione all'esterno del controller originale. Codice qui sotto e in jsfiddle qui .

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

Qualsiasi aiuto è molto apprezzato.


Risposte:


481

Una soluzione semplice è fare in modo che la fabbrica restituisca un oggetto e permetta ai controller di funzionare con un riferimento allo stesso oggetto:

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/

Quando le applicazioni diventano più grandi, più complesse e più difficili da testare, potresti non voler esporre l'intero oggetto dalla fabbrica in questo modo, ma invece dare un accesso limitato, ad esempio tramite getter e setter:

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

    var data = {
        FirstName: ''
    };

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

Con questo approccio spetta ai responsabili del consumo aggiornare la fabbrica con nuovi valori e controllare le modifiche per ottenerli:

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
La prima soluzione ha funzionato meglio per me: il servizio ha mantenuto l'oggetto e i controller hanno gestito il riferimento. Mille grazie.
johnkeese,

5
il metodo $ scope. $ watch funziona magnificamente per effettuare una chiamata di riposo da un ambito e applicare i risultati a un altro ambito
aclave1

Invece di restituire un oggetto simile return { FirstName: '' };o restituire un oggetto contenente metodi che interagiscono con un oggetto, non sarebbe preferibile restituire un'istanza di una classe ( function) in modo che quando si utilizza l'oggetto, abbia un tipo specifico e non sia solo un oggetto{}" ?
RPDeshaies

8
Solo un avvertimento, la funzione di ascolto non ha bisogno del newValue !== oldValuecontrollo perché Angular non esegue la funzione se oldValue e newValue sono uguali. L'unica eccezione a questo è se ti preoccupi del valore iniziale. Vedi: docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.

@tasseKATT, Funziona bene, se non aggiorno la pagina. Potete suggerirmi qualche soluzione se aggiorno la pagina ???
MaNn,

71

Preferisco non usarlo $watchper questo. Invece di assegnare l'intero servizio all'ambito di un controller è possibile assegnare solo i dati.

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>

In alternativa è possibile aggiornare i dati di servizio con un metodo diretto.

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

Non usi esplicitamente watch () ma internamente AngularJS lo fa per aggiornare la vista con nuovi valori nelle variabili $ scope. In termini di prestazioni è uguale o no?
Mart Dominguez,

4
Non sono sicuro delle prestazioni di entrambi i metodi. Da quanto ho capito, Angular sta già eseguendo un ciclo digest sull'oscilloscopio, quindi l'impostazione di espressioni di controllo aggiuntive probabilmente aggiunge più lavoro per Angular. Dovresti eseguire un test delle prestazioni per scoprirlo davvero. Preferisco semplicemente il trasferimento e la condivisione dei dati tramite la risposta sopra rispetto all'aggiornamento tramite espressioni $ watch.
bennick,

2
Questa non è un'alternativa a $ watch. È un modo diverso di condividere i dati tra due oggetti angolari, in questo caso i controller, senza dover usare $ watch.
bennick,

1
IMO questa soluzione è la migliore perché è più nello spirito dell'approccio dichiarativo di Angular, invece di aggiungere dichiarazioni $ watch, che sembrano un po 'più jQuery-esque.
JamesG,

8
Perché questa non è la risposta più votata che mi chiedo. Dopo tutto $ watch rende il codice più complesso
Shyamal Parikh,

11

Esistono molti modi per condividere i dati tra i controller

  1. utilizzando i servizi
  2. utilizzando i servizi $ state.go
  3. usando stateparams
  4. usando il rootcope

Spiegazione di ciascun metodo:

  1. Non ho intenzione di spiegare come è già spiegato da qualcuno

  2. utilizzando $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
  3. $stateparamfunziona in modo simile a $state.go, lo passi come oggetto dal controller del mittente e lo raccogli nel controller del ricevitore usando stateparam

  4. utilizzando $rootscope

    (a) invio di dati dal bambino al controllore controllore

      $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) invio di dati dal controllore principale a quello minore

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

6

Ho creato una fabbrica che controlla l'ambito condiviso tra il modello del percorso, così puoi mantenere i dati condivisi proprio quando gli utenti navigano nello stesso percorso principale del percorso.

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

Pertanto, se l'utente passa a un altro percorso, ad esempio "/ Supporto", i dati condivisi per il percorso "/ Cliente" verranno automaticamente distrutti. Tuttavia, se invece l'utente passa a percorsi "secondari", come "/ Cliente / 1" o "/ Cliente / elenco", l'ambito non verrà distrutto.

Puoi vedere un esempio qui: http://plnkr.co/edit/OL8of9


Usa $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })se hai ui.router
Nuno Silva il

6

Esistono diversi modi per condividere i dati tra i controller

  • Servizi angolari
  • $ broadcast, $ emit method
  • Comunicazione del controllore da padre a figlio
  • $ rootscope

Come sappiamo $rootscope non è un modo preferibile per il trasferimento o la comunicazione dei dati perché è un ambito globale che è disponibile per l'intera applicazione

Per la condivisione dei dati tra i controller di Angular Js, i servizi angolari sono le migliori pratiche, ad es. .factory, .service
Per riferimento

In caso di trasferimento dei dati da genitore a figlio di controllo è possibile dati principali direttamente accesso a controller di bambino attraverso $scope
Se si utilizza ui-router, è possibile utilizzare $stateParmasper passare i parametri URL come id, name, key, ecc

$broadcastè anche un buon modo per trasferire dati tra controllori da padre a figlio e $emittrasferire dati da figlio a controllori genitore

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

Consultare il link indicato per saperne di più$broadcast


4

Soluzione più semplice:

Ho usato un servizio AngularJS .

Step1: Ho creato un servizio AngularJS chiamato SharedDataService.

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

    };
    return Person;
});

Passaggio 2: creare due controller e utilizzare il servizio sopra creato.

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

Passaggio 3: utilizzare semplicemente i controller creati nella vista.

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

Per vedere la soluzione funzionante a questo problema, premi il link qui sotto

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
questo è un chiaro esempio per noi nuovi folk che usano angularJs. Come condivideresti / passeresti i dati da una relazione padre / figlio? FirstCtrlè genitore e ha la promessa che anche SecondCtrl(figlio) sarà necessario? Non voglio fare due chiamate API. Grazie.
Chris22,

1

C'è un altro modo senza usare $ watch, usando 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

Esistono diversi modi per farlo.

  1. Eventi - già spiegati bene.

  2. ui router - spiegato sopra.

  3. Servizio: con il metodo di aggiornamento visualizzato sopra
  4. MALE - Osservando le modifiche.
  5. Un altro approccio figlio genitore piuttosto che emettere e 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

Non sono sicuro di dove ho preso questo modello, ma per la condivisione dei dati tra controller e la riduzione di $ rootScope e $ scope funziona alla grande. Ricorda una replica dei dati in cui hai editori e abbonati. Spero che sia d'aiuto.

Il servizio:

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

Il controller che sta ottenendo i nuovi dati, che è l'editore, farebbe qualcosa del genere.

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

Il controller che utilizza anche questi nuovi dati, che si chiama l'abbonato, farebbe qualcosa del genere ...

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

Questo funzionerà per qualsiasi scenario. Pertanto, quando il controller principale viene inizializzato e ottiene i dati, questo chiama il metodo changeData che lo trasmette a tutti gli abbonati di tali dati. Ciò riduce l'accoppiamento dei nostri controller tra loro.


l'utilizzo di un "modello" che condivide lo stato tra i controller sembra l'opzione maggiormente utilizzata. Purtroppo questo non copre lo scenario in cui si aggiorna il controller figlio. Come si "recuperano" i dati dal server e si ricrea il modello? E come gestiresti l'invalidazione della cache?
Andrei,

Penso che l'idea dei controllori genitori e figli sia un po 'pericolosa e causi troppe dipendenze l'una dall'altra. Tuttavia, se utilizzato in combinazione con UI-Router, è possibile aggiornare facilmente i dati sul "figlio", soprattutto se i dati vengono iniettati tramite la configurazione dello stato. L'annullamento della cache si verifica sull'editore in base al quale sarebbero responsabili del recupero dei dati e della chiamatasharedDataEventHub.changeData(newData)
ewahner,

Penso che ci sia un malinteso. Con aggiornamento intendo dire che gli utenti premono letteralmente il tasto F5 sulla tastiera e inducono il browser a rispondere. Se ti trovi nella vista figlio / dettagli quando ciò accade, non c'è nessuno a chiamare "var someData = yourDataService.getSomeData ()". La domanda è: come gestiresti questo scenario? chi dovrebbe aggiornare il modello?
Andrei,

A seconda di come attivi i controller, questo potrebbe essere molto semplice. Se si dispone di un metodo che attiva quindi il controller che è responsabile del caricamento iniziale dei dati, li caricherà e quindi utilizzerà sharedDataEventHub.changeData(newData)qualsiasi figlio o controller che dipende da quei dati avrebbe da qualche parte nel loro corpo del controller quanto segue: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });Se si utilizzava l'interfaccia utente -Router questo potrebbe essere iniettato dalla configurazione dello stato.
ewahner,

Ecco un esempio che illustra il problema che sto cercando di descrivere: plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . Se fai uscire il plunk, vai alla pagina di modifica e aggiorna il browser, si bloccherà. Chi dovrebbe ricreare il modello in questo caso?
Andrei,

-3

Fallo semplicemente (testato con 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
Penso che questo sia stato sottovalutato perché non è modulare. dummyè globale per entrambi i controller piuttosto che condiviso in modo più modulare tramite ereditarietà con $ scope o controllerAs o utilizzando un servizio.
Chad Johnson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.