Обмен данными между контроллерами AngularJS

308

Я пытаюсь обмениваться данными между контроллерами. Вариант использования - многоступенчатая форма, данные, введенные на одном входе, позже используются в нескольких местах отображения вне исходного контроллера. Код ниже и в jsfiddle здесь.

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

Любая помощь очень ценится.

Теги:

9 ответов

453
Лучший ответ

Простое решение - вернуть ваш factory объект и позволить вашим контроллерам работать со ссылкой на тот же объект:

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>

Демо: http://jsfiddle.net/HEdJF/

Когда приложения становятся больше, сложнее и сложнее тестировать, вы, возможно, не захотите выставлять весь объект из factory таким образом, но вместо этого предоставляете ограниченный доступ, например, через getters и seters:

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

    var data = {
        FirstName: ''
    };

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

При таком подходе контроллеры-потребители должны обновлять factory новыми значениями и следить за изменениями для их получения:

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>

Демо: http://jsfiddle.net/27mk1n1o/

  • 2
    Первое решение работало лучше для меня - если бы служба поддерживала объект, а контроллеры просто обрабатывали ссылку. Большое спасибо.
  • 5
    Метод $ scope. $ watch прекрасно работает для вызова остальных из одной области и применения результатов к другой области
Показать ещё 11 комментариев
58

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

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>

В качестве альтернативы вы можете обновить служебные данные прямым методом.

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 update method
myApp.controller('SecondCtrl', function($scope, MyService){
   $scope.data = MyService.data;

   $scope.updateData = function(first, last) {
     MyService.update(first, last);
   }
});
  • 0
    Вы не используете явно watch (), но внутренне AngularJS делает для обновления представления новыми значениями в переменных $ scope. С точки зрения производительности это то же самое или нет?
  • 4
    Я не уверен в эффективности любого из этих методов. Насколько я понимаю, Angular уже выполняет дайджест-цикл в области видимости, поэтому настройка дополнительных выражений наблюдения, вероятно, добавляет больше работы для Angular. Вы должны запустить тест производительности, чтобы действительно выяснить это. Я просто предпочитаю передачу и обмен данными через ответ выше, чем обновление через выражения $ watch.
Показать ещё 8 комментариев
9

Существует множество способов совместного использования данных между контроллерами

  • Использование служб
  • используя службы $state.go
  • с использованием stateparams
  • Использование корнеплодов

Объяснение каждого метода:

  • Я не буду объяснять, как его уже объяснил кто-то

  • с помощью $state.go

      $state.go('book.name', {Name: 'XYZ'}); 
    
      // then get parameter out of URL
      $state.params.Name;
    
  • $stateparam работает аналогично $state.go, вы передаете его как объект из контроллера отправителя и собираете в контроллере получателя с помощью stateparam

  • с помощью $rootscope

    (a) отправка данных с дочернего на родительский контроллер

      $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) отправка данных из родительского контроллера в дочерний контроллер

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

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

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

Итак, если пользователь переходит на другой маршрут маршрута, например '/Support', общие данные для пути '/Customer' будут автоматически уничтожены. Но если вместо этого пользователь переходит к "дочерним" путям, например "/Клиент/1" или "Клиент/список", область не будет уничтожена.

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

  • 0
    Используйте $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... }) если у вас есть ui.router
2

Существует несколько способов обмена данными между контроллерами

  • Angular услуги
  • $broadcast, метод $emit
  • Передача родительского контроллера.
  • $rootscope

Как известно, $rootscope не является предпочтительным способом передачи данных или связи, поскольку это глобальная область, доступная для всего приложения

Для обмена данными между Angular Js-контроллерами Angular являются лучшими практиками, например. .factory, .service
Для справки

В случае передачи данных от родителя к дочернему контроллеру вы можете напрямую обращаться к родительским данным в дочернем контроллере через $scope
Если вы используете ui-router, вы можете использовать $stateParmas для передачи параметров URL, таких как id, name, key и т.д.

$broadcast также является хорошим способом передачи данных между контроллерами от родителя к дочерней и $emit для передачи данных от дочерних к родительским контроллерам.

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

Обратитесь к ссылке, чтобы узнать больше о $broadcast

1

Существует несколько способов сделать это.

  • События - уже хорошо объяснены.

  • ui router - объяснено выше.

  • Сервис - с приведенным выше способом обновления
  • BAD - просмотр изменений.
  • Другой родительский подход, а не emit и brodcast -

*

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

Существует другой способ без использования $watch, используя 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;
});
0

Не знаю, где я взял этот шаблон, но для обмена данными между контроллерами и сокращения $rootScope и $scope это отлично работает. Это напоминает репликацию данных, где у вас есть издатели и подписчики. Надеюсь, что это поможет.

Сервис:

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

Контроллер, который получает новые данные, который является издателем, сделает что-то вроде этого.

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

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

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

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

  • 0
    использование «модели», которая разделяет состояние между контроллерами, похоже на вариант, который чаще всего используется. К сожалению, это не относится к сценарию, в котором вы обновите дочерний контроллер. Как вы «заново» получаете данные с сервера и воссоздаете модель? И как бы вы справились с аннулированием кэша?
  • 0
    Я думаю, что идея родительских и дочерних контроллеров немного опасна и вызывает слишком много зависимостей друг от друга. Однако, при использовании в сочетании с UI-Router, вы можете легко обновить данные вашего «потомка», особенно если эти данные вводятся через конфигурацию состояния. Аннулирование кэша происходит на издателе, в результате чего они будут нести ответственность за извлечение данных и вызов sharedDataEventHub.changeData(newData)
Показать ещё 5 комментариев
-4

Просто сделайте это просто (протестировано с 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
    Я думаю, что за это проголосовали, потому что это не модульное. dummy является глобальным для обоих контроллеров, а не совместно используется более модульным способом через наследование с $ scope или controllerAs или с помощью службы.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню