AngularJS: Come posso passare le variabili tra i controller?


326

Ho due controller angolari:

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
}

Non posso usare Ctrl1dentro Ctrl2perché non è definito. Tuttavia, se provo a passarlo in questo modo ...

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

Ho ricevuto un errore Qualcuno sa come fare questo?

fare

Ctrl2.prototype = new Ctrl1();

Inoltre fallisce.

NOTA: questi controller non sono nidificati l'uno dentro l'altro.


Ci sono molti modi, ma il modo migliore è l'orologio angolare. Sempre quando usiamo un framework è il modo migliore per usare i suoi metodi per fare il lavoro, non dimenticarlo
pejman

Ho trovato questo blog molto utile Blog
Black Mamba

Risposte:


503

Un modo per condividere variabili tra più controller è quello di creare un servizio e iniettarlo in qualsiasi controller in cui si desidera utilizzarlo.

Esempio di servizio semplice:

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

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

Utilizzo del servizio in un controller:

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

Questo è descritto molto bene in questo blog (lezione 2 e in particolare).

Ho scoperto che se si desidera associare queste proprietà su più controller funziona meglio se si associa alla proprietà di un oggetto invece di un tipo primitivo (booleano, stringa, numero) per conservare il riferimento associato.

Esempio: var property = { Property1: 'First' };anziché var property = 'First';.


AGGIORNAMENTO: Per (si spera) chiarire le cose, ecco un violino che mostra un esempio di:

  • Binding a copie statiche del valore condiviso (in myController1)
    • Associazione a una primitiva (stringa)
    • Binding alla proprietà di un oggetto (salvato in una variabile scope)
  • Associazione a valori condivisi che aggiornano l'interfaccia utente quando i valori vengono aggiornati (in myController2)
    • Associazione a una funzione che restituisce una primitiva (stringa)
    • Legando alla proprietà dell'oggetto
    • Associazione bidirezionale alla proprietà di un oggetto

5
In questo caso - come potrebbe "conoscere" l'ambito di Ctrl2 quando sharedProperties.getProperty () cambia valore?
OpherV

5
Se desideri che la tua IU si aggiorni ogni volta che la proprietà cambia, puoi cambiarla bothin una funzione che verrà chiamata / rivalutata durante il processo di digest angolare. Vedi questo violino per un esempio. Inoltre, se si esegue il binding alla proprietà di un oggetto, è possibile utilizzarlo direttamente nella vista e verrà aggiornato man mano che i dati vengono modificati in modo simile a questo esempio .
Gloopy

11
Se si desidera rilevare e reagire alle modifiche nel controller, un'opzione è quella di aggiungere la getProperty()funzione all'ambito e utilizzare $ scope. $ Guardare come in questo esempio . Spero che questi esempi siano d'aiuto!
Gloopy

1
C'è un problema qui perché i servizi dovrebbero essere apolidi. La memorizzazione di una proprietà all'interno di un servizio è errata (ma conveniente). Ho iniziato a usare $ cacheFactory per leggere e scrivere dati. Uso quasi un servizio identico a Gloopy ma invece di memorizzare lo stato nel servizio, ora è nella cache. Per prima cosa crea un servizio cache: angular.module ('CacheService', ['ng']) .factory ('CacheService', function ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Includilo nel tuo app.js, iniettalo nel servizio, usalo così: return CacheService.get (chiave); o CacheService.put (chiave, valore);
Jordan Papaleo,

4
Cercando di capire come e perché questa risposta utilizza .serviceinvece di .factorycome descritto nei documenti angolari. Perché questa risposta viene votata così in alto quando la documentazione utilizza un metodo diverso?
pspahn,

44

Mi piace illustrare cose semplici con semplici esempi :)

Ecco un Serviceesempio molto semplice :


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

E qui il jsbin

Ed ecco un Factoryesempio molto semplice :


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

E qui il jsbin


Se è troppo semplice, ecco un esempio più sofisticato

Vedere anche la risposta qui per i commenti sulle migliori pratiche correlate


1
si sono d'accordo con te. Cerca sempre di semplificare le cose.
Evan Hu,

Qual è il punto nel dichiarare var _dataObj = {};quando si restituisce un riferimento diretto ad esso ..? Questo non è privato . Nel primo esempio puoi fare this.dataObj = {};e nel secondo return { dataObj: {} };è una dichiarazione variabile IMHO inutile.
TJ,

@TJ Il punto è condividere questa variabile tra altri componenti. È un esempio di base che illustra il concetto di condivisione. La variabile È privata all'interno del blocco, quindi la esponi come variabile pubblica usando il modello rivelatore. In questo modo esiste una separazione delle responsabilità tra detenere la variabile e usarla.
Dmitri Zaitsev,

@DmitriZaitsev dici "semplici esempi" ma a meno che tu non mostri correttamente come utilizzare lo stato privato, stai solo confondendo le persone. Nel tuo esempio non esiste uno stato privato purché tu restituisca un riferimento diretto.
TJ,

@TJ Non vedo nulla di confuso. Una variabile privata può essere esposta da un modulo. Sentiti libero di scrivere una risposta migliore.
Dmitri Zaitsev,

26

--- So che questa risposta non è per questa domanda, ma voglio che le persone che leggono questa domanda e vogliono gestire servizi come Fabbriche per evitare problemi a farlo ----

Per questo è necessario utilizzare un servizio o una fabbrica.

I servizi sono la PRATICA MIGLIORE per condividere i dati tra controller non nidificati.

Un'ottima annotazione su questo argomento sulla condivisione dei dati è come dichiarare gli oggetti. Sono stato sfortunato perché sono caduto in una trappola di AngularJS prima di leggerlo, ed ero molto frustrato. Quindi lascia che ti aiuti a evitare questo problema.

Ho letto dal "ng-book: il libro completo su AngularJS" che i modelli ng di AngularJS creati nei controller come dati non corretti sono SBAGLIATI!

Un elemento $ scope dovrebbe essere creato in questo modo:

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

E non così:

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

Questo perché si consiglia (BEST PRACTICE) che il DOM (documento html) contenga le chiamate come

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

Ciò è molto utile per i controller nidificati se si desidera che il controller figlio sia in grado di modificare un oggetto dal controller principale ....

Ma nel tuo caso non vuoi ambiti nidificati, ma c'è un aspetto simile per ottenere oggetti dai servizi ai controller.

Diciamo che hai il tuo servizio 'Factory' e nello spazio di ritorno c'è un oggetto A che contiene oggetto B che contiene oggettoC.

Se dal controller si desidera ottenere l'oggetto C nel proprio ambito, è un errore dire:

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

Non funzionerà ... Invece usa solo un punto.

$scope.neededObjectInController = Factory.ObjectA;

Quindi, nel DOM è possibile chiamare objectC da objectA. Questa è una best practice relativa alle fabbriche e, cosa più importante, contribuirà a evitare errori imprevisti e non irreparabili.


2
Penso che questa sia una buona risposta, ma è piuttosto difficile da digerire.
pspahn,

17

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

Utilizzo di $ rootScope in un modello (accedere alle proprietà con $ root):

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

5
Stai usando variabili con portata globale a quel punto che si discosta dall'idea di AngularJS di scopare localmente tutto all'interno delle sue varie strutture. L'aggiunta di un file di variabili globali consentirebbe di ottenere la stessa cosa e renderebbe più semplice l'individuazione del punto in cui la variabile è stata originariamente definita. Ad ogni modo, non suggerito.
Organiccat,

4
@Organiccat - Capisco la tua preoccupazione ed è per questo che ho già detto che il modo preferito saranno i servizi, senza dubbio. Ma ya angular offre anche questo. Sta a te decidere come gestire il tuo globale. Ho avuto uno scenario in cui questo approccio ha funzionato meglio per me.
Sanjeev,

8

L'esempio sopra ha funzionato come un fascino. Ho appena fatto una modifica nel caso avessi bisogno di gestire più valori. Spero che aiuti!

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

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});

1
Ho anche creato un campione utilizzando un servizio per condividere dati tra diversi controller. Spero che vi piaccia. jsfiddle.net/juazammo/du53553a/1
Juan Zamora,

1
Anche se funziona, di solito questa è la sintassi per .factory. A .servicedovrebbe essere usato "se definisci il tuo servizio come tipo / classe" come da docs.angularjs.org/api/auto/service/$provide#service
Dmitri Zaitsev

1
Dmitri, hai ragione, comunque i ragazzi angolari dal mio punto di vista, hanno appena cambiato un po 'il concetto che avevo tra servizi (facciate) e fabbriche .... vabbè ....
Juan Zamora,

1
E correggimi se sbaglio, i servizi sono destinati a restituire qualcosa che può essere un oggetto o un valore. Le fabbriche hanno lo scopo di creare oggetti. Un facace che in realtà è una raccolta di funzionalità che restituiscono qualcosa, è ciò che ho pensato servizi dove. Comprese le funzionalità di invocazione dalle fabbriche. Ancora una volta, sto entrando nella nozione di base di ciò che è per me e non di ciò che realmente è dalla prospettiva angolare. (Abstract Factory dofactory.com/net/abstract-factory-design-pattern ) e un approccio Adapter è ciò che esporrò come servizio
Juan Zamora,

1
Controlla qui il modello dell'adattatore dofactory.com/net/adapter-design-pattern
Juan Zamora,

6

Tendo a usare i valori, felice per chiunque di discutere sul perché questa è una cattiva idea ..

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

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

Quindi iniettare il valore secondo un servizio.

Imposta in ctrl1:

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

e accesso da ctrl2:

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

});

in che cosa differisce dall'uso di un servizio?
dopatraman,

5

L'esempio seguente mostra come passare le variabili tra i controller dei fratelli e come agire quando il valore cambia.

Esempio di caso d'uso: in una barra laterale è presente un filtro che modifica il contenuto di un'altra 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>


4

Vorrei contribuire a questa domanda sottolineando che il modo raccomandato per condividere i dati tra i controller e persino le direttive è utilizzare i servizi (fabbriche) come è già stato sottolineato, ma vorrei anche fornire un esempio pratico funzionante di come farlo.

Ecco il plunker di lavoro: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

Innanzitutto, crea il tuo servizio , che avrà i tuoi dati condivisi :

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

Quindi, basta iniettarlo sui controller e acquisire i dati condivisi sul proprio ambito:

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

Puoi anche farlo per le tue direttive , funziona allo stesso modo:

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

Spero che questa risposta pratica e chiara possa essere utile a qualcuno.


3

Potresti farlo con servizi o fabbriche. Sono essenzialmente gli stessi a parte per alcune differenze fondamentali. Ho trovato questa spiegazione su thinkster.io come la più facile da seguire. Semplice, al punto ed efficace.


1
"Potresti farlo con servizi o fabbriche" - Come ..? Come farlo è ciò che l'OP sta chiedendo ... si prega di inviare una risposta completa nello stackoverflow stesso anziché collegarsi a risorse esterne, i collegamenti potrebbero andare fuori orario.
TJ,

2

Non potresti anche rendere la proprietà parte dell'ambito padre?

$scope.$parent.property = somevalue;

Non sto dicendo che è giusto ma funziona.


3
L'autore ha affermato che NOTE: These controllers are not nested inside each other.. Se questi fossero controller nidificati o controller che condividevano lo stesso genitore, ciò funzionerebbe, ma non possiamo aspettarci che.
Chris Foster,

2
È generalmente una cattiva pratica fare affidamento su $parentse ciò può essere evitato. Un componente riutilizzabile ben progettato non dovrebbe conoscere i suoi genitori.
Dmitri Zaitsev,

2

Ah, prendi un po 'di queste nuove cose come un'altra alternativa. È un archivio locale e funziona dove lavora angolare. Prego. (Ma davvero, grazie al ragazzo)

https://github.com/gsklee/ngStorage

Definisci le tue impostazioni predefinite:

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

Accedi ai valori:

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

Memorizza i valori

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

Ricorda di iniettare ngStorage nella tua app e $ localStorage nel controller.


1
Questo risolve un problema diverso: l'archiviazione persistente. Non è una soluzione scalabile per il problema in questione poiché rende il codice trapelato con effetti collaterali come la modifica dell'oggetto di archiviazione locale con vulnerabilità dello scontro tra nomi, tra gli altri.
Dmitri Zaitsev,

1

Ci sono due modi per farlo

1) Utilizzare il servizio get / set

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

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

1

Secondo approccio:

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>

Per maggiori dettagli vedi plunker:

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


1
Funziona solo se Ctrl2(il listener) è un controller figlio di Ctrl1. I controllori di pari livello devono comunicare tramite $rootScope.
Herzbube,

0

Se non si desidera fornire assistenza, è possibile farlo in questo modo.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;

37
Non dubito che questa risposta funzioni, ma voglio notare che ciò va contro la filosofia di AngularJS di non avere mai oggetti DOM nel codice del modello / controller.
JoeCool

3
-1 perché, secondo me, la comunicazione del controller tramite DOM non è buona.
Chris Foster,

3
@ChrisFoster, solo perché un martello viene venduto come "strumento", ciò non significa che non possa essere utilizzato come grammatura della carta. Sono sicuro che per ogni framework o strumento là fuori troverai sempre sviluppatori che devono "piegare" l'elenco delle "migliori pratiche".
Andrei V,

5
@AndreiV - Scarsa analogia, non ci sono svantaggi nell'usare un martello come grammatura della carta. Fare cattive pratiche come questa ha chiari svantaggi e può facilmente portare al codice spaghetti. Il codice sopra è fragile perché ora dipende da dove si trova il controller nel DOM ed è molto difficile da testare. L'uso di un servizio è una buona pratica per un motivo, perché non lega l'implementazione al modello. Sono d'accordo che gli sviluppatori devono spesso piegare l'elenco delle best practice, ma non quando esiste una best practice chiara, comune e più modulare che funzioni meglio.
Chris Foster,

-1

Oltre a $ rootScope e ai servizi, esiste una soluzione alternativa semplice e chiara per estendere gli angoli per aggiungere i dati condivisi:

nei controller:

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

Queste proprietà appartengono all'oggetto "angolare", separato dagli ambiti e possono essere condivisi in ambiti e servizi.

1 vantaggio che non devi iniettare l'oggetto: sono accessibili ovunque immediatamente dopo la tua definizione!


2
È come avere variabili globali su tutto l' windowoggetto ... Se hai intenzione di inquinare angolare, perché non andare avanti e inquinare l'oggetto finestra ...
TJ
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.