Przekazywanie danych między kontrolerami w Angular JS?

243

Mam podstawowy kontroler, który wyświetla moje produkty,

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

Moim zdaniem wyświetlam te produkty na liście

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

Próbuję zrobić, gdy ktoś kliknie nazwę produktu, mam inny widok o nazwie koszyk, w którym ten produkt został dodany.

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

Więc mam wątpliwości, w jaki sposób przenieść te kliknięte produkty z pierwszego kontrolera na drugi? założyłem, że wózek powinien być również kontrolerem.

Obsługuję zdarzenie click za pomocą dyrektywy. Czuję też, że powinienem korzystać z usługi, aby osiągnąć powyższą funkcjonalność, po prostu nie mogę zrozumieć, w jaki sposób? ponieważ koszyk zostanie wstępnie zdefiniowany, liczba dodanych produktów może wynosić 5/10, w zależności od użytkownika strony. Więc chciałbym zachować to ogólne.

Aktualizacja:

Stworzyłem usługę do emisji i na drugim kontrolerze ją odbieram. Teraz pytanie brzmi: jak zaktualizować dom? Ponieważ moja lista upuszczania produktu jest dość zakodowana.

kishanio
źródło

Odpowiedzi:

322

Z opisu wydaje się, że powinieneś korzystać z usługi. Sprawdź http://egghead.io/lessons/angularjs-sharing-data-between-controllers i AngularJS Service przekazywanie danych między kontrolerami, aby zobaczyć kilka przykładów.

Możesz zdefiniować swoją usługę produktu (jako fabrykę) jako taką:

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

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

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

  return {
    addProduct: addProduct,
    getProducts: getProducts
  };

});

Zależność wstrzykuje usługę do obu kontrolerów.

W swoim ProductControllerzdefiniuj akcję, która doda wybrany obiekt do tablicy:

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

W swoim CartController, uzyskaj produkty z usługi:

app.controller('CartController', function($scope, productService) {
    $scope.products = productService.getProducts();
});
Chalise
źródło
3
Kontroler koszyka zaktualizuje się raz, gdy zostanie wywołane getProducts ()? Lub za każdym razem, gdy dodawany jest nowy produkt?
kishanio
1
Jeśli chcesz, aby aktualizowała się automatycznie, możesz dodać transmisję z odpowiedzi Maxima Shoustina i wywołać funkcję getProducts () w funkcji $ on, aby zaktualizować zakres CartCtrl.
Chalise
2
@krillgar Masz całkowitą rację, która początkowo została przeoczona. Zredagowałem odpowiedź, aby odzwierciedlić działające rozwiązanie.
Chalise
1
@DeveshM Tak, rób, co sugerujesz. Możesz rozważyć rozszerzenie metod, aby nie tylko przechowywały dane w pamięci i były bardziej trwałe ( $httpna przykład zapisywanie na serwerze przez połączenie).
Chalise
1
Co się stanie, jeśli mam na przykład 2 kontrolery produktu i 2 wózki? Oba będą miały dodany element? Jak to rozwiązać w tym przypadku?
atoth
67

jak przekazać te kliknięte produkty z pierwszego kontrolera na drugi?

Po kliknięciu możesz wywołać metodę wywołującą rozgłaszanie :

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

a drugi kontroler będzie nasłuchiwał na tym tagu:

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

Ponieważ nie możemy wstrzyknąć $ scope do usług, nie ma to jak singleton $ scope.

Ale możemy wstrzyknąć $rootScope. Jeśli więc przechowujesz wartość w usłudze, możesz uruchomić $rootScope.$broadcast('SOME_TAG', 'your value');w treści usługi. (Zobacz opis @Charx na temat usług)

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

Sprawdź dobry artykuł o $ broadcast , $ emit

Maxim Shoustin
źródło
1
Tak, to działa jak urok używam fabryki? Jest tylko jedna ostatnia rzecz, w której utknąłem: za każdym razem, gdy klikam produkt, dostaję dane do nowego kontrolera. Teraz jak zaktualizować go w DOM? Ponieważ mam już pozwala lista powiedzmy z 5 sztywno z granicami więc każdy produkty muszą przejść wewnątrz nich
kishanio
1
@KT - Czy kiedykolwiek dostałeś odpowiedź? To wydaje się oczywiste pytanie, ale nigdzie nie mogę znaleźć odpowiedzi. Chcę, aby kontroler zmienił jakąś wartość. Gdy wartość tej aplikacji zmienia się, wówczas każdy inny kontroler nasłuchujący powinien zaktualizować się w razie potrzeby. Nie mogę tego znaleźć
raddevus
2
@odświetl odpowiedź na q. jest oparty na posiadanej strukturze kontrolera: równoległy lub podrzędny-nadrzędny. $rootScope.$broadcastpowiadamia wszystkie kontrolery, które mają równoległą strukturę aka tego samego poziomu.
Maxim Shoustin
@MS Dzięki za odpowiedź. Czy można to zrobić za pomocą $ watch? BTW, moje są równoległymi kontrolerami i mam pracować z twoją metodą. Dla mnie, ilekroć próbuję dodać $ watch (nawet do $ rootScope), metoda powiązana z $ watch w 2. kontrolerze uruchamia się tylko za pierwszym razem (inicjalizacja 2. kontrolera). Dzięki.
raddevus
1
możemy użyć, $watchjeśli wartość istnieje na $rootScopepoziomie. W przeciwnym razie tylko transmisja może powiadomić inne kontrolery. Do twojej wiadomości, jeśli inny programista widzi w twoim kodzie broadcast- logika jest jasna, co próbujesz zrobić - „zdarzenie broadcast”. W każdym razie z mojego doświadczenia. to nie jest dobra praktyka, aby użyć rootscopedo watch. O „strzelaj tylko raz”: spójrz na ten przykład: plnkr.co/edit/5zqkj7iYloeHYkzK1Prt?p=preview masz stare i nowe wartości. upewnij się, że za każdym razem, gdy dostajesz nową wartość, która nie jest równa starej
Maxim Shoustin
24

Rozwiązanie bez tworzenia usługi przy użyciu $ rootScope:

Aby udostępnić właściwości między kontrolerami aplikacji, możesz użyć Angular $ rootScope. To kolejna opcja udostępniania danych, dzięki czemu ludzie o nich wiedzą.

Preferowanym sposobem udostępniania niektórych funkcji między kontrolerami są Usługi, do odczytu lub zmiany globalnej właściwości, której można użyć $ 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;
}]);

Używanie $ rootScope w szablonie (Dostęp do właściwości za pomocą $ root):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>
Sanjeev
źródło
27
$rootScopenależy unikać w jak największym stopniu.
Shaz
1
@Shaz mógłbyś wyjaśnić, dlaczego?
Mirko
5
@Mirko Powinieneś starać się unikać stanu globalnego tak bardzo, jak to możliwe, ponieważ każdy może to zmienić - czyniąc stan programu nieprzewidywalnym.
Umut Seven
4
$ rootScope ma zasięg globalny, więc podobnie jak natywne zmienne globalne musimy z nich korzystać ostrożnie. Jeśli jakikolwiek kontroler zmieni swoje wartości, zostanie zmieniony dla zasięgu globalnego.
Sanjeev,
1
Zarówno usługi, jak i fabryki są Singleton, ale możesz zdecydować się na wstrzyknięcie dowolnej usługi w kontrolerze. Ale cały zakres potomny wywodzi się z rootscope i postępuje zgodnie z łańcuchem prototypowym. Więc jeśli spróbujesz uzyskać dostęp do właściwości w twoim zakresie i jej nie ma, wówczas przejrzy łańcuch. Istnieje możliwość niepożądanej zmiany właściwości rootscope.
Sanjeev
16

Możesz to zrobić na dwa sposoby.

  1. Używam $rootscope, ale nie polecam tego. Jest $rootScopeto najwyższy zakres. Aplikacja może mieć tylko jedną, $rootScopektóra będzie współużytkowana przez wszystkie składniki aplikacji. Dlatego działa jak zmienna globalna.

  2. Korzystanie z usług. Możesz to zrobić, udostępniając usługę między dwoma kontrolerami. Kod usługi może wyglądać następująco:

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

    Można zobaczyć moje skrzypce tutaj .

Nisham Mahsin
źródło
i trzecią opcję, możesz wysłać wydarzenie, aby dodać je do listy
DanteTheSmith
CO JEŚLI UŻYTKOWNIK ODŚWIEŻY STRONĘ? NASTĘPNIE getProducts () NIE DAJE CI NIC! (Nie możesz powiedzieć wszystkim użytkownikom, żeby nie odświeżali, prawda?)
shreedhar bhat
2
@shreedharbhat Pytanie nie dotyczy utrzymywania danych w trakcie przeładowywania stron.
Jack Vial
9

Jeszcze prostszym sposobem udostępniania danych między kontrolerami jest użycie zagnieżdżonych struktur danych. Zamiast na przykład

$scope.customer = {};

możemy użyć

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

dataNieruchomość będą dziedziczone z zakresu dominującej więc możemy nadpisać swoje pola, zachowując dostęp z innych kontrolerów.

Bartłomiej Zalewski
źródło
1
Kontrolery dziedziczą właściwości zakresu od kontrolerów nadrzędnych do własnego zakresu. Czysty! prostota sprawia, że ​​jest
piękna
nie mogę tego zrobić. na drugim kontrolerze używam $ scope.data i jest niezdefiniowany.
Bart Calixto,
Myślę, że mówi o kontrolerach zagnieżdżonych. Niewiele też mi się przyda.
Norbert Norbertson,
8
angular.module('testAppControllers', [])
    .controller('ctrlOne', function ($scope) {
        $scope.$broadcast('test');
    })
    .controller('ctrlTwo', function ($scope) {
        $scope.$on('test', function() {
        });
    });
Jijo Paulose
źródło
używa niether
Leathan
5

możemy przechowywać dane w sesji i możemy ich używać w dowolnym miejscu w naszym programie.

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

Inne miejsce

$scope.data = $window.sessionStorage.getItem("Mydata");
Alex Kumbhani
źródło
4

Widziałem tutaj odpowiedzi i to odpowiada na pytanie o współdzielenie danych między administratorami, ale co powinienem zrobić, jeśli chcę, aby jeden administrator powiadomił drugi o fakcie, że dane zostały zmienione (bez użycia rozgłaszania)? ŁATWO! Wystarczy użyć słynnego wzoru dla odwiedzających:

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

W ten prosty sposób jeden kontroler może zaktualizować inny kontroler, że niektóre dane zostały zaktualizowane.

Omri Lahav
źródło
2
Czy wzorzec obserwatora ( en.wikipedia.org/wiki/Observer_pattern ) nie byłby bardziej odpowiedni tutaj? Wzorzec odwiedzających to coś innego ... en.wikipedia.org/wiki/Visitor_pattern
Ant Kutschera
3

Stworzyłem fabrykę, która kontroluje współużytkowany zakres między wzorcem ścieżki trasy, abyś mógł utrzymywać wspólne 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
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)

Po kliknięciu możesz wywołać metodę wywołującą rozgłaszanie:

$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

Jednym ze sposobów korzystania z usługi kątowej:

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: ''};
});
Subhransu
źródło
2
dodaj trochę wyjaśnienia do swojej odpowiedzi.
Gerhard Barnard
2

Stwórz fabrykę w swoim module i dodaj referencję do fabryki w kontrolerze i użyj jej zmiennych w kontrolerze, a teraz uzyskaj wartość danych w innym kontrolerze, dodając referencję tam, gdzie chcesz

Resham Kadel
źródło
2

Nie wiem, czy to komukolwiek pomoże, ale na podstawie odpowiedzi Charxa (dzięki!) Stworzyłem prostą usługę pamięci podręcznej. Zachęcamy do korzystania, remiksowania i udostępniania:

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
    };
});
marverix
źródło
2

Jednym ze sposobów korzystania z usługi kątowej:

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/

Siva ganesh
źródło
1

Do Twojej wiadomości Obiekt $ scope ma $ emit, $ broadcast, $ on ORAZ $ rootScope Object ma identyczny $ emit, $ broadcast, $ on

czytaj więcej o publikowaniu / subskrybowaniu wzorca projektowego w kanciastych tutaj

Anja Ishmukhametova
źródło
1

Aby ulepszyć rozwiązanie zaproponowane przez @Maxim za pomocą $ broadcast, wysyłanie danych się nie zmienia

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

ale do nasłuchiwania danych

$scope.$on('SOME_TAG', function(event, args) {
    console.log("My variable is", args);// args is value of your variable
})
Cedriga
źródło
1

Istnieją trzy sposoby, aby to zrobić,

a) korzystanie z usługi

b) Wykorzystywanie zależności relacji rodzic / dziecko między zakresami kontrolera.

c) W Angular 2.0 słowo kluczowe „As” będzie przekazywać dane z jednego kontrolera do drugiego.

Aby uzyskać więcej informacji z przykładem, sprawdź poniższy link:

http://www.tutespace.com/2016/03/angular-js.html

Shrinivas Kalangutkar
źródło
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'
}
}

Pierwszy kontroler

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

Drugi kontroler

 <div ng-controller="SecondController as vm">
    Second Controller<br>
    {{vm.data.value}}
</div>
testmeon
źródło
-1

Myślę że

Najlepszym sposobem

jest użycie $ localStorage. (Działa cały czas)

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

Twoja kartaController będzie

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

Możesz także dodać

if($localStorage.selectedObj){
    $scope.selectedProducts = $localStorage.selectedObj;
}else{
    //redirect to select product using $location.url('/select-product')
}
shreedhar bhat
źródło