Передача данных между контроллерами в Angular JS?


243

У меня есть основной контроллер, который отображает мои продукты,

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

На мой взгляд, я показываю эти продукты в списке

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

То, что я пытаюсь сделать, это когда кто-то нажимает на название продукта, у меня есть другое представление с именем корзина, в которую добавляется этот продукт.

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

Поэтому я сомневаюсь в том, как передать продукты, на которые нажали, с первого контроллера на второй? я предположил, что корзина должна быть контроллером тоже.

Я обрабатываю событие click, используя директиву. Также я чувствую, что должен использовать сервис для достижения вышеуказанной функциональности, просто не могу понять, как? потому что в корзине будет предопределенное количество добавленных товаров, может быть 5/10 в зависимости от того, на какой странице находится пользователь. Так что я хотел бы сохранить это общее.

Обновить:

Я создал сервис для трансляции и во втором контроллере я его получаю. Теперь вопрос, как я могу обновить дом? Поскольку мой список для отбрасывания продукта довольно жестко закодирован.


Ответы:


322

Из описания кажется, что вы должны использовать сервис. Посетите http://egghead.io/lessons/angularjs-sharing-data-between-controllers и AngularJS Service, передающий данные между контроллерами, чтобы увидеть некоторые примеры.

Вы можете определить свой продукт службы (как фабрика), как таковой:

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

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

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

  return {
    addProduct: addProduct,
    getProducts: getProducts
  };

});

Зависимость внедряет сервис в оба контроллера.

В вашем ProductController, определите некоторое действие, которое добавляет выбранный объект в массив:

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

В вашем CartControllerраспоряжении товары из сервиса:

app.controller('CartController', function($scope, productService) {
    $scope.products = productService.getProducts();
});

3
Контроллер корзины будет обновляться один раз, когда вызывается getProducts ()? Или каждый раз, когда добавляется новый продукт?
Кишанио

1
Если вы хотите, чтобы оно автоматически обновлялось, вы можете добавить трансляцию из ответа Максима Шустина и вызвать getProducts () в функции $ on, чтобы обновить область действия CartCtrl.
Chalise

2
@krillgar Вы совершенно правы, это было упущено изначально. Я отредактировал ответ, чтобы отразить рабочее решение.
Chalise

1
@DeveshM Да, делай, как ты предлагаешь. Возможно, вы захотите расширить методы, чтобы они не просто хранили данные в памяти и были более постоянными (например, сохранение на сервере через $httpвызов).
Chalise

1
Что произойдет, если у меня есть, например, 2 productControllers и 2 тележки? У обоих будет добавлен элемент? Как вы решаете это в этом случае?
atoth

67

как передать этот клик продукт с первого контроллера на второй?

По нажатию вы можете вызвать метод, который вызывает трансляцию :

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

и второй контроллер будет слушать этот тег как:

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

Так как мы не можем внедрить $ scope в сервисы, нет ничего лучше, чем singleton $ scope.

Но мы можем ввести $rootScope. Поэтому, если вы сохраняете значение в Сервисе, вы можете запустить его $rootScope.$broadcast('SOME_TAG', 'your value');в теле Сервиса. (См. @Charx описание услуг)

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

Пожалуйста, проверьте хорошую статью о трансляции $ , $ emit


1
Да это работает как шарм, я использую фабрику? Есть только одна последняя вещь, где я застрял - я получаю данные в новом контроллере каждый раз, когда нажимаю на продукт. Теперь, как мне обновить его в DOM? Поскольку у меня уже есть, скажем, список из 5 жестко закодированных границ, поэтому каждый продукт должен идти внутри них
Кишанио

1
@KT - Ты когда-нибудь получал ответ? Это кажется очевидным вопросом, но я нигде не могу найти ответ. Я хочу, чтобы контроллер изменил какое-то значение. Когда значение этого приложения изменяется, тогда любые другие прослушивающие контроллеры должны обновлять себя по мере необходимости. Не могу найти это.
Раддевус

2
@ Daylight ответ на свой вопрос. зависит от того, какая у вас структура контроллера: параллельная или дочерняя. $rootScope.$broadcastуведомляет все контроллеры, которые имеют параллельную структуру, то есть того же уровня.
Максим Шустин

@MS Спасибо за ответ. Есть ли способ сделать это с помощью $ watch? Кстати, у меня параллельные контроллеры, и я заставил их работать, используя ваш метод. Для меня всякий раз, когда я пытаюсь добавить $ watch (даже в $ rootScope), метод, связанный с $ watch во 2-м контроллере, будет запускаться только в первый раз (инициализация 2-го контроллера). Спасибо.
Раддевус

1
мы можем использовать, $watchесли значение существует на $rootScopeуровне. В противном случае только трансляция может уведомить другие контроллеры. К вашему сведению, если другой программист видит в вашем коде broadcast- логика ясна, что вы пытаетесь сделать - «широковещательное событие». В любом случае, из моего опыта. его не рекомендуется использовать rootscopeдля watch. О «только 1-й раз»: посмотрите на этот пример: plnkr.co/edit/5zqkj7iYloeHYkzK1Prt?p=preview у вас есть старые и новые значения. будьте уверены, что каждый раз, когда вы получаете новое значение, которое не совпадает со старым
Максим Шустин

24

Решение без создания Сервиса с использованием $ rootScope:

Для совместного использования свойств через контроллеры приложения вы можете использовать Angular $ rootScope. Это еще один вариант обмена данными, чтобы люди знали об этом.

Предпочтительный способ обмена некоторыми функциями между контроллерами - это службы, для чтения или изменения глобального свойства вы можете использовать $ rootcope.

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

Использование $ rootScope в шаблоне (доступ к свойствам с помощью $ root):

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

27
$rootScopeследует избегать как можно больше.
Shaz

1
@ Шаз, не могли бы вы объяснить, почему?
Мирко

5
@Mirko Вы должны стараться избегать глобального состояния настолько, насколько это возможно, потому что любой может изменить его, делая состояние вашей программы непредсказуемым.
Умут семь

4
$ rootScope имеет глобальную область видимости, поэтому, как и собственные глобальные переменные, мы должны использовать их осторожно. Если какой-либо контроллер изменит свои значения, он будет изменен для глобальной области видимости.
Санджив

1
Службы и фабрики - это Singleton, но вы можете выбрать, добавлять или нет какие-либо услуги в вашем контроллере. Но все дочерние области происходят из корневого пространства и следуют прототипной цепочке. Поэтому, если вы попытаетесь получить доступ к свойству в вашей области, а оно отсутствует, то оно будет искать цепочку. Существует вероятность того, что вы нежелательно можете изменить свойство rootcope.
Санджив

16

Вы можете сделать это двумя способами.

  1. Используя $rootscope, но я не рекомендую это. Это $rootScopeсамая верхняя область применения. В приложении может быть только одно приложение, $rootScopeкоторое будет доступно всем компонентам приложения. Следовательно, он действует как глобальная переменная.

  2. Использование услуг. Вы можете сделать это, разделив сервис между двумя контроллерами. Код для сервиса может выглядеть так:

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

    Вы можете увидеть мою скрипку здесь .


и третий вариант, вы можете отправить событие, просто чтобы добавить в список
DanteTheSmith

Что если пользователь обновит страницу? ТОГДА getProducts () НИЧЕГО НЕ ДАЕТ ВАМ! (Вы не можете сказать всем пользователям не обновлять, не так ли?)
shreedhar bhat

2
@shreedharbhat Вопрос не в том, чтобы сохранить данные при перезагрузке страницы.
Джек Виал

9

Еще более простой способ обмена данными между контроллерами - использование вложенных структур данных. Вместо, например

$scope.customer = {};

мы можем использовать

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

dataИмущество будет наследоваться от родительской области , поэтому мы можем переписать свои поля, сохраняя доступ из других контроллеров.


1
Контроллеры наследуют свойства области от родительских Контроллеров к своей собственной области. Чистота! простота делает его красивым
Карлос Р. Балебона

не могу заставить его работать. на втором контроллере я использую $ scope.data, и он не определен.
Барт Каликсто

Я думаю, он говорит о вложенных контроллерах. Не слишком много для меня тоже.
Норберт Норбертсон

8
angular.module('testAppControllers', [])
    .controller('ctrlOne', function ($scope) {
        $scope.$broadcast('test');
    })
    .controller('ctrlTwo', function ($scope) {
        $scope.$on('test', function() {
        });
    });

он использует
Niether

5

мы можем хранить данные в сеансе и использовать их в любом месте нашей программы.

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

Другое место

$scope.data = $window.sessionStorage.getItem("Mydata");

4

Я видел ответы здесь, и он отвечает на вопрос об обмене данными между контроллерами, но что мне делать, если я хочу, чтобы один контроллер уведомил другой о том, что данные были изменены (без использования широковещательной передачи)? ЛЕГКО! Просто используя известный шаблон посетителей:

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

Таким простым способом один контроллер может обновить другой контроллер, если некоторые данные были обновлены.


2
Разве шаблон наблюдателя ( en.wikipedia.org/wiki/Observer_pattern ) не будет более уместным здесь? Шаблон посетителей - это нечто другое ... en.wikipedia.org/wiki/Visitor_pattern
Муравей Кучера

3

Я создал фабрику, которая управляет общей областью действия между шаблоном пути маршрута, поэтому вы можете поддерживать общие данные только тогда, когда пользователи переходят по одному и тому же родительскому пути маршрута.

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

Таким образом, если пользователь перейдет к другому пути маршрута, например, «/ Поддержка», общие данные для пути «/ Клиент» будут автоматически уничтожены. Но если вместо этого пользователь перейдет к «дочерним» путям, таким как «/ Customer / 1» или «/ Customer / list», область действия не будет уничтожена.

Вы можете увидеть образец здесь: http://plnkr.co/edit/OL8of9


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

При нажатии вы можете вызвать метод, который вызывает трансляцию:

$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

Один из способов использования углового сервиса:

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

2
добавьте еще несколько объяснений в ваш ответ.
Герхард Барнард

2

Создайте фабрику в вашем модуле и добавьте ссылку на фабрику в контроллере и используйте ее переменные в контроллере, а теперь получите значение данных в другом контроллере, добавив ссылку туда, где вы хотите


2

Я не знаю, поможет ли это кому-нибудь, но, основываясь на ответе Charx (спасибо!), Я создал простой сервис кеширования. Не стесняйтесь использовать, делать ремиксы и делиться:

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

2

Один из способов использования углового сервиса:

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/


1

К сведению, объект $ scope имеет $ emit, $ broadcast, $ on И объект $ rootScope имеет идентичный $ emit, $ broadcast, $ on

Подробнее о шаблоне публикации / подписки на английском языке здесь


1

Чтобы улучшить решение, предложенное @Maxim с использованием $ broadcast, отправляйте данные без изменений

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

но для прослушивания данных

$scope.$on('SOME_TAG', function(event, args) {
    console.log("My variable is", args);// args is value of your variable
})

1

Есть три способа сделать это,

а) используя сервис

б) Использование зависимых родительских / дочерних отношений между областями контроллера.

в) В Angular 2.0 ключевое слово «как» будет передавать данные с одного контроллера на другой.

Для получения дополнительной информации с примером, пожалуйста, проверьте ссылку ниже:

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


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

Первый контроллер

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

Второй контроллер

 <div ng-controller="SecondController as vm">
    Second Controller<br>
    {{vm.data.value}}
</div>

-1

я думаю

лучший способ

это использовать $ 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
});

Вы также можете добавить

if($localStorage.selectedObj){
    $scope.selectedProducts = $localStorage.selectedObj;
}else{
    //redirect to select product using $location.url('/select-product')
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.