Udostępniaj dane między kontrolerami AngularJS

362

Próbuję udostępnić dane między administratorami. Przypadek użycia jest formularzem wieloetapowym, dane wprowadzone na jednym wejściu są później wykorzystywane w wielu lokalizacjach wyświetlania poza pierwotnym sterownikiem. Kod poniżej oraz w jsfiddle tutaj .

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

Każda pomoc jest mile widziana.

johnkeese
źródło

Odpowiedzi:

481

Prostym rozwiązaniem jest zwrócenie obiektu do fabryki i umożliwienie kontrolerom pracy w odniesieniu do tego samego obiektu:

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/

Gdy aplikacje stają się większe, bardziej złożone i trudniejsze do przetestowania, możesz nie chcieć w ten sposób eksponować całego obiektu z fabryki, ale zamiast tego dać ograniczony dostęp, na przykład za pośrednictwem programów pobierających i ustawiających:

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

    var data = {
        FirstName: ''
    };

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

Przy takim podejściu do kontrolerów korzystających z oprogramowania należy aktualizowanie fabryki o nowe wartości i sprawdzanie zmian w celu ich uzyskania:

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/

tasseKATT
źródło
2
Pierwsze rozwiązanie działało dla mnie najlepiej - usługa utrzymywała obiekt, a kontrolery obsługiwały tylko referencję. Wielkie dzięki.
johnkeese
5
Metoda $ scope. $ watch działa pięknie w celu wykonania wywołania reszty z jednego zakresu i zastosowania wyników do innego zakresu
aclave1
Zamiast zwracać obiekt podobny return { FirstName: '' };lub zwracający obiekt zawierający metody, które wchodzą w interakcje z obiektem, czy nie byłoby wskazane zwracanie instancji klasy ( function), aby podczas używania obiektu miał on określony typ i był nie tylko obiekt{}" ?
RPDeshaies,
8
Tylko jedna głowa, funkcja nasłuchiwania nie wymaga newValue !== oldValuesprawdzania, ponieważ Angular nie wykonuje tej funkcji, jeśli oldValue i newValue są takie same. Jedynym wyjątkiem jest sytuacja, gdy zależy Ci na wartości początkowej. Patrz: docs.quarejs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.
@tasseKATT, Działa dobrze, jeśli nie odświeżę strony. Czy możesz mi zaproponować jakieś rozwiązanie, jeśli odświeżę stronę?
Maj
71

Wolę nie używać $watch . Zamiast przypisywać całą usługę do zakresu kontrolera, możesz przypisać tylko dane.

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>

Alternatywnie możesz zaktualizować dane usługi metodą bezpośrednią.

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);
   }
});
bennick
źródło
Nie używasz jawnie watch (), ale wewnętrznie AngularJS robi aktualizację widoku o nowe wartości w zmiennych $ scope. Pod względem wydajności jest to to samo, czy nie?
Mart Dominguez
4
Nie jestem pewien wydajności żadnej z metod. Z tego, co rozumiem, Angular już uruchamia pętlę podsumowania zakresu, więc konfigurowanie dodatkowych wyrażeń obserwacyjnych prawdopodobnie dodaje więcej pracy dla Angulara. Musisz naprawdę przeprowadzić test wydajności, aby naprawdę się dowiedzieć. Po prostu wolę transfer danych i udostępnianie za pomocą powyższej odpowiedzi niż aktualizację za pomocą wyrażeń $ watch.
bennick
2
To nie jest alternatywa dla $ watch. Jest to inny sposób udostępniania danych między dwoma obiektami Angular, w tym przypadku kontrolerami, bez konieczności używania $ watch.
bennick
1
IMO to rozwiązanie jest najlepsze, ponieważ jest bardziej zgodne z deklaratywnym podejściem Angulara, w przeciwieństwie do dodawania instrukcji $ watch, co wydaje się nieco bardziej jQuery.
JamesG
8
Zastanawiam się, dlaczego nie jest to najczęściej głosowana odpowiedź. W końcu $ watch sprawia, że ​​kod jest bardziej złożony
Shyamal Parikh
11

Istnieje wiele sposobów udostępniania danych między kontrolerami

  1. korzystanie z usług
  2. korzystając z usług $ state.go
  3. przy użyciu stateparams
  4. za pomocą rootscope

Objaśnienie każdej metody:

  1. Nie zamierzam wyjaśniać, ponieważ ktoś już to wyjaśnił

  2. za pomocą $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
  3. $stateparamdziała w podobny sposób $state.go, przekazujesz go jako obiekt z kontrolera nadawcy i zbierasz w kontrolerze odbiornika za pomocą stateparam

  4. za pomocą $rootscope

    (a) wysyłanie danych od dziecka do nadrzędnego kontrolera

      $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) wysyłanie danych od nadrzędnego do kontrolera podrzędnego

      $scope.SaveDB(Obj,function(data){
          $scope.$broadcast('savedata',data);
      });
    
      $scope.SaveDB(Obj,function(data){`enter code here`
          $rootScope.$broadcast('saveCallback',data);
      });
Ayush Mishra
źródło
6

Stworzyłem fabrykę, która kontroluje współużytkowany zakres między wzorcem ścieżki trasy, abyś mógł utrzymywać udostępnione dane tylko wtedy, gdy użytkownicy nawigują tą samą ścieżką nadrzędną trasy.

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

Jeśli więc użytkownik przejdzie do innej ścieżki trasy, na przykład „/ Wsparcie”, udostępnione dane dla ścieżki „/ Klient” zostaną automatycznie zniszczone. Ale jeśli zamiast tego użytkownik przejdzie do ścieżek „podrzędnych”, takich jak „/ Customer / 1” lub „/ Customer / list”, zakres nie zostanie zniszczony.

Możesz zobaczyć próbkę tutaj: http://plnkr.co/edit/OL8of9

Oberdan Nunes
źródło
Użyj, $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })jeśli masz ui.router
Nuno Silva
6

Istnieje wiele sposobów udostępniania danych między kontrolerami

  • Usługi kątowe
  • Metoda $ broadcast, $ emit
  • Komunikacja rodzic-dziecko kontrolera
  • $ rootscope

Jak wiemy $rootscope nie jest to preferowany sposób przesyłania danych lub komunikacji, ponieważ jest to globalny zakres, który jest dostępny dla całej aplikacji

W zakresie udostępniania danych między kontrolerami Angular Js najlepsze są np. Usługi Angular. .factory, .service
W celach informacyjnych

W przypadku transferu danych z rodzica na dziecko można kontrolera dostępu do danych bezpośrednio dominującym w kontrolerze dzieci poprzez $scope
Jeśli używasz ui-routernastępnie można użyć $stateParmasdo przekazywania parametrów URL jak id, name, keyitp

$broadcastjest również dobrym sposobem na przesyłanie danych między kontrolerami z nadrzędnego na podrzędny i $emittransfer danych z podrzędnych do nadrzędnych

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

Zapoznaj się z podanym linkiem, aby dowiedzieć się więcej$broadcast

ojus kulkarni
źródło
4

Najprostsze rozwiązanie:

Korzystałem z usługi AngularJS .

Krok 1: Utworzyłem usługę AngularJS o nazwie SharedDataService.

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

    };
    return Person;
});

Krok 2: Utwórz dwa kontrolery i użyj wyżej utworzonej usługi.

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

Krok 3: Po prostu użyj utworzonych kontrolerów w widoku.

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

Aby zobaczyć działające rozwiązanie tego problemu, kliknij poniższy link

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

plik .html:

<!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>
wygrywa999
źródło
1
jest to bardzo wyraźny przykład dla nas, nowych ludzi korzystających z angularJs. W jaki sposób udostępniasz / przekazujesz dane z relacji rodzic / dziecko? FirstCtrljest rodzicem i dostaje obietnicę, której SecondCtrl(dziecko) będzie potrzebować? Nie chcę wykonywać dwóch połączeń API. Dzięki.
Chris22
1

Istnieje inny sposób bez użycia $ watch, używając angular.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;
});
Hinrich
źródło
1

Można to zrobić na wiele sposobów.

  1. Wydarzenia - już dobrze wyjaśnione.

  2. router interfejsu użytkownika - wyjaśniono powyżej.

  3. Serwis - z powyższą metodą aktualizacji
  4. ZŁY - oczekiwanie na zmiany.
  5. Inne podejście dziecka nadrzędnego zamiast emitować i nadawać -

*

<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();
        }
    }
});
użytkownik2756335
źródło
0

Nie wiem, gdzie wybrałem ten wzorzec, ale działa to wspaniale w przypadku udostępniania danych między kontrolerami i zmniejszenia $ rootScope i $ scope. Przypomina replikację danych, w której są wydawcy i subskrybenci. Mam nadzieję, że to pomoże.

Obsługa:

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

Administrator, który pobiera nowe dane, czyli Wydawca, zrobiłby coś takiego.

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

Administrator, który również korzysta z tych nowych danych, zwany subskrybentem, zrobiłby coś takiego ...

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

Będzie to działać w każdym scenariuszu. Więc kiedy główny kontroler zostanie zainicjowany i pobierze dane, wywołałby metodę changeData, która następnie rozesłałaby to do wszystkich subskrybentów tych danych. Zmniejsza to sprzężenie naszych sterowników ze sobą.

Ewahner
źródło
użycie „modelu”, który dzieli stan między kontrolerami, wydaje się być najczęściej stosowaną opcją. Niestety nie obejmuje to scenariusza odświeżenia kontrolera podrzędnego. Jak „ponownie” pobrać dane z serwera i odtworzyć model? A jak poradzisz sobie z unieważnieniem pamięci podręcznej?
Andrei
Myślę, że idea nadrzędnych i podrzędnych kontrolerów jest nieco niebezpieczna i powoduje zbyt wiele zależności od siebie. Jednak w połączeniu z routerem interfejsu użytkownika można łatwo odświeżyć dane na swoim „dziecku”, zwłaszcza jeśli dane te są wstrzykiwane przez konfigurację stanu. sharedDataEventHub.changeData(newData)
Unieważnienie
Myślę, że istnieje nieporozumienie. Przez odświeżenie rozumiem, że użytkownicy dosłownie naciskają klawisz F5 na klawiaturze i powodują, że przeglądarka wysyła post z powrotem. Jeśli to nastąpi, w widoku potomnym / szczegółów nie ma nikogo, kto mógłby wywołać „var someData = yourDataService.getSomeData ()”. Pytanie brzmi: jak poradziłbyś sobie z tym scenariuszem? kto powinien zaktualizować model?
Andrei
W zależności od sposobu aktywacji kontrolerów może to być bardzo proste. Jeśli masz metodę, która się aktywuje, wówczas kontroler odpowiedzialny za początkowe ładowanie danych załaduje ją, a następnie użyje sharedDataEventHub.changeData(newData)dowolnego elementu potomnego lub kontrolera zależnego od tych danych, który miałby gdzieś w ciele kontrolera: sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });Jeśli użyjesz interfejsu użytkownika -Router może zostać wstrzyknięty z konfiguracji stanu.
ewahner
Oto przykład ilustrujący problem, który próbuję opisać: plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview . Jeśli wyskoczysz z listy, przejdź do strony edycji i odśwież przeglądarkę, nastąpi awaria. Kto powinien odtworzyć model w tym przypadku?
Andrei
-3

Po prostu zrób to prosto (testowane w wersji 1.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>

Lumic
źródło
5
Myślę, że to zostało odrzucone, ponieważ nie jest modułowe. dummyjest globalny dla obu kontrolerów, a nie współdzielony w bardziej modułowy sposób poprzez dziedziczenie z $ scope lub controllerAs lub za pomocą usługi.
Chad Johnson