Passando i dati tra i controller in Angular JS?


243

Ho un controller di base che visualizza i miei prodotti,

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

A mio avviso, sto visualizzando questi prodotti in un elenco

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

Quello che sto cercando di fare è quando qualcuno fa clic sul nome del prodotto, ho un'altra vista chiamata carrello in cui questo prodotto viene aggiunto.

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

Quindi il mio dubbio qui è, come passare questi prodotti cliccati dal primo controller al secondo? ho pensato che anche il carrello dovrebbe essere un controller.

Gestisco l'evento click usando la direttiva. Inoltre sento che dovrei usare il servizio per ottenere le funzionalità sopra appena non riesco a capire come? perché il carrello sarà predefinito il numero di prodotti aggiunti potrebbe essere 5/10 a seconda dell'utente della pagina. Quindi vorrei mantenere questo generico.

Aggiornare:

Ho creato un servizio per trasmettere e nel secondo controller lo ricevo. Ora la query è come posso aggiornare dom? Dal momento che la mia lista per eliminare il prodotto è piuttosto hardcoded.


Risposte:


322

Dalla descrizione, sembra che dovresti usare un servizio. Dai un'occhiata a http://egghead.io/lessons/angularjs-sharing-data-between-controllers e al servizio AngularJS che passa i dati tra i controller passa i per vedere alcuni esempi.

È possibile definire il servizio del prodotto (come una fabbrica) in quanto tale:

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

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

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

  return {
    addProduct: addProduct,
    getProducts: getProducts
  };

});

La dipendenza inietta il servizio in entrambi i controller.

Nel tuo ProductController, definisci alcune azioni che aggiungono l'oggetto selezionato all'array:

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

Nel tuo CartController, ottieni i prodotti dal servizio:

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

3
Il controller del carrello verrà aggiornato una volta quando viene chiamato getProducts ()? O ogni volta che viene aggiunto un nuovo prodotto?
kishanio,

1
Se vuoi che si aggiorni automaticamente, puoi aggiungere la trasmissione dalla risposta di Maxim Shoustin e chiamare getProducts () all'interno della funzione $ on per aggiornare l'ambito di CartCtrl.
Chalise,

2
@krillgar Hai perfettamente ragione, inizialmente trascurato. Ho modificato la risposta per riflettere una soluzione funzionante.
Chalise,

1
@DeveshM Sì, fai come suggerisci. Potresti voler considerare di estendere i metodi in modo che non trattengano solo i dati in memoria e siano più persistenti (ad $httpesempio, salva sul server tramite chiamata).
Chalise,

1
Cosa succede se ho 2 productController e 2 carrelli per esempio? Entrambi avranno l'elemento aggiunto? Come risolverlo in questo caso?
atoth

67

come passare questi prodotti cliccati dal primo controller al secondo?

Al clic puoi chiamare il metodo che invoca la trasmissione :

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

e il secondo controller ascolterà su questo tag come:

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

Dato che non possiamo iniettare $ scope nei servizi, non c'è niente come un singleton $ scope.

Ma possiamo iniettare $rootScope. Pertanto, se si memorizza valore nel servizio, è possibile eseguirlo $rootScope.$broadcast('SOME_TAG', 'your value');nel corpo del servizio. (Vedi la descrizione di @Charx sui servizi)

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

Controlla un buon articolo su $ broadcast , $ emit


1
Sì, questo funziona come il fascino che sto usando factory? C'è solo un'ultima cosa in cui sono bloccato: ricevo i dati nel nuovo controller ogni volta che clicco sul prodotto. Ora come lo aggiorno in DOM? Perché ho già un elenco di 5 hardcoded con bordi, quindi ogni prodotto deve andare al loro interno
kishanio

1
@KT - Hai mai ricevuto una risposta? Sembra una domanda ovvia ma non riesco a trovare la risposta da nessuna parte. Voglio che un controller cambi un valore. Quando il valore di quell'app cambia, allora qualsiasi altro controller di ascolto dovrebbe aggiornarsi se necessario. Non lo trovo.
Raddevus,

2
@daylight la risposta sul tuo q. si basa sulla struttura del controller che hai: parallelo o genitore figlio. $rootScope.$broadcastnotifica a tutti i controller che hanno una struttura parallela, ovvero lo stesso livello.
Maxim Shoustin,

@MS Grazie per la risposta. C'è un modo per farlo usando $ watch? A proposito, i miei sono controller paralleli e l'ho fatto funzionare usando il tuo metodo. Per quanto mi riguarda, ogni volta che provo ad aggiungere $ watch (anche a $ rootScope) il metodo associato a $ watch nel secondo controller si attiva solo la prima volta (inizializzazione del secondo controller). Grazie.
Raddevus,

1
possiamo usare $watchse il valore esiste a $rootScopelivello. Altrimenti solo le trasmissioni potrebbero avvisare altri controller. Cordiali saluti, se un altro programmatore vede nel tuo codice broadcast- la logica è chiara cosa cerchi di fare - "evento di trasmissione". Comunque, dal mio exp. non è una buona pratica da usare rootscopeper watch. Informazioni sul "solo fuoco per la prima volta": dai un'occhiata a questo esempio: plnkr.co/edit/5zqkj7iYloeHYkzK1Prt?p=preview hai valori vecchi e nuovi. assicurati che ogni volta che ottieni un nuovo valore che non sia uguale a quello precedente
Maxim Shoustin,

24

Soluzione senza creare servizio, usando $ rootScope:

Per condividere le proprietà tra i controller delle app puoi usare Angular $ rootScope. Questa è un'altra opzione per condividere i dati, mettendoli in modo che le persone ne siano a conoscenza.

Il modo preferito di condividere alcune funzionalità tra i controller è Servizi, per leggere o modificare una proprietà globale è possibile utilizzare $ 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 in un modello (accedi alle proprietà con $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>

27
$rootScopedovrebbe essere evitato il più possibile.
Shaz,

1
@Shaz potresti spiegare perché?
Mirko,

5
@Mirko Dovresti cercare di evitare lo stato globale il più possibile perché chiunque può cambiarlo, rendendo imprevedibile lo stato del tuo programma.
Umut Seven,

4
$ rootScope ha portata globale, quindi come le variabili globali native dobbiamo usarle con attenzione. Se un controller cambia i suoi valori, verrà modificato per scopi globali.
Sanjeev,

1
Servizi e fabbriche sono entrambi Singleton, ma puoi scegliere di iniettare o meno qualsiasi servizio nel tuo controller. Ma tutto lo scopo del bambino deriva dal rootcope e segue la catena prototipica. Quindi, se provi ad accedere a una proprietà sul tuo ambito e non è presente, cercherà la catena. Esiste la possibilità che tu possa involontariamente modificare la proprietà di rootcope.
Sanjeev,

16

Puoi farlo con due metodi.

  1. Usando $rootscope, ma non lo consiglio. Il $rootScopeè il più in alto portata. Un'app può avere solo una $rootScopeche verrà condivisa tra tutti i componenti di un'app. Quindi si comporta come una variabile globale.

  2. Utilizzando i servizi. Puoi farlo condividendo un servizio tra due controller. Il codice per l'assistenza può essere simile al seguente:

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

    Puoi vedere il mio violino qui .


e una terza opzione, è possibile inviare un evento, solo per aggiungere alla lista
DanteTheSmith

COSA SE L'UTENTE AGGIORNA LA PAGINA? POI getProducts () NON TI DÀ NULLA! (Non puoi dire a tutti gli utenti di non aggiornare, vero?)
shreedhar bhat,

2
@shreedharbhat La domanda non sta ponendo domande sulla persistenza dei dati nei ricarichi delle pagine.
Jack Vial,

9

Un modo ancora più semplice per condividere i dati tra i controller è l'utilizzo di strutture di dati nidificate. Invece di, per esempio

$scope.customer = {};

possiamo usare

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

La dataproprietà verrà ereditata dall'ambito padre in modo da poter sovrascrivere i suoi campi, mantenendo l'accesso da altri controller.


1
I controller ereditano le proprietà dell'ambito dai controller principali nel proprio ambito. Pulito! la semplicità lo rende bello
Carlos R Balebona,

non posso farlo funzionare. sul secondo controller uso $ scope.data ed è indefinito.
Bart Calixto,

Sta parlando di controller nidificati credo. Neanche a me serve molto.
Norbert Norbertson,

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

usa
niether

5

possiamo archiviare i dati in sessione e usarli ovunque nel nostro programma.

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

Un altro posto

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

4

Ho visto le risposte qui e sta rispondendo alla domanda sulla condivisione dei dati tra i controller, ma cosa devo fare se desidero che un controller informi l'altro del fatto che i dati sono stati modificati (senza utilizzare la trasmissione)? FACILE! Basta usare il famoso modello di visitatore:

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

In questo modo semplice, un controller può aggiornare un altro controller che alcuni dati sono stati aggiornati.


2
Il modello di osservatore ( en.wikipedia.org/wiki/Observer_pattern ) non sarebbe più pertinente qui? Il modello dei visitatori è qualcos'altro ... en.wikipedia.org/wiki/Visitor_pattern
Ant Kutschera

3

Ho creato una fabbrica che controlla l'ambito condiviso tra il modello del percorso, in modo da poter 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


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 clic puoi chiamare il metodo che invoca la trasmissione:

$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

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

5

Un modo usando il servizio angolare:

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
metti qualche spiegazione in più nella tua risposta.
Gerhard Barnard,

2

Crea una fabbrica nel tuo modulo e aggiungi un riferimento della fabbrica nel controller e usa le sue variabili nel controller e ora ottieni il valore dei dati in un altro controller aggiungendo riferimento dove vuoi


2

Non so se sarà di aiuto a nessuno, ma sulla base della risposta Charx (grazie!) Ho creato un servizio cache semplice. Sentiti libero di usare, remixare e condividere:

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

Un modo usando il servizio angolare:

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 L'oggetto $ scope ha $ emit, $ broadcast, $ on AND L'oggetto $ rootScope ha lo stesso $ emit, $ broadcast, $ on

leggi di più sul modello di progettazione di pubblicazione / abbonamento in angolare qui


1

Per migliorare la soluzione proposta da @Maxim utilizzando $ broadcast, l'invio dei dati non cambia

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

ma ai dati di ascolto

$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'
}
}

Primo controller

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

Secondo controller

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

-1

Penso che la

miglior modo

è usare $ localStorage. (Funziona sempre)

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

Lo sarà il tuo CardController

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

Puoi anche aggiungere

if($localStorage.selectedObj){
    $scope.selectedProducts = $localStorage.selectedObj;
}else{
    //redirect to select product using $location.url('/select-product')
}
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.