AngularJS: Wie kann ich Variablen zwischen Controllern übergeben?

326

Ich habe zwei Angular Controller:

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
}

Ich kann nicht Ctrl1drinnen verwenden, Ctrl2weil es undefiniert ist. Wenn ich es jedoch so weitergebe ...

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

Ich bekomme eine Fehlermeldung. Weiß jemand, wie man das macht?

Tun

Ctrl2.prototype = new Ctrl1();

Scheitert auch.

HINWEIS: Diese Controller sind nicht ineinander verschachtelt.

Dopatraman
quelle
Es gibt viele Möglichkeiten, aber der beste Weg ist die eckige Uhr. Immer wenn wir ein Framework verwenden, ist dies der beste Weg, um ihre eigenen Methoden für die Arbeit zu verwenden. Vergessen Sie dies nicht
Pejman
Ich fand diesen Blog sehr hilfreich Blog
Black Mamba

Antworten:

503

Eine Möglichkeit, Variablen für mehrere Controller freizugeben, besteht darin , einen Dienst zu erstellen und ihn in einen beliebigen Controller einzufügen, in dem Sie ihn verwenden möchten.

Einfaches Servicebeispiel:

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

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

Verwenden des Dienstes in einer Steuerung:

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

Dies wird in diesem Blog sehr gut beschrieben (Lektion 2 und insbesondere weiter).

Ich habe festgestellt, dass es besser funktioniert, wenn Sie über mehrere Controller hinweg an diese Eigenschaften binden möchten, wenn Sie an die Eigenschaft eines Objekts binden, anstatt an einen primitiven Typ (Boolescher Wert, Zeichenfolge, Zahl), um die gebundene Referenz beizubehalten.

Beispiel: var property = { Property1: 'First' };statt var property = 'First';.


UPDATE: Um (hoffentlich) die Dinge hier klarer zu machen, gibt es eine Geige , die ein Beispiel zeigt für:

  • Bindung an statische Kopien des gemeinsam genutzten Werts (in myController1)
    • Bindung an ein Grundelement (Zeichenfolge)
    • Bindung an die Eigenschaft eines Objekts (in einer Bereichsvariablen gespeichert)
  • Bindung an gemeinsam genutzte Werte, die die Benutzeroberfläche aktualisieren, wenn die Werte aktualisiert werden (in myController2)
    • Bindung an eine Funktion, die ein Grundelement (Zeichenfolge) zurückgibt
    • Bindung an die Objekteigenschaft
    • Zwei-Wege-Bindung an die Eigenschaft eines Objekts
Gloopy
quelle
5
In diesem Fall - wie würde der Bereich von Strg2 "wissen", wenn sharedProperties.getProperty () den Wert ändert?
OpherV
5
Wenn Sie möchten, dass Ihre Benutzeroberfläche jedes Mal aktualisiert wird, wenn sich die Eigenschaft ändert, können Sie sie bothin eine Funktion ändern , die während des Angle Digest-Prozesses aufgerufen / neu bewertet wird. Ein Beispiel finden Sie in dieser Geige . Wenn Sie an die Eigenschaft eines Objekts binden, können Sie es direkt in Ihrer Ansicht verwenden. Es wird aktualisiert, wenn die Daten ähnlich wie in diesem Beispiel geändert werden .
Gloopy
11
Wenn Sie Änderungen in Ihrem Controller erkennen und darauf reagieren möchten, können Sie die getProperty()Funktion dem Bereich hinzufügen und $ scope. $ Watch wie in diesem Beispiel verwenden . Hoffe diese Beispiele helfen!
Gloopy
1
Hier gibt es ein Problem, da Dienste zustandslos sein sollten. Das Speichern einer Immobilie innerhalb eines Dienstes ist falsch (aber bequem). Ich habe angefangen, $ cacheFactory zum Lesen und Schreiben von Daten zu verwenden. Ich verwende fast einen identischen Dienst wie Gloopy, aber anstatt den Status im Dienst zu speichern, befindet er sich jetzt im Cache. Erstellen Sie zuerst einen Cache-Dienst: angle.module ('CacheService', ['ng']) .factory ('CacheService', Funktion ($ cacheFactory) {return $ cacheFactory ('CacheService');}); Fügen Sie es in Ihre app.js ein, fügen Sie es in den Dienst ein und verwenden Sie es wie folgt: return CacheService.get (key); oder CacheService.put (Schlüssel, Wert);
Jordan Papaleo
4
Der Versuch herauszufinden, wie und warum diese Antwort verwendet wird, .serviceanstatt .factorywie in den Angular-Dokumenten beschrieben. Warum wird diese Antwort so hoch bewertet, wenn die Dokumentation eine andere Methode verwendet?
pspahn
44

Ich illustriere gerne einfache Dinge anhand einfacher Beispiele :)

Hier ist ein sehr einfaches ServiceBeispiel:


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

Und hier der jsbin

Und hier ist ein sehr einfaches FactoryBeispiel:


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

Und hier der jsbin


Wenn das zu einfach ist, finden Sie hier ein komplexeres Beispiel

In der Antwort finden Sie auch Kommentare zu verwandten Best Practices

Dmitri Zaitsev
quelle
1
ja, ich stimme dir zu. Versuchen Sie immer, die Dinge einfach zu machen.
Evan Hu
Was bringt es, var _dataObj = {};wenn Sie einen direkten Verweis darauf zurückgeben? Das ist nicht privat . Im ersten Beispiel können Sie dies tun this.dataObj = {};und im zweiten Beispiel handelt return { dataObj: {} };es sich meiner Meinung nach um eine nutzlose Variablendeklaration.
TJ
@TJ Der Punkt ist, diese Variable unter anderen Komponenten zu teilen. Es ist ein grundlegendes Beispiel, das das Konzept des Teilens veranschaulicht. Die Variable ist innerhalb des Blocks privat, und Sie machen sie dann mithilfe des aufschlussreichen Musters als öffentliche Variable verfügbar. Auf diese Weise werden die Verantwortlichkeiten zwischen dem Halten der Variablen und ihrer Verwendung getrennt.
Dmitri Zaitsev
@DmitriZaitsev Sie sagen "einfache Beispiele", aber wenn Sie nicht richtig zeigen, wie man den privaten Staat nutzt, verwirren Sie nur die Leute. In Ihrem Beispiel gibt es keinen privaten Status, solange Sie eine direkte Referenz zurückgeben.
TJ
@TJ Ich sehe nichts verwirrendes. Eine private Variable kann von einem Modul verfügbar gemacht werden. Fühlen Sie sich frei, eine bessere Antwort zu schreiben.
Dmitri Zaitsev
26

--- Ich weiß, dass diese Antwort nicht für diese Frage ist, aber ich möchte, dass Leute, die diese Frage lesen und mit Diensten wie Fabriken umgehen, Probleme damit vermeiden ----

Dazu müssen Sie einen Service oder eine Fabrik nutzen.

Die Dienste sind die BESTE PRAXIS , um Daten zwischen nicht verschachtelten Controllern auszutauschen .

Eine sehr sehr gute Anmerkung zu diesem Thema über die gemeinsame Nutzung von Daten ist das Deklarieren von Objekten. Ich hatte Pech, weil ich in eine AngularJS-Falle geraten bin, bevor ich darüber gelesen habe, und ich war sehr frustriert. Lassen Sie mich Ihnen helfen, diese Probleme zu vermeiden.

Ich habe aus dem "ng-book: Das komplette Buch über AngularJS" gelesen, dass AngularJS ng-Modelle, die in Controllern als Bare-Data erstellt werden, FALSCH sind!

Ein $ scope-Element sollte folgendermaßen erstellt werden:

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

Und nicht so:

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

Dies liegt daran, dass empfohlen wird (BEST PRACTICE), dass das DOM (HTML-Dokument) die Aufrufe als enthält

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

Dies ist für verschachtelte Controller sehr hilfreich, wenn Sie möchten, dass Ihr untergeordneter Controller ein Objekt vom übergeordneten Controller ändern kann.

In Ihrem Fall möchten Sie jedoch keine verschachtelten Bereiche, aber es gibt einen ähnlichen Aspekt, um Objekte von Diensten an die Controller zu übertragen.

Nehmen wir an, Sie haben Ihren Service 'Factory' und im Rückgabebereich befindet sich ein ObjektA, das ObjektB enthält, das ObjektC enthält.

Wenn Sie von Ihrem Controller aus das Objekt C in Ihren Bereich holen möchten, ist es ein Fehler zu sagen:

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

Das wird nicht funktionieren ... Verwenden Sie stattdessen nur einen Punkt.

$scope.neededObjectInController = Factory.ObjectA;

Anschließend können Sie im DOM objectC von objectA aus aufrufen. Dies ist eine bewährte Methode für Fabriken und hilft vor allem dabei, unerwartete und nicht auffindbare Fehler zu vermeiden.

AFP_555
quelle
2
Ich denke, das ist eine gute Antwort, aber es ist ziemlich schwer zu verdauen.
pspahn
17

Lösung ohne Service zu erstellen, mit $ rootScope:

Um Eigenschaften für App-Controller freizugeben, können Sie Angular $ rootScope verwenden. Dies ist eine weitere Option zum Freigeben von Daten, damit die Benutzer davon erfahren.

Die bevorzugte Methode zum Teilen einiger Funktionen zwischen Controllern ist Services, um eine globale Eigenschaft zu lesen oder zu ändern, die Sie mit $ rootcope verwenden können.

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

Verwenden von $ rootScope in einer Vorlage (Zugriffseigenschaften mit $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Sanjeev
quelle
5
Sie verwenden an diesem Punkt Variablen mit globalem Gültigkeitsbereich, was von der AngularJS-Idee abweicht, alles innerhalb seiner verschiedenen Strukturen lokal zu erfassen. Das Hinzufügen einer globalen Variablendatei würde dasselbe bewirken und es einfacher machen, herauszufinden, wo die Variable ursprünglich definiert wurde. So oder so, nicht vorgeschlagen.
Organiccat
4
@Organiccat - Ich verstehe Ihre Besorgnis und deshalb habe ich bereits erwähnt, dass der bevorzugte Weg Dienstleistungen sein werden, ohne Zweifel. Aber ya Winkel bietet auch diesen Weg. Es liegt an Ihnen, wie Sie Ihre globalen verwalten möchten. Ich hatte ein Szenario, in dem dieser Ansatz für mich am besten funktionierte.
Sanjeev
8

Das obige Beispiel hat wie ein Zauber gewirkt. Ich habe gerade eine Änderung vorgenommen, nur für den Fall, dass ich mehrere Werte verwalten muss. Ich hoffe das hilft!

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

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});
Juan Zamora
quelle
1
Ich habe auch ein Beispiel mit einem Dienst erstellt, um Daten zwischen verschiedenen Controllern auszutauschen. Ich hoffe es gefällt euch. jsfiddle.net/juazammo/du53553a/1
Juan Zamora
1
Obwohl es funktioniert, ist dies normalerweise die Syntax für .factory. A .servicesollte verwendet werden "wenn Sie Ihren Dienst als Typ / Klasse definieren" gemäß docs.angularjs.org/api/auto/service/$provide#service
Dmitri Zaitsev
1
Dmitri, Sie haben Recht, aber die Angular-Leute aus meiner Sicht haben das Konzept, das ich zwischen Dienstleistungen (Fassaden) und Fabriken hatte, ein wenig geändert ...
Juan Zamora
1
Und korrigieren Sie mich, wenn ich falsch liege. Services sollen etwas zurückgeben, das ein Objekt oder ein Wert sein kann. Fabriken sollen Objekte erstellen. Ein Gesicht, das eigentlich eine Sammlung von Funktionen ist, die etwas zurückgeben, ist das, was ich für Dienstleistungen hielt. Einschließlich Aufrufen von Funktionen aus Fabriken. Wieder komme ich in die Grundidee, was dies für mich ist und nicht, was eigentlich aus der Winkelperspektive ist. (Abstract Factory dofactory.com/net/abstract-factory-design-pattern ) und ein Adapter-Ansatz ist das, was ich als Service aussetzen werde
Juan Zamora
1
Überprüfen Sie das Adaptermuster hier dofactory.com/net/adapter-design-pattern
Juan Zamora
6

Ich neige dazu, Werte zu verwenden, und freue mich, wenn jemand darüber spricht, warum dies eine schlechte Idee ist.

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

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

Fügen Sie dann den Wert gemäß einem Dienst ein.

In Strg1 einstellen:

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

und Zugriff von Strg2:

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

});
Gekühlte Flamme
quelle
Wie unterscheidet sich dies von der Nutzung eines Dienstes?
Dopatraman
5

Das folgende Beispiel zeigt, wie Variablen zwischen Geschwister- Controllern und übergeben werden eine Aktion ausgeführt werden, wenn sich der Wert ändert.

Anwendungsfallbeispiel: Sie haben einen Filter in einer Seitenleiste, der den Inhalt einer anderen Ansicht ändert.

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
quelle
4

Ich möchte zu dieser Frage beitragen, indem ich darauf hinweise, dass die empfohlene Methode zum Austausch von Daten zwischen Controllern und sogar Direktiven darin besteht, Dienste (Fabriken) zu verwenden, wie bereits erwähnt, aber ich möchte auch eine praktisches Beispiel dafür, wie das gemacht werden sollte.

Hier ist der funktionierende Plunker: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

Zuerst erstellen Sie Ihre Service , dass Ihr haben gemeinsam genutzter Daten :

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

Fügen Sie es dann einfach in Ihre Controller ein und greifen Sie auf die freigegebenen Daten in Ihrem Bereich zu:

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

Sie können dies auch für Ihre Anweisungen tun. Dies funktioniert auf die gleiche Weise:

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

Hoffe, diese praktische und saubere Antwort kann jemandem helfen.

Fedaykin
quelle
3

Sie könnten das mit Dienstleistungen oder Fabriken tun. Sie sind für einige Kernunterschiede im Wesentlichen gleich. Ich fand diese Erklärung auf thinkster.io am einfachsten zu befolgen. Einfach, auf den Punkt und effektiv.

Noahdecoco
quelle
1
"Das könnte man mit Dienstleistungen oder Fabriken machen" - Wie ..? Wie das geht, fragt das OP ... Bitte geben Sie die vollständige Antwort im Stackoverflow selbst ein, anstatt auf externe Ressourcen zu verlinken. Die Links können im Laufe der Zeit ausfallen.
TJ
2

Könnten Sie die Eigenschaft nicht auch Teil des übergeordneten Bereichs machen?

$scope.$parent.property = somevalue;

Ich sage nicht, dass es richtig ist, aber es funktioniert.

SideFX
quelle
3
Der Autor gab das an NOTE: These controllers are not nested inside each other.. Wenn dies verschachtelte Controller oder Controller wären, die dasselbe übergeordnete Element gemeinsam nutzen, würde dies funktionieren, aber das können wir nicht erwarten.
Chris Foster
2
Es ist im Allgemeinen eine schlechte Praxis, sich darauf zu verlassen, $parentwenn dies vermieden werden kann. Eine gut gestaltete wiederverwendbare Komponente sollte nichts über ihre Eltern wissen.
Dmitri Zaitsev
2

Ah, haben Sie ein bisschen von diesem neuen Zeug als eine andere Alternative. Es ist ein lokaler Speicher und funktioniert dort, wo eckig funktioniert. Bitte. (Aber wirklich, danke dem Kerl)

https://github.com/gsklee/ngStorage

Definieren Sie Ihre Standardeinstellungen:

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

Greifen Sie auf die Werte zu:

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

Speichern Sie die Werte

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

Denken Sie daran, ngStorage in Ihre App und $ localStorage in Ihren Controller einzufügen.

kJamesy
quelle
1
Dies löst ein anderes Problem - die dauerhafte Speicherung. Es ist keine skalierbare Lösung für das betreffende Problem, da Ihr Code durch Nebenwirkungen wie das Ändern des lokalen Speicherobjekts unter anderem durch die Sicherheitsanfälligkeit von Namenskonflikten undicht wird.
Dmitri Zaitsev
1

Es gibt zwei Möglichkeiten, dies zu tun

1) Verwenden Sie den get / set-Dienst

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

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

Zweiter Ansatz:

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>

Für weitere Details siehe Plunker:

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

Codiee
quelle
1
Funktioniert nur, wenn Ctrl2(der Listener) ein untergeordneter Controller von ist Ctrl1. Geschwistercontroller müssen über kommunizieren $rootScope.
Herzbube
0

Wenn Sie keinen Service erbringen möchten, können Sie dies tun.

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;
Dankesnote
quelle
37
Ich bezweifle nicht, dass diese Antwort funktioniert, aber ich möchte darauf hinweisen, dass dies gegen die Philosophie von AngularJS verstößt, niemals DOM-Objekte in Ihrem Modell- / Controller-Code zu haben.
JoeCool
3
-1 weil die Controller-Kommunikation über das DOM meiner Meinung nach eine schlechte Praxis ist.
Chris Foster
3
@ChrisFoster, nur weil ein Hammer als "Werkzeug" verkauft wird, heißt das nicht, dass er nicht als Briefbeschwerer verwendet werden kann. Ich bin sicher, dass Sie für jedes Framework oder Tool immer Entwickler finden, die die Liste der "Best Practices" "biegen" müssen.
Andrei V
5
@AndreiV - Schlechte Analogie, es gibt keinen Nachteil, einen Hammer als Papiergewicht zu verwenden. Eine solche schlechte Praxis hat klare Nachteile und kann leicht zu Spaghetti-Code führen. Der obige Code ist fragil, da er jetzt davon abhängt, wo sich Ihr Controller im DOM befindet, und sehr schwer zu testen ist. Die Verwendung eines Dienstes ist aus einem bestimmten Grund eine bessere Vorgehensweise, da Ihre Implementierung nicht an Ihre Vorlage gebunden ist. Ich bin damit einverstanden, dass Entwickler häufig die Best-Practice-Liste biegen müssen, aber nicht, wenn es eine klare, gemeinsame, modularere Best-Practice gibt, die besser funktioniert.
Chris Foster
-1

Neben $ rootScope und Services gibt es eine saubere und einfache alternative Lösung, um Angular zu erweitern und die gemeinsam genutzten Daten hinzuzufügen:

in den Controllern:

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

Diese Eigenschaften gehören zu einem 'Winkel'-Objekt, das von den Bereichen getrennt ist, und können in Bereichen und Diensten gemeinsam genutzt werden.

1 Vorteil davon, dass Sie das Objekt nicht injizieren müssen: Sie sind unmittelbar nach Ihrer Definition überall zugänglich!

williamjxj
quelle
2
Dies ist so, als hätten Sie globale Variablen im gesamten windowObjekt ... Wenn Sie den Winkel verschmutzen möchten, warum nicht einfach das Fensterobjekt verschmutzen ...
TJ