AngularJS: ¿Cómo puedo pasar variables entre controladores?

326

Tengo dos controladores angulares:

function Ctrl1($scope) {
    $scope.prop1 = "First";
}

function Ctrl2($scope) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

No puedo usar Ctrl1adentro Ctrl2porque no está definido. Sin embargo, si trato de pasarlo así ...

function Ctrl2($scope, Ctrl1) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

Me sale un error ¿Alguien sabe como hacer esto?

Haciendo

Ctrl2.prototype = new Ctrl1();

También falla

NOTA: Estos controladores no están anidados uno dentro del otro.

dopatraman
fuente
Hay muchas formas, pero la mejor es el reloj angular. Siempre cuando usamos un marco es la mejor manera de utilizar sus propios métodos de trabajo de tareas no se olvide este
Pejman
Este blog me pareció muy útil Blog
Black Mamba

Respuestas:

503

Una forma de compartir variables entre varios controladores es crear un servicio e inyectarlo en cualquier controlador donde desee usarlo.

Ejemplo de servicio simple:

angular.module('myApp', [])
    .service('sharedProperties', function () {
        var property = 'First';

        return {
            getProperty: function () {
                return property;
            },
            setProperty: function(value) {
                property = value;
            }
        };
    });

Usando el servicio en un controlador:

function Ctrl2($scope, sharedProperties) {
    $scope.prop2 = "Second";
    $scope.both = sharedProperties.getProperty() + $scope.prop2;
}

Esto se describe muy bien en este blog (Lección 2 y más en particular).

Descubrí que si desea enlazar estas propiedades a través de múltiples controladores, funciona mejor si se une a la propiedad de un objeto en lugar de un tipo primitivo (booleano, cadena, número) para retener la referencia enlazada.

Ejemplo: en var property = { Property1: 'First' };lugar de var property = 'First';.


ACTUALIZACIÓN: Para (con suerte) aclarar las cosas aquí, hay un violín que muestra un ejemplo de:

  • Enlace a copias estáticas del valor compartido (en myController1)
    • Enlace a una primitiva (cadena)
    • Enlace a la propiedad de un objeto (guardado en una variable de alcance)
  • Enlace a valores compartidos que actualizan la IU a medida que se actualizan los valores (en myController2)
    • Enlace a una función que devuelve una primitiva (cadena)
    • Enlace a la propiedad del objeto
    • Enlace bidireccional a la propiedad de un objeto
Gloopy
fuente
55
En este caso, ¿cómo "conocería" el alcance de Ctrl2 cuando sharedProperties.getProperty () cambia el valor?
OpherV
55
Si desea que su IU se actualice cada vez que cambie la propiedad, puede cambiar bothpara que sea una función y se llamará / reevaluará durante el proceso de resumen angular. Vea este violín para ver un ejemplo. Además, si se vincula a la propiedad de un objeto, puede usarla directamente en su vista y se actualizará a medida que los datos cambien de manera similar a este ejemplo .
Gloopy
11
Si desea detectar y reaccionar a los cambios en su controlador, una opción es agregar la getProperty()función al ámbito y usar $ scope. $ Watch como en este ejemplo . Espero que estos ejemplos ayuden!
Gloopy
1
Aquí hay un problema ya que los servicios deberían ser apátridas. Almacenar una propiedad dentro de un servicio es incorrecto (pero conveniente). Comencé a usar $ cacheFactory para leer y escribir datos. Utilizo un servicio casi idéntico al de Gloopy, pero en lugar de almacenar el estado en el servicio, ahora está en el caché. Primero cree un servicio de caché: angular.module ('CacheService', ['ng']) .factory ('CacheService', function ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Inclúyalo en su app.js, inyéctelo en el servicio, utilícelo así: return CacheService.get (key); o CacheService.put (clave, valor);
Jordan Papaleo
44
Intentando entender cómo y por qué esta respuesta se usa en .servicelugar de .factorycomo se describe en los documentos angulares. ¿Por qué esta respuesta se vota tan alto cuando la documentación utiliza un método diferente?
pspahn
44

Me gusta ilustrar cosas simples con ejemplos simples :)

Aquí hay un Serviceejemplo muy simple :


angular.module('toDo',[])

.service('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  this.dataObj = _dataObj;
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

Y aqui el jsbin

Y aquí hay un Factoryejemplo muy simple :


angular.module('toDo',[])

.factory('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  return {
    dataObj: _dataObj
  };
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

Y aqui el jsbin


Si eso es demasiado simple, aquí hay un ejemplo más sofisticado

Consulte también la respuesta aquí para ver los comentarios relacionados con las mejores prácticas.

Dmitri Zaitsev
fuente
1
Sí estoy de acuerdo con usted. Siempre trata de hacer las cosas simples.
Evan Hu
¿Cuál es el punto de declarar var _dataObj = {};cuando devuelve una referencia directa a él ...? Eso no es privado . En el primer ejemplo que puede hacer this.dataObj = {};y en el segundo return { dataObj: {} };es una declaración de variable inútil en mi humilde opinión.
TJ
@TJ El punto es compartir esta variable entre otros componentes. Es un ejemplo básico que ilustra el concepto de compartir. La variable ES privada dentro del bloque, luego la expone como variable pública utilizando el patrón revelador. De esta manera, hay una separación de responsabilidades entre mantener la variable y usarla.
Dmitri Zaitsev
@DmitriZaitsev dices "ejemplos simples" pero a menos que muestres correctamente cómo hacer uso del estado privado, solo estás confundiendo a las personas. No hay estado privado en su ejemplo siempre que devuelva una referencia directa.
TJ
@TJ No veo nada confuso. Una variable privada puede ser expuesta por un módulo. Siéntase libre de escribir una mejor respuesta.
Dmitri Zaitsev
26

--- Sé que esta respuesta no es para esta pregunta, pero quiero personas que lean esta pregunta y quieran manejar Servicios como Fábricas para evitar problemas al hacer esto ----

Para ello, deberá utilizar un Servicio o una Fábrica.

Los servicios son la MEJOR PRÁCTICA para compartir datos entre controladores no anidados.

Una muy buena anotación sobre este tema sobre el intercambio de datos es cómo declarar objetos. Tuve mala suerte porque caí en una trampa AngularJS antes de leer sobre eso, y estaba muy frustrado. Déjame ayudarte a evitar este problema.

Leí del "ng-book: El libro completo sobre AngularJS" que los modelos ng de AngularJS que se crean en controladores como datos desnudos ¡ESTÁN INCORRECTOS!

Se debe crear un elemento $ scope así:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // best practice, always use a model
  $scope.someModel = {
    someValue: 'hello computer'
  });

Y no así:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // anti-pattern, bare value
  $scope.someBareValue = 'hello computer';
  };
});

Esto se debe a que se recomienda (MEJOR PRÁCTICA) que el DOM (documento html) contenga las llamadas como

<div ng-model="someModel.someValue"></div>  //NOTICE THE DOT.

Esto es muy útil para los controladores anidados si desea que su controlador secundario pueda cambiar un objeto desde el controlador principal ...

Pero en su caso no desea ámbitos anidados, pero hay un aspecto similar para obtener objetos de los servicios a los controladores.

Digamos que tiene su servicio 'Factory' y en el espacio de retorno hay un objeto A que contiene un objeto B que contiene un objeto C.

Si desde su controlador desea OBTENER el objectC en su alcance, es un error decir:

$scope.neededObjectInController = Factory.objectA.objectB.objectC;

Eso no funcionará ... En su lugar, use solo un punto.

$scope.neededObjectInController = Factory.ObjectA;

Luego, en el DOM puede llamar a objectC desde objectA. Esta es una práctica recomendada relacionada con las fábricas y, lo más importante, ayudará a evitar errores inesperados y no detectables.

AFP_555
fuente
2
Creo que esta es una buena respuesta, pero es bastante difícil de digerir.
pspahn
17

Solución sin crear Servicio, usando $ rootScope:

Para compartir propiedades entre los Controladores de aplicaciones, puede usar Angular $ rootScope. Esta es otra opción para compartir datos, para que la gente lo sepa.

La forma preferida de compartir algunas funciones entre los Controladores es Servicios, para leer o cambiar una propiedad global puede usar $ rootscope.

var app = angular.module('mymodule',[]);
app.controller('Ctrl1', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = true;
}]);

app.controller('Ctrl2', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = false;
}]);

Usando $ rootScope en una plantilla (Propiedades de acceso con $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Sanjeev
fuente
55
Está utilizando variables de ámbito global en ese punto que se desvía de la idea de AngularJS de definir localmente todo dentro de sus diversas estructuras. Agregar un archivo de variable global lograría lo mismo y facilitaría encontrar dónde se definió originalmente la variable. De cualquier manera, no sugerido.
Organiccat
44
@Organiccat: entiendo su preocupación y es por eso que ya he mencionado que la forma preferida serán los servicios, sin duda. Pero ya angular proporciona esta manera también. Depende de ti cómo quieres gestionar tu global. Tuve un escenario donde este enfoque funcionó mejor para mí.
Sanjeev
8

La muestra anterior funcionó a las mil maravillas. Acabo de hacer una modificación en caso de que necesite administrar varios valores. ¡Espero que esto ayude!

app.service('sharedProperties', function () {

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});
Juan Zamora
fuente
1
También creé una muestra usando un servicio para compartir datos entre diferentes controladores. Espero que les guste. jsfiddle.net/juazammo/du53553a/1
Juan Zamora
1
Aunque funciona, esta suele ser la sintaxis para .factory. A .servicedebe usarse "si define su servicio como un tipo / clase" según docs.angularjs.org/api/auto/service/$provide#service
Dmitri Zaitsev
1
Dmitri, tienes razón, sin embargo, los chicos angulares desde mi punto de vista, solo cambiaron un poco el concepto que tenía entre los servicios (fachadas) y las fábricas ... oh, bueno ...
Juan Zamora
1
Y corríjame si estoy equivocado, los servicios están destinados a devolver algo que puede ser un objeto o un valor. Las fábricas están destinadas a crear objetos. Una fachada que en realidad es una colección de funcionalidades que devuelven algo, es lo que pensé que los servicios tenían. Incluyendo invocar funcionalidades de fábricas. Nuevamente, me estoy metiendo en la noción básica de lo que esto es para mí y no de lo que realmente es desde la perspectiva angular. (Abstract Factory dofactory.com/net/abstract-factory-design-pattern ) y un enfoque Adaptador es lo que expondré como servicio
Juan Zamora
1
Verifique el patrón del adaptador aquí dofactory.com/net/adapter-design-pattern
Juan Zamora
6

Tiendo a usar valores, me alegra que alguien discuta por qué es una mala idea.

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

myApp.value('sharedProperties', {}); //set to empty object - 

Luego inyecte el valor según un servicio.

Establecer en ctrl1:

myApp.controller('ctrl1', function DemoController(sharedProperties) {
  sharedProperties.carModel = "Galaxy";
  sharedProperties.carMake = "Ford";
});

y acceso desde ctrl2:

myApp.controller('ctrl2', function DemoController(sharedProperties) {
  this.car = sharedProperties.carModel + sharedProperties.carMake; 

});
Llama fría
fuente
¿Cómo es esto diferente de usar un servicio?
dopatraman
5

El siguiente ejemplo muestra cómo pasar variables entre controladores hermanos y realizar una acción cuando el valor cambia.

Ejemplo de caso de uso: tiene un filtro en una barra lateral que cambia el contenido de otra vista.

angular.module('myApp', [])

  .factory('MyService', function() {

    // private
    var value = 0;

    // public
    return {
      
      getValue: function() {
        return value;
      },
      
      setValue: function(val) {
        value = val;
      }
      
    };
  })
  
  .controller('Ctrl1', function($scope, $rootScope, MyService) {

    $scope.update = function() {
      MyService.setValue($scope.value);
      $rootScope.$broadcast('increment-value-event');
    };
  })
  
  .controller('Ctrl2', function($scope, MyService) {

    $scope.value = MyService.getValue();

    $scope.$on('increment-value-event', function() {    
      $scope.value = MyService.getValue();
    });
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="myApp">
  
  <h3>Controller 1 Scope</h3>
  <div ng-controller="Ctrl1">
    <input type="text" ng-model="value"/>
    <button ng-click="update()">Update</button>
  </div>
  
  <hr>
  
  <h3>Controller 2 Scope</h3>
  <div ng-controller="Ctrl2">
    Value: {{ value }}
  </div>  

</div>

Zanon
fuente
4

Me gustaría contribuir a esta pregunta señalando que la forma recomendada de compartir datos entre los controladores, e incluso las directivas, es mediante el uso de servicios (fábricas) como ya se ha señalado, pero también me gustaría proporcionar un Ejemplo práctico de trabajo de cómo hacerlo.

Aquí está el plunker de trabajo: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

Primero, cree su servicio , que tendrá sus datos compartidos :

app.factory('SharedService', function() {
  return {
    sharedObject: {
      value: '',
      value2: ''
    }
  };
});

Luego, simplemente inyecte en sus controladores y tome los datos compartidos en su alcance:

app.controller('FirstCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('SecondCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('MainCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

También puede hacerlo para sus directivas , funciona de la misma manera:

app.directive('myDirective',['SharedService', function(SharedService){
  return{
    restrict: 'E',
    link: function(scope){
      scope.model = SharedService.sharedObject;
    },
    template: '<div><input type="text" ng-model="model.value"/></div>'
  }
}]);

Espero que esta respuesta práctica y limpia pueda ser útil para alguien.

Fedaykin
fuente
3

Podrías hacerlo con servicios o fábricas. Son esencialmente iguales, aparte de algunas diferencias centrales. Encontré esta explicación en thinkster.io como la más fácil de seguir. Simple, directo y efectivo.

Noahdecoco
fuente
1
"Podrías hacer eso con servicios o fábricas" - ¿Cómo ...? Cómo hacerlo es lo que está pidiendo el OP ... por favor publique la respuesta completa en stackoverflow en lugar de vincular a recursos externos, los enlaces pueden fallar en el tiempo extra.
TJ
2

¿No podría también hacer que la propiedad sea parte de los ámbitos primarios?

$scope.$parent.property = somevalue;

No digo que sea correcto, pero funciona.

SideFX
fuente
3
El autor declaró que NOTE: These controllers are not nested inside each other.. Si estos fueran controladores anidados o controladores que compartieran el mismo padre, esto funcionaría, pero no podemos esperar eso.
Chris Foster,
2
En general, es una mala práctica confiar $parentsi eso se puede evitar. Un componente reutilizable bien diseñado no debe conocer a sus padres.
Dmitri Zaitsev
2

Ah, tenga un poco de estas cosas nuevas como otra alternativa. Es almacenamiento local y funciona donde funciona angular. De nada. (Pero realmente, gracias al chico)

https://github.com/gsklee/ngStorage

Defina sus valores predeterminados:

$scope.$storage = $localStorage.$default({
    prop1: 'First',
    prop2: 'Second'
});

Acceda a los valores:

$scope.prop1 = $localStorage.prop1;
$scope.prop2 = $localStorage.prop2;

Almacenar los valores

$localStorage.prop1 = $scope.prop1;
$localStorage.prop2 = $scope.prop2;

Recuerde inyectar ngStorage en su aplicación y $ localStorage en su controlador.

kJamesy
fuente
1
Esto resuelve un problema diferente: almacenamiento persistente. No es una solución escalable para el problema en cuestión, ya que hace que su código tenga fugas con efectos secundarios como la modificación del objeto de almacenamiento local con vulnerabilidad de choque de nombres, entre otros.
Dmitri Zaitsev
1

Hay dos maneras de hacer esto

1) Use el servicio get / set

2) $scope.$emit('key', {data: value}); //to set the value

 $rootScope.$on('key', function (event, data) {}); // to get the value
Rohan Kawade
fuente
1

Segundo enfoque

angular.module('myApp', [])
  .controller('Ctrl1', ['$scope',
    function($scope) {

    $scope.prop1 = "First";

    $scope.clickFunction = function() {
      $scope.$broadcast('update_Ctrl2_controller', $scope.prop1);
    };
   }
])
.controller('Ctrl2', ['$scope',
    function($scope) {
      $scope.prop2 = "Second";

        $scope.$on("update_Ctrl2_controller", function(event, prop) {
        $scope.prop = prop;

        $scope.both = prop + $scope.prop2; 
    });
  }
])

HTML:

<div ng-controller="Ctrl2">
  <p>{{both}}</p>
</div>

<button ng-click="clickFunction()">Click</button>

Para más detalles ver plunker:

http://plnkr.co/edit/cKVsPcfs1A1Wwlud2jtO?p=preview

Codiee
fuente
1
Funciona solo si Ctrl2(el oyente) es un controlador secundario de Ctrl1. Los controladores hermanos deben comunicarse a través de $rootScope.
herzbube
0

Si no desea hacer el servicio, puede hacerlo así.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;
nota de agradecimiento
fuente
37
No dudo que esta respuesta funcione, pero quiero señalar que esto va en contra de la filosofía de AngularJS de nunca tener objetos DOM en su código de modelo / controlador.
JoeCool
3
-1 porque la comunicación del controlador a través del DOM es una mala práctica, en mi opinión.
Chris Foster,
3
@ChrisFoster, solo porque un martillo se venda como una "herramienta", no significa que no se pueda usar como gramaje de papel. Estoy seguro de que para cada marco o herramienta, siempre encontrará desarrolladores que necesitan "doblar" la lista de "mejores prácticas".
Andrei V
55
@AndreiV - Mala analogía, no hay inconveniente en usar un martillo como gramaje. Hacer malas prácticas como esta tiene claras desventajas y puede conducir fácilmente a un código de espagueti. El código anterior es frágil porque ahora depende de dónde esté su controlador en el DOM y es muy difícil de probar. Usar un servicio es una mejor práctica por una razón, porque no vincula su implementación a su plantilla. Estoy de acuerdo en que los desarrolladores a menudo necesitan doblar la lista de mejores prácticas, pero no cuando hay una mejor práctica clara, común y más modular que funciona mejor.
Chris Foster
-1

Además de $ rootScope y los servicios, hay una solución alternativa limpia y fácil de extender angular para agregar los datos compartidos:

en los controladores:

angular.sharedProperties = angular.sharedProperties 
    || angular.extend(the-properties-objects);

Estas propiedades pertenecen al objeto 'angular', separado de los ámbitos, y se pueden compartir en ámbitos y servicios.

Uno de los beneficios es que no tiene que inyectar el objeto: ¡son accesibles desde cualquier lugar inmediatamente después de su definición!

williamjxj
fuente
2
Esto es como tener variables globales en todo el windowobjeto ... Si vas a contaminar angularmente, ¿por qué no simplemente seguir contaminando el objeto de la ventana ...
TJ