Daten zwischen AngularJS-Controllern austauschen


362

Ich versuche, Daten zwischen Controllern auszutauschen. Der Anwendungsfall ist ein mehrstufiges Formular. Die in einem Eingang eingegebenen Daten werden später an mehreren Anzeigeorten außerhalb des ursprünglichen Controllers verwendet. Code unten und in jsfiddle hier .

HTML

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="FirstName"><!-- Input entered here -->
    <br>Input is : <strong>{{FirstName}}</strong><!-- Successfully updates here -->
</div>

<hr>

<div ng-controller="SecondCtrl">
    Input should also be here: {{FirstName}}<!-- How do I automatically updated it here? -->
</div>

JS

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// make a factory to share data between controllers
myApp.factory('Data', function(){
    // I know this doesn't work, but what will?
    var FirstName = '';
    return FirstName;
});

// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

Jede Hilfe wird sehr geschätzt.


Antworten:


481

Eine einfache Lösung besteht darin, dass Ihre Fabrik ein Objekt zurückgibt und Ihre Controller mit einem Verweis auf dasselbe Objekt arbeiten lassen:

JS:

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// Create the factory that share the Fact
myApp.factory('Fact', function(){
  return { Field: '' };
});

// Two controllers sharing an object that has a string in it
myApp.controller('FirstCtrl', function( $scope, Fact ){
  $scope.Alpha = Fact;
});

myApp.controller('SecondCtrl', function( $scope, Fact ){
  $scope.Beta = Fact;
});

HTML:

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="Alpha.Field">
    First {{Alpha.Field}}
</div>

<div ng-controller="SecondCtrl">
<input type="text" ng-model="Beta.Field">
    Second {{Beta.Field}}
</div>

Demo: http://jsfiddle.net/HEdJF/

Wenn Anwendungen größer, komplexer und schwieriger zu testen sind, möchten Sie möglicherweise nicht das gesamte Objekt ab Werk auf diese Weise verfügbar machen, sondern nur über Getter und Setter eingeschränkten Zugriff gewähren:

myApp.factory('Data', function () {

    var data = {
        FirstName: ''
    };

    return {
        getFirstName: function () {
            return data.FirstName;
        },
        setFirstName: function (firstName) {
            data.FirstName = firstName;
        }
    };
});

Bei diesem Ansatz liegt es an den konsumierenden Controllern, die Fabrik mit neuen Werten zu aktualisieren und auf Änderungen zu achten, um diese zu erhalten:

myApp.controller('FirstCtrl', function ($scope, Data) {

    $scope.firstName = '';

    $scope.$watch('firstName', function (newValue, oldValue) {
        if (newValue !== oldValue) Data.setFirstName(newValue);
    });
});

myApp.controller('SecondCtrl', function ($scope, Data) {

    $scope.$watch(function () { return Data.getFirstName(); }, function (newValue, oldValue) {
        if (newValue !== oldValue) $scope.firstName = newValue;
    });
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="firstName">
  <br>Input is : <strong>{{firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{firstName}}
</div>

Demo: http://jsfiddle.net/27mk1n1o/


2
Die erste Lösung funktionierte am besten für mich - ließ den Service das Objekt warten und die Controller handhabten nur mit der Referenz. Vielen Dank.
Johnkeese

5
Die $ scope. $ watch-Methode
eignet sich hervorragend,

Anstatt ein Objekt wie return { FirstName: '' };oder ein Objekt zurückzugeben, das Methoden enthält, die mit einem Objekt interagieren, wäre es nicht vorzuziehen, eine Instanz einer Klasse ( function) zurückzugeben, damit sie bei Verwendung Ihres Objekts einen bestimmten Typ hat und nicht nur ein Objekt{}" ?
RPDeshaies

8
Nur ein Heads-Up, die Listener-Funktion benötigt keine newValue !== oldValuePrüfung, da Angular die Funktion nicht ausführt, wenn oldValue und newValue identisch sind. Die einzige Ausnahme ist, wenn Sie sich um den Anfangswert kümmern. Siehe: docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.

@tasseKATT, Es funktioniert gut, wenn ich die Seite nicht aktualisiere. Können Sie mir eine Lösung vorschlagen, wenn ich die Seite aktualisiere?
MaNn

71

Ich bevorzuge es nicht dafür zu verwenden $watch. Anstatt den gesamten Dienst dem Bereich eines Controllers zuzuweisen, können Sie nur die Daten zuweisen.

JS:

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

myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    }
    // Other methods or objects can go here
  };
});

myApp.controller('FirstCtrl', function($scope, MyService){
  $scope.data = MyService.data;
});

myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;
});

HTML:

<div ng-controller="FirstCtrl">
  <input type="text" ng-model="data.firstName">
  <br>Input is : <strong>{{data.firstName}}</strong>
</div>
<hr>
<div ng-controller="SecondCtrl">
  Input should also be here: {{data.firstName}}
</div>

Alternativ können Sie die Servicedaten mit einer direkten Methode aktualisieren.

JS:

// A new factory with an update method
myApp.factory('MyService', function(){
  return {
    data: {
      firstName: '',
      lastName: ''
    },
    update: function(first, last) {
      // Improve this method as needed
      this.data.firstName = first;
      this.data.lastName = last;
    }
  };
});

// Your controller can use the service's update method
myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;

   $scope.updateData = function(first, last) {
     MyService.update(first, last);
   }
});

Sie verwenden nicht explizit watch (), aber intern aktualisiert AngularJS die Ansicht mit neuen Werten in $ scope-Variablen. In Bezug auf die Leistung ist es das gleiche oder nicht?
Mart Dominguez

4
Ich bin mir bei der Leistung beider Methoden nicht sicher. Soweit ich weiß, führt Angular bereits eine Digest-Schleife für den Bereich aus, sodass das Einrichten zusätzlicher Überwachungsausdrücke wahrscheinlich mehr Arbeit für Angular bedeutet. Sie müssten einen Leistungstest durchführen, um dies wirklich herauszufinden. Ich bevorzuge nur die Datenübertragung und -freigabe über die obige Antwort gegenüber der Aktualisierung über $ watch-Ausdrücke.
Bennick

2
Dies ist keine Alternative zu $ ​​watch. Es ist eine andere Möglichkeit, Daten zwischen zwei Angular-Objekten, in diesem Fall Controllern, auszutauschen, ohne $ watch verwenden zu müssen.
Bennick

1
IMO ist diese Lösung die beste, da sie eher dem deklarativen Ansatz von Angular entspricht als dem Hinzufügen von $ watch-Anweisungen, die sich etwas jQuery-artiger anfühlen.
JamesG

8
Warum ist dies nicht die am häufigsten gewählte Antwort? Immerhin macht $ watch den Code komplexer
Shyamal Parikh

11

Es gibt viele Möglichkeiten, wie Sie die Daten zwischen Controllern teilen können

  1. Nutzung von Diensten
  2. Verwenden von $ state.go-Diensten
  3. mit stateparams
  4. mit Rootscope

Erklärung jeder Methode:

  1. Ich werde es nicht erklären, wie es bereits von jemandem erklärt wurde

  2. mit $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
  3. $stateparamfunktioniert ähnlich wie $state.go, Sie übergeben es als Objekt vom Sender-Controller und sammeln es im Empfänger-Controller mit stateparam

  4. mit $rootscope

    (a) Senden von Daten vom Kind zum übergeordneten Controller

      $scope.Save(Obj,function(data) {
          $scope.$emit('savedata',data); 
          //pass the data as the second parameter
      });
    
      $scope.$on('savedata',function(event,data) {
          //receive the data as second parameter
      }); 

    (b) Senden von Daten vom übergeordneten zum untergeordneten Controller

      $scope.SaveDB(Obj,function(data){
          $scope.$broadcast('savedata',data);
      });
    
      $scope.SaveDB(Obj,function(data){`enter code here`
          $rootScope.$broadcast('saveCallback',data);
      });

6

Ich habe eine Factory erstellt, die den gemeinsamen Bereich zwischen dem Muster des Routenpfads steuert, sodass Sie die freigegebenen Daten nur dann verwalten können, wenn Benutzer im gleichen übergeordneten Routenpfad navigieren.

.controller('CadastroController', ['$scope', 'RouteSharedScope',
    function($scope, routeSharedScope) {
      var customerScope = routeSharedScope.scopeFor('/Customer');
      //var indexScope = routeSharedScope.scopeFor('/');
    }
 ])

Wenn der Benutzer zu einem anderen Routenpfad wechselt, z. B. '/ Support', werden die gemeinsam genutzten Daten für Pfad '/ Kunde' automatisch zerstört. Wenn der Benutzer stattdessen zu "untergeordneten" Pfaden wie "/ Customer / 1" oder "/ Customer / list" wechselt, wird der Bereich nicht zerstört.

Ein Beispiel finden Sie hier: http://plnkr.co/edit/OL8of9


Verwenden $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })Sie, wenn Sie ui.router
Nuno Silva

6

Es gibt mehrere Möglichkeiten, Daten zwischen Controllern auszutauschen

  • Winkeldienste
  • $ Broadcast, $ Emit-Methode
  • Kommunikation zwischen übergeordneten und untergeordneten Controllern
  • $ Rootscope

Wie wir wissen $rootscope ist dies kein bevorzugter Weg für die Datenübertragung oder Kommunikation, da es sich um einen globalen Bereich handelt, der für die gesamte Anwendung verfügbar ist

Für den Datenaustausch zwischen Angular Js-Controllern sind Angular-Dienste Best Practices, z. .factory, .service
Für Referenz

Im Falle einer Datenübertragung von Eltern auf das Kind Controller können Sie direkt Zugriff Mutter Daten in Child - Controller durch $scope
Wenn Sie verwenden , ui-routerdann können Sie verwenden , $stateParmasURL - Parameter wie passieren id, name, keyusw.

$broadcastDies ist auch eine gute Möglichkeit, Daten zwischen Controllern von übergeordneten $emitzu untergeordneten Controllern zu übertragen und Daten von untergeordneten Controllern zu übergeordneten Controllern zu übertragen

HTML

<div ng-controller="FirstCtrl">
   <input type="text" ng-model="FirstName">
   <br>Input is : <strong>{{FirstName}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
   Input should also be here: {{FirstName}}
</div>

JS

myApp.controller('FirstCtrl', function( $rootScope, Data ){
    $rootScope.$broadcast('myData', {'FirstName': 'Peter'})
});

myApp.controller('SecondCtrl', function( $rootScope, Data ){
    $rootScope.$on('myData', function(event, data) {
       $scope.FirstName = data;
       console.log(data); // Check in console how data is coming
    });
});

Weitere Informationen finden Sie unter dem angegebenen Link$broadcast


4

Einfachste Lösung:

Ich habe einen AngularJS-Dienst verwendet .

Schritt 1: Ich habe einen AngularJS-Dienst namens SharedDataService erstellt.

myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

Schritt 2: Erstellen Sie zwei Controller und verwenden Sie den oben erstellten Dienst.

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
   function ($scope, SharedDataService) {
   $scope.Person = SharedDataService;
   }]);

Schritt 3: Verwenden Sie einfach die erstellten Controller in der Ansicht.

<body ng-app="myApp">

<div ng-controller="FirstCtrl">
<input type="text" ng-model="Person.name">
<br>Input is : <strong>{{Person.name}}</strong>
</div>

<hr>

<div ng-controller="SecondCtrl">
Input should also be here: {{Person.name}}
</div>

</body>

Um eine funktionierende Lösung für dieses Problem zu sehen, klicken Sie bitte auf den unten stehenden Link

https://codepen.io/wins/pen/bmoYLr

.html Datei:

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

<body ng-app="myApp">

  <div ng-controller="FirstCtrl">
    <input type="text" ng-model="Person.name">
    <br>Input is : <strong>{{Person.name}}</strong>
   </div>

<hr>

  <div ng-controller="SecondCtrl">
    Input should also be here: {{Person.name}}
  </div>

//Script starts from here

<script>

var myApp = angular.module("myApp",[]);
//create SharedDataService
myApp.service('SharedDataService', function () {
     var Person = {
        name: ''

    };
    return Person;
});

//First Controller
myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
    }]);

//Second Controller
myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
    function ($scope, SharedDataService) {
    $scope.Person = SharedDataService;
}]);

</script>


</body>
</html>

1
Dies ist ein sehr klares Beispiel für uns neue Leute, die eckige Js verwenden. Wie würden Sie die Daten aus einer Eltern / Kind-Beziehung teilen / weitergeben? FirstCtrlist Eltern und bekommt ein Versprechen, das SecondCtrl(Kind) auch brauchen wird? Ich möchte nicht zwei API-Anrufe tätigen. Vielen Dank.
Chris22

1

Es gibt einen anderen Weg, ohne $ watch zu verwenden, mit angle.copy:

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

myApp.factory('Data', function(){

    var service = {
        FirstName: '',
        setFirstName: function(name) {
            // this is the trick to sync the data
            // so no need for a $watch function
            // call this from anywhere when you need to update FirstName
            angular.copy(name, service.FirstName); 
        }
    };
    return service;
});


// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

1

Es gibt mehrere Möglichkeiten, dies zu tun.

  1. Ereignisse - bereits gut erklärt.

  2. UI-Router - oben erklärt.

  3. Service - mit der oben angezeigten Aktualisierungsmethode
  4. SCHLECHT - Auf Veränderungen .
  5. Ein anderer Eltern-Kind-Ansatz, anstatt zu senden und zu senden -

* *

<superhero flight speed strength> Superman is here! </superhero>
<superhero speed> Flash is here! </superhero>

* *

app.directive('superhero', function(){
    return {
        restrict: 'E',
        scope:{}, // IMPORTANT - to make the scope isolated else we will pollute it in case of a multiple components.
        controller: function($scope){
            $scope.abilities = [];
            this.addStrength = function(){
                $scope.abilities.push("strength");
            }
            this.addSpeed = function(){
                $scope.abilities.push("speed");
            }
            this.addFlight = function(){
                $scope.abilities.push("flight");
            }
        },
        link: function(scope, element, attrs){
            element.addClass('button');
            element.on('mouseenter', function(){
               console.log(scope.abilities);
            })
        }
    }
});
app.directive('strength', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addStrength();
        }
    }
});
app.directive('speed', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addSpeed();
        }
    }
});
app.directive('flight', function(){
    return{
        require:'superhero',
        link: function(scope, element, attrs, superHeroCtrl){
            superHeroCtrl.addFlight();
        }
    }
});

0

Ich bin mir nicht sicher, wo ich dieses Muster aufgegriffen habe, aber um Daten zwischen Controllern auszutauschen und $ rootScope und $ scope zu reduzieren, funktioniert dies hervorragend. Es erinnert an eine Datenreplikation, bei der Sie Herausgeber und Abonnenten haben. Ich hoffe es hilft.

Der Service:

(function(app) {
    "use strict";
    app.factory("sharedDataEventHub", sharedDataEventHub);

    sharedDataEventHub.$inject = ["$rootScope"];

    function sharedDataEventHub($rootScope) {
        var DATA_CHANGE = "DATA_CHANGE_EVENT";
        var service = {
            changeData: changeData,
            onChangeData: onChangeData
        };
        return service;

        function changeData(obj) {
            $rootScope.$broadcast(DATA_CHANGE, obj);
        }

        function onChangeData($scope, handler) {
            $scope.$on(DATA_CHANGE, function(event, obj) {
                handler(obj);
            });
        }
    }
}(app));

Der Controller, der die neuen Daten erhält, also der Publisher, würde so etwas tun.

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

Der Controller, der auch diese neuen Daten verwendet, die als Abonnent bezeichnet werden, würde so etwas tun ...

sharedDataEventHub.onChangeData($scope, function(data) {
    vm.localData.Property1 = data.Property1;
    vm.localData.Property2 = data.Property2;
});

Dies funktioniert für jedes Szenario. Wenn der primäre Controller initialisiert wird und Daten erhält, ruft er die changeData-Methode auf, die diese dann an alle Abonnenten dieser Daten sendet. Dies reduziert die Kopplung unserer Steuerungen aneinander.


Die Verwendung eines 'Modells', das den Status zwischen den Controllern teilt, scheint die am häufigsten verwendete Option zu sein. Leider deckt dies nicht das Szenario ab, in dem Sie den untergeordneten Controller aktualisieren würden. Wie können Sie die Daten vom Server erneut abrufen und das Modell neu erstellen? Und wie würden Sie mit der Ungültigmachung des Caches umgehen?
Andrei

Ich denke, die Idee von übergeordneten und untergeordneten Controllern ist ein bisschen gefährlich und verursacht zu viele Abhängigkeiten voneinander. In Kombination mit UI-Router können Sie die Daten Ihres "Kindes" jedoch problemlos aktualisieren, insbesondere wenn diese Daten über die Statuskonfiguration eingespeist werden. Die Cache-Ungültigmachung erfolgt beim Herausgeber, wobei er für das Abrufen der Daten und das Aufrufen verantwortlich istsharedDataEventHub.changeData(newData)
ewahner

Ich denke, es gibt ein Missverständnis. Mit Aktualisieren meine ich, dass die Benutzer buchstäblich die F5-Taste auf der Tastatur drücken und den Browser veranlassen, einen Beitrag zurück zu schreiben. Wenn Sie sich in diesem Fall in der untergeordneten Ansicht / Detailansicht befinden, kann niemand "var someData = yourDataService.getSomeData ()" aufrufen. Die Frage ist, wie würden Sie mit diesem Szenario umgehen? Wer sollte das Modell aktualisieren?
Andrei

Je nachdem, wie Sie Ihre Controller aktivieren, kann dies sehr einfach sein. Wenn Sie eine Methode haben, die aktiviert wird, lädt der Controller, der für das erstmalige Laden der Daten verantwortlich ist, diese und verwendet sharedDataEventHub.changeData(newData)dann das untergeordnete Element oder den Controller, der von diesen Daten abhängt, irgendwo in seinem Controller-Körper: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });Wenn Sie die Benutzeroberfläche verwendet haben -Router Dies könnte aus der Statuskonfiguration eingefügt werden.
Ewahner

Hier ist ein Beispiel, das das Problem veranschaulicht , das ich beschreiben möchte : plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . Wenn Sie den Plunk herausspringen lassen, zur Bearbeitungsseite navigieren und den Browser aktualisieren, stürzt er ab. Wer sollte das Modell in diesem Fall neu erstellen?
Andrei

-3

Mach es einfach (getestet mit v1.3.15):

<article ng-controller="ctrl1 as c1">
    <label>Change name here:</label>
    <input ng-model="c1.sData.name" />
    <h1>Control 1: {{c1.sData.name}}, {{c1.sData.age}}</h1>
</article>
<article ng-controller="ctrl2 as c2">
    <label>Change age here:</label>
    <input ng-model="c2.sData.age" />
    <h1>Control 2: {{c2.sData.name}}, {{c2.sData.age}}</h1>
</article>

<script>
    var app = angular.module("MyApp", []);

    var dummy = {name: "Joe", age: 25};

    app.controller("ctrl1", function () {
        this.sData = dummy;
    });

    app.controller("ctrl2", function () {
        this.sData = dummy;
    });
</script>


5
Ich denke, dies wurde abgelehnt, weil es nicht modular ist. dummyist für beide Controller global und wird nicht modularer durch Vererbung mit $ scope oder controllerAs oder mithilfe eines Dienstes gemeinsam genutzt.
Chad Johnson
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.