Compartir datos entre controladores AngularJS

362

Estoy tratando de compartir datos entre los controladores. El caso de uso es un formulario de varios pasos, los datos ingresados ​​en una entrada se usan más tarde en múltiples ubicaciones de visualización fuera del controlador original. Código a continuación y en jsfiddle aquí .

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

Cualquier ayuda es muy apreciada.

johnkeese
fuente

Respuestas:

481

Una solución simple es que su fábrica devuelva un objeto y permita que sus controladores trabajen con una referencia al mismo objeto:

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>

Demostración: http://jsfiddle.net/HEdJF/

Cuando las aplicaciones se hacen más grandes, más complejas y más difíciles de probar, es posible que no desee exponer todo el objeto de la fábrica de esta manera, sino que, en cambio, otorgue acceso limitado, por ejemplo, a través de getters y setters:

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

    var data = {
        FirstName: ''
    };

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

Con este enfoque, corresponde a los controladores consumidores actualizar la fábrica con nuevos valores y observar los cambios para obtenerlos:

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>

Demostración: http://jsfiddle.net/27mk1n1o/

tasseKATT
fuente
2
La primera solución funcionó mejor para mí: hacer que el servicio mantuviera el objeto y los controladores simplemente manejaran con la referencia. Mucho gracias.
johnkeese
55
el método $ scope. $ watch funciona de maravilla para hacer una llamada de descanso desde un alcance y aplicar los resultados a otro alcance
aclave1
En lugar de devolver un objeto como return { FirstName: '' };o devolver un objeto que contiene métodos que interactúan con un objeto, ¿no sería preferible devolver una instancia de una clase ( function) para que cuando use su objeto, tenga un tipo específico y no solo sea un objeto{}" ?
RPDeshaies
8
Solo un aviso, la función de escucha no necesita la newValue !== oldValueverificación porque Angular no ejecuta la función si oldValue y newValue son iguales. La única excepción a esto es si le importa el valor inicial. Ver: docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.
@tasseKATT, funciona bien, si no actualizo la página. ¿Me puede sugerir alguna solución si actualizo la página?
MaNn
71

Prefiero no usar $watchpara esto. En lugar de asignar todo el servicio al alcance de un controlador, puede asignar solo los datos.

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>

Alternativamente, puede actualizar los datos del servicio con un método directo.

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);
   }
});
Bennick
fuente
No usa explícitamente watch () pero internamente AngularJS lo hace para actualizar la vista con nuevos valores en las variables $ scope. ¿En términos de rendimiento es igual o no?
Mart Domínguez,
44
No estoy seguro del rendimiento de ninguno de los métodos. Por lo que entiendo, Angular ya está ejecutando un bucle de resumen en el alcance, por lo que configurar expresiones de observación adicionales probablemente agrega más trabajo para Angular. Tendría que ejecutar una prueba de rendimiento para descubrirlo realmente. Simplemente prefiero la transferencia y el intercambio de datos a través de la respuesta anterior a la actualización a través de expresiones $ watch
bennick
2
Esta no es una alternativa a $ watch. Es una forma diferente de compartir datos entre dos objetos angulares, en este caso controladores, sin tener que usar $ watch.
bennick
1
En mi opinión, esta solución es la mejor porque está más en el espíritu del enfoque declarativo de Angular, en lugar de agregar declaraciones $ watch, que se siente un poco más jQuery-esque.
JamesG
8
¿Por qué no es esta la respuesta más votada? Me pregunto. Después de todo, $ watch hace que el código sea más complejo
Shyamal Parikh
11

Hay muchas maneras de compartir los datos entre los controladores.

  1. utilizando servicios
  2. utilizando los servicios de $ state.go
  3. usando stateparams
  4. usando el endoscopio

Explicación de cada método:

  1. No voy a explicar ya que alguien ya lo explicó

  2. utilizando $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
  3. $stateparamfunciona de manera similar $state.go, lo pasa como objeto desde el controlador del remitente y lo recopila en el controlador del receptor utilizando stateparam

  4. utilizando $rootscope

    (a) enviar datos del controlador hijo al padre

      $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) enviar datos del controlador principal al controlador secundario

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

Creé una fábrica que controla el alcance compartido entre el patrón de ruta de ruta, para que pueda mantener los datos compartidos justo cuando los usuarios navegan en la misma ruta principal de ruta.

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

Entonces, si el usuario va a otra ruta de ruta, por ejemplo '/ Soporte', los datos compartidos para la ruta '/ Cliente' se destruirán automáticamente. Pero, si en lugar de esto, el usuario va a rutas 'secundarias', como '/ Cliente / 1' o '/ Cliente / lista', el alcance no se destruirá.

Puede ver una muestra aquí: http://plnkr.co/edit/OL8of9

Oberdan Nunes
fuente
Úselo $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })si tiene ui.router
Nuno Silva
6

Existen múltiples formas de compartir datos entre controladores

  • Servicios angulares
  • $ broadcast, $ método de emisión
  • Comunicación de padre a hijo controlador
  • $ rootscope

Como sabemos, $rootscopeno es una forma preferible para la transferencia de datos o la comunicación porque es un alcance global que está disponible para toda la aplicación

Para compartir datos entre controladores Angular Js, los servicios angulares son las mejores prácticas, por ejemplo. .factory, .service
Para referencia

En caso de transferencia de datos de padre a hijo controlador se puede acceder directamente a los datos de los padres en el controlador infantil a través $scope
Si está utilizando ui-routerentonces se puede utilizar $stateParmaspara pasar parámetros URL como id, name, key, etc.

$broadcastTambién es una buena manera de transferir datos entre controladores de padres a hijos y $emitde transferir datos de controladores hijos a padres

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

Consulte el enlace dado para saber más sobre$broadcast

ojus kulkarni
fuente
4

La solución más simple:

He usado un servicio AngularJS .

Paso 1: he creado un servicio AngularJS llamado SharedDataService.

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

    };
    return Person;
});

Paso 2: Cree dos controladores y use el servicio creado anteriormente.

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

Paso 3: simplemente use los controladores creados en la 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>

Para ver una solución funcional a este problema, presione el siguiente enlace

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

archivo .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>
gana999
fuente
1
Este es un ejemplo muy claro para nosotros, gente nueva que usa angularJs. ¿Cómo compartiría / pasaría los datos de una relación padre / hijo? FirstCtrles padre y recibe una promesa que SecondCtrl(niño) también necesitará? No quiero hacer dos llamadas api. Gracias.
Chris22
1

Hay otra forma sin usar $ 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;
});
Hinrich
fuente
1

Hay varias formas de hacer esto.

  1. Eventos - ya explicados bien.

  2. enrutador ui - explicado anteriormente.

  3. Servicio: con el método de actualización que se muestra arriba
  4. MALO - Observando los cambios.
  5. Otro enfoque padre-hijo en lugar de emitir y transmitir :

* *

<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();
        }
    }
});
usuario2756335
fuente
0

No estoy seguro de dónde tomé este patrón, pero para compartir datos entre controladores y reducir $ rootScope y $ scope, esto funciona muy bien. Es una reminiscencia de una replicación de datos donde tienes editores y suscriptores. Espero eso ayude.

El servicio:

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

El controlador que está obteniendo los nuevos datos, que es el editor, haría algo como esto ...

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

El controlador que también está utilizando estos nuevos datos, que se llama suscriptor, haría algo como esto ...

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

Esto funcionará para cualquier escenario. Entonces, cuando el controlador primario se inicializa y obtiene datos, llamará al método changeData, que luego lo transmitirá a todos los suscriptores de esos datos. Esto reduce el acoplamiento de nuestros controladores entre sí.

ewahner
fuente
El uso de un 'modelo' que comparte el estado entre los controladores parece ser la opción que se usa principalmente. Desafortunadamente, esto no cubre el escenario en el que actualizarías el controlador secundario. ¿Cómo 'recuperas' los datos del servidor y recreas el modelo? ¿Y cómo lidiarías con la invalidación de caché?
Andrei
Creo que la idea de los controladores de padres e hijos es un poco peligrosa y causa demasiadas dependencias entre sí. Sin embargo, cuando se usa en combinación con UI-Router, puede actualizar fácilmente los datos de su "hijo", especialmente si esos datos se inyectan a través de la configuración de estado. La invalidación de la memoria caché ocurre en el editor por el cual serían responsables de recuperar los datos y llamarsharedDataEventHub.changeData(newData)
ewahner
Creo que hay un malentendido. Por actualización quiero decir que los usuarios literalmente presionan la tecla F5 en el teclado y hacen que el navegador vuelva a publicar. Si está en la vista secundaria / detalles cuando esto sucede, no hay nadie a quien llamar "var someData = yourDataService.getSomeData ()". La pregunta es, ¿cómo lidiarías con este escenario? ¿Quién debería actualizar el modelo?
Andrei
Dependiendo de cómo active sus controladores, esto podría ser muy simple. Si tiene un método que se activa, el controlador que es responsable de cargar inicialmente los datos, lo cargaría y luego usaría sharedDataEventHub.changeData(newData)cualquier elemento secundario o controlador que dependa de esos datos tendría en algún lugar de su cuerpo del controlador lo siguiente: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });Si usó la IU -Router esto podría inyectarse desde la configuración de estado.
ewahner
Aquí hay un ejemplo que ilustra el problema que estoy tratando de describir: plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . Si saca el plunk, navegue a la página de edición y actualice el navegador, se bloqueará. ¿Quién debería recrear el modelo en este caso?
Andrei
-3

Simplemente hazlo simple (probado 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>

Lumic
fuente
55
Creo que esto fue rechazado porque no es modular. dummyes global para ambos controladores en lugar de compartirse de forma más modular a través de la herencia con $ scope o controllerAs o mediante un servicio.
Chad Johnson