¿Pasando datos entre controladores en Angular JS?


243

Tengo un controlador básico que muestra mis productos,

App.controller('ProductCtrl',function($scope,$productFactory){
     $productFactory.get().success(function(data){
           $scope.products = data;
     });
});

En mi opinión, estoy mostrando estos productos en una lista

<ul>
    <li ng-repeat="product as products">
        {{product.name}}
    </li>
</ul

Lo que intento hacer es cuando alguien hace clic en el nombre del producto, tengo otra vista llamada carrito donde se agrega este producto.

 <ul class="cart">
      <li>
          //click one added here
      </li>
      <li>
          //click two added here
      </li>
 </ul>

Entonces mi duda aquí es, ¿cómo pasar estos productos clicados del primer controlador al segundo? Supuse que el carro también debería ser un controlador.

Manejo eventos click usando directiva. También creo que debería estar utilizando el servicio para lograr la funcionalidad anterior, pero no puedo entender cómo. debido a que el carrito tendrá un número predefinido de productos agregados, podría ser 5/10 dependiendo de la página del usuario. Entonces me gustaría mantener esto genérico.

Actualizar:

Creé un servicio para transmitir y en el segundo controlador lo recibo. Ahora la consulta es ¿cómo actualizo dom? Dado que mi lista para dejar caer el producto está bastante codificada.


Posible duplicado de variables
T.Todua

Respuestas:


322

Según la descripción, parece que debería estar utilizando un servicio. Consulte http://egghead.io/lessons/angularjs-sharing-data-between-controllers y AngularJS Service Passing Data Between Controllers para ver algunos ejemplos.

Puede definir el servicio de su producto (como fábrica) como tal:

app.factory('productService', function() {
  var productList = [];

  var addProduct = function(newObj) {
      productList.push(newObj);
  };

  var getProducts = function(){
      return productList;
  };

  return {
    addProduct: addProduct,
    getProducts: getProducts
  };

});

La dependencia inyecta el servicio en ambos controladores.

En su ProductController, defina alguna acción que agregue el objeto seleccionado a la matriz:

app.controller('ProductController', function($scope, productService) {
    $scope.callToAddToProductList = function(currObj){
        productService.addProduct(currObj);
    };
});

En su CartController, obtenga los productos del servicio:

app.controller('CartController', function($scope, productService) {
    $scope.products = productService.getProducts();
});

3
¿El controlador de carrito se actualizará una vez cuando se llame a getProducts ()? ¿O cada vez que se agrega un nuevo producto?
kishanio

1
Si desea que se actualice automáticamente, puede agregar la transmisión desde la respuesta de Maxim Shoustin y llamar a getProducts () dentro de la función $ on para actualizar el alcance de CartCtrl.
Chalise

2
@krillgar Tienes toda la razón, eso se pasó por alto inicialmente. He editado la respuesta para reflejar una solución que funcione.
Chalise

1
@DeveshM Sí, haz lo que sugieras. Es posible que desee considerar extender los métodos para que no solo contengan datos en la memoria y sean más persistentes (guardar en el servidor a través de una $httpllamada, por ejemplo).
Chalise

1
¿Qué sucede si tengo 2 controladores de producto y 2 carritos, por ejemplo? Ambos tendrán el elemento agregado? ¿Cómo se resuelve eso en este caso?
atoth

67

¿Cómo pasan estos productos cliqueados del primer controlador al segundo?

Al hacer clic, puede llamar al método que invoca la transmisión :

$rootScope.$broadcast('SOME_TAG', 'your value');

y el segundo controlador escuchará en esta etiqueta como:

$scope.$on('SOME_TAG', function(response) {
      // ....
})

Como no podemos inyectar $ scope en los servicios, no hay nada como un singleton $ scope.

Pero podemos inyectar $rootScope. Entonces, si almacena valor en el Servicio, puede ejecutarlo $rootScope.$broadcast('SOME_TAG', 'your value');en el cuerpo del Servicio. (Consulte la descripción de @Charx sobre los servicios)

app.service('productService',  function($rootScope) {/*....*/}

Por favor revise un buen artículo sobre $ broadcast , $ emit


1
Sí, esto funciona como encanto estoy usando fábrica? Solo hay una última cosa en la que estoy atascado: obtengo datos en el nuevo controlador cada vez que hago clic en el producto. Ahora, ¿cómo lo actualizo en DOM? Porque ya tengo una lista de 5 codificados con bordes, por lo que cada producto debe ir dentro de ellos
kishanio

1
@KT - ¿Alguna vez recibiste una respuesta? Parece una pregunta obvia, pero no puedo encontrar la respuesta en ningún lado. Quiero un controlador para cambiar algún valor. Cuando el valor de la aplicación cambia, cualquier otro controlador de escucha debe actualizarse según sea necesario. No puedo encontrarlo.
raddevus

2
@ daylight la respuesta en tu q. se basa en la estructura del controlador que tiene: paralelo o secundario-primario. $rootScope.$broadcastnotifica a todos los controladores que tienen una estructura paralela, también conocido como el mismo nivel.
Maxim Shoustin

@MS Gracias por la respuesta. ¿Hay alguna manera de hacer esto usando $ watch? Por cierto, los míos son controladores paralelos y lo hice funcionar usando su método. Para mí, cada vez que intento agregar $ watch (incluso a $ rootScope), el método asociado con $ watch en el segundo controlador solo se activará la primera vez (inicialización del segundo controlador). Gracias.
raddevus

1
podemos usar $watchsi el valor existe en el $rootScopenivel. De lo contrario, solo la transmisión podría notificar a otros controladores. Para su información, si otro programador ve en su código broadcast- la lógica es clara lo que intenta hacer - "evento de difusión". De todos modos, desde mi exp. no es una buena práctica utilizar rootscopepara watch. Acerca de "disparar solo por primera vez": eche un vistazo a este ejemplo: plnkr.co/edit/5zqkj7iYloeHYkzK1Prt?p=preview tiene valores antiguos y nuevos. asegúrese de que cada vez que obtenga un nuevo valor que no sea igual al anterior
Maxim Shoustin

24

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>

27
$rootScopedebe evitarse tanto como sea posible.
Shaz

1
@ Shaz, ¿podrías explicar por qué?
Mirko

55
@Mirko Deberías tratar de evitar el estado global tanto como puedas porque cualquiera puede cambiarlo, haciendo que tu programa sea impredecible.
Umut Seven

44
$ rootScope tiene un alcance global, por lo que, al igual que las variables globales nativas, tenemos que usarlas con cuidado. Si algún controlador cambia sus valores, se cambiará para el alcance global.
Sanjeev

1
Los servicios y las fábricas son Singleton, pero puede optar por inyectar o no cualquier servicio en su controlador. Pero todo el alcance del niño se deriva del rootcope y sigue la cadena prototípica. Entonces, si intenta acceder a una propiedad en su alcance y no está presente, buscará en la cadena. Existe la posibilidad de que usted cambie de forma no deseada la propiedad del rootcope.
Sanjeev

16

Puedes hacer esto por dos métodos.

  1. Al usar $rootscope, pero no recomiendo esto. El $rootScopees el alcance más alto. Una aplicación solo puede tener una $rootScopeque se compartirá entre todos los componentes de una aplicación. Por lo tanto, actúa como una variable global.

  2. Utilizando servicios. Puede hacerlo compartiendo un servicio entre dos controladores. El código de servicio puede verse así:

    app.service('shareDataService', function() {
        var myList = [];
    
        var addList = function(newObj) {
            myList.push(newObj);
        }
    
        var getList = function(){
            return myList;
        }
    
        return {
            addList: addList,
            getList: getList
        };
    });

    Puedes ver mi violín aquí .


y una tercera opción, puede enviar un evento, solo para agregarlo a la lista
DanteTheSmith

¿QUÉ PASA SI EL USUARIO ACTUALIZA LA PÁGINA? ENTONCES getProducts () ¡NO LE DARÁ NADA! (No puede decirle a todos los usuarios que no actualicen, ¿
verdad

2
@shreedharbhat La pregunta no es sobre los datos persistentes en las recargas de páginas.
Jack Vial

9

Una forma aún más simple de compartir los datos entre los controladores es usar estructuras de datos anidados. En lugar de, por ejemplo

$scope.customer = {};

nosotros podemos usar

$scope.data = { customer: {} };

La datapropiedad se heredará del ámbito primario para que podamos sobrescribir sus campos, manteniendo el acceso desde otros controladores.


1
Los controladores heredan las propiedades del alcance de los Controladores principales a su propio alcance. ¡Limpiar! la simplicidad lo hace hermoso
Carlos R Balebona

No puedo hacer que funcione. en el segundo controlador, uso $ scope.data y no está definido.
Bart Calixto

Está hablando de controladores anidados, creo. No me sirve de mucho tampoco.
Norbert Norbertson

8
angular.module('testAppControllers', [])
    .controller('ctrlOne', function ($scope) {
        $scope.$broadcast('test');
    })
    .controller('ctrlTwo', function ($scope) {
        $scope.$on('test', function() {
        });
    });

utiliza niether
Leathan

5

Podemos almacenar datos en sesión y usarlos en cualquier parte de nuestro programa.

$window.sessionStorage.setItem("Mydata",data);

Otro lugar

$scope.data = $window.sessionStorage.getItem("Mydata");

4

Vi las respuestas aquí, y está respondiendo a la pregunta de compartir datos entre los controladores, pero ¿qué debo hacer si quiero que un controlador notifique al otro sobre el hecho de que los datos han cambiado (sin usar la transmisión)? ¡FÁCIL! Simplemente usando el famoso patrón de visitante:

myApp.service('myService', function() {

    var visitors = [];

    var registerVisitor = function (visitor) {
        visitors.push(visitor);
    }

    var notifyAll = function() {
        for (var index = 0; index < visitors.length; ++index)
            visitors[index].visit();
    }

    var myData = ["some", "list", "of", "data"];

    var setData = function (newData) {
        myData = newData;
        notifyAll();
    }

    var getData = function () {
        return myData;
    }

    return {
        registerVisitor: registerVisitor,
        setData: setData,
        getData: getData
    };
}

myApp.controller('firstController', ['$scope', 'myService',
    function firstController($scope, myService) {

        var setData = function (data) {
            myService.setData(data);
        }

    }
]);

myApp.controller('secondController', ['$scope', 'myService',
    function secondController($scope, myService) {

        myService.registerVisitor(this);

        this.visit = function () {
            $scope.data = myService.getData();
        }

        $scope.data = myService.getData();
    }
]);

De esta manera simple, un controlador puede actualizar otro controlador que algunos datos han sido actualizados.


2
¿No sería más relevante aquí el patrón de observador ( en.wikipedia.org/wiki/Observer_pattern )? El patrón de visitante es otra cosa ... es.wikipedia.org/wiki/Visitor_pattern
Ant Kutschera

3

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

Por lo tanto, 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


3

1

using $localStorage

app.controller('ProductController', function($scope, $localStorage) {
    $scope.setSelectedProduct = function(selectedObj){
        $localStorage.selectedObj= selectedObj;
    };
});

app.controller('CartController', function($scope,$localStorage) { 
    $scope.selectedProducts = $localStorage.selectedObj;
    $localStorage.$reset();//to remove
});

2

Al hacer clic, puede llamar al método que invoca la transmisión:

$rootScope.$broadcast('SOME_TAG', 'your value');

and the second controller will listen on this tag like:
$scope.$on('SOME_TAG', function(response) {
      // ....
})

3

using $rootScope:

4 4

window.sessionStorage.setItem("Mydata",data);
$scope.data = $window.sessionStorage.getItem("Mydata");

5 5

Una forma de usar el servicio angular:

var app = angular.module("home", []);

app.controller('one', function($scope, ser1){
$scope.inputText = ser1;
});

app.controller('two',function($scope, ser1){
$scope.inputTextTwo = ser1;
});

app.factory('ser1', function(){
return {o: ''};
});

2
Pon más explicaciones en tu respuesta.
Gerhard Barnard el

2

Haga una fábrica en su módulo y agregue una referencia de la fábrica en el controlador y use sus variables en el controlador y ahora obtenga el valor de los datos en otro controlador agregando la referencia donde quiera


2

No sé si ayudará a alguien, pero según la respuesta de Charx (¡gracias!), He creado un servicio de caché simple. Siéntase libre de usar, mezclar y compartir:

angular.service('cache', function() {
    var _cache, _store, _get, _set, _clear;
    _cache = {};

    _store = function(data) {
        angular.merge(_cache, data);
    };

    _set = function(data) {
        _cache = angular.extend({}, data);
    };

    _get = function(key) {
        if(key == null) {
            return _cache;
        } else {
            return _cache[key];
        }
    };

    _clear = function() {
        _cache = {};
    };

    return {
        get: _get,
        set: _set,
        store: _store,
        clear: _clear
    };
});

2

Una forma de usar el servicio angular:

var app = angular.module("home", []);

app.controller('one', function($scope, ser1){
$scope.inputText = ser1;
});


app.controller('two',function($scope, ser1){
$scope.inputTextTwo = ser1;
});

app.factory('ser1', function(){
return {o: ''};
});



<div ng-app='home'>

<div ng-controller='one'>
  Type in text: 
  <input type='text' ng-model="inputText.o"/>
</div>
<br />

<div ng-controller='two'>
  Type in text:
  <input type='text' ng-model="inputTextTwo.o"/>
</div>

</div>

https://jsfiddle.net/1w64222q/


1

FYI El objeto $ scope tiene $ emit, $ broadcast, $ on Y el objeto $ rootScope tiene idéntico $ emit, $ broadcast, $ on

Lea más sobre el patrón de diseño de publicación / suscripción en angular aquí


1

Para mejorar la solución propuesta por @Maxim usando $ broadcast, los datos de envío no cambian

$rootScope.$broadcast('SOME_TAG', 'my variable');

pero a escuchar datos

$scope.$on('SOME_TAG', function(event, args) {
    console.log("My variable is", args);// args is value of your variable
})


0
var custApp = angular.module("custApp", [])
.controller('FirstController', FirstController)
.controller('SecondController',SecondController)
.service('sharedData', SharedData);

FirstController.$inject = ['sharedData'];
function FirstController(sharedData) {
this.data = sharedData.data;
}

SecondController.$inject['sharedData'];
function SecondController(sharedData) {
this.data = sharedData.data;
}

function SharedData() {
this.data = {
    value: 'default Value'
}
}

Primer controlador

<div ng-controller="FirstController as vm">
<input type=text ng-model="vm.data.value" />
</div>

Segundo controlador

 <div ng-controller="SecondController as vm">
    Second Controller<br>
    {{vm.data.value}}
</div>

-1

Pienso que el

mejor manera

es usar $ localStorage. (Funciona todo el tiempo)

app.controller('ProductController', function($scope, $localStorage) {
    $scope.setSelectedProduct = function(selectedObj){
        $localStorage.selectedObj= selectedObj;
    };
});

Su cardController será

app.controller('CartController', function($scope,$localStorage) { 
    $scope.selectedProducts = $localStorage.selectedObj;
    $localStorage.$reset();//to remove
});

También puedes agregar

if($localStorage.selectedObj){
    $scope.selectedProducts = $localStorage.selectedObj;
}else{
    //redirect to select product using $location.url('/select-product')
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.