Каков правильный способ связи между контроллерами в AngularJS?

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

в настоящее время я использую ужасную помадку с участием window:

function StockSubgroupCtrl($scope, $http) {
    $scope.subgroups = [];
    $scope.handleSubgroupsLoaded = function(data, status) {
        $scope.subgroups = data;
    }
    $scope.fetch = function(prod_grp) {
        $http.get('/api/stock/groups/' + prod_grp + '/subgroups/').success($scope.handleSubgroupsLoaded);
    }
    window.fetchStockSubgroups = $scope.fetch;
}

function StockGroupCtrl($scope, $http) {
    ...
    $scope.select = function(prod_grp) {
        $scope.selectedGroup = prod_grp;
        window.fetchStockSubgroups(prod_grp);
    }
}

19 ответов


редактировать: проблема, рассмотренная в этом ответе, была решена в angular.js версия 1.2.7. $broadcast теперь избегает пузыриться над незарегистрированными областями и работает так же быстро, как $emit. $broadcast performances are identical to $emit with angular 1.2.16

Итак, теперь вы можете:

  • использовать $broadcast С $rootScope
  • слушайте с помощью $on местных $scope это нужно знать о событие

Оригинальный Ответ Ниже

я настоятельно советую не использовать $rootScope.$broadcast + $scope.$on а $rootScope.$emit+ $rootScope.$on. Первый может вызвать серьезные проблемы с производительностью, поднятые @numan. Это потому, что событие будет пузыриться через все областей.

однако последний (используя $rootScope.$emit + $rootScope.$on) делает не терпят от этого и могут поэтому быть использованы как быстрое сообщение канал!

из угловой документации $emit:

отправляет имя события вверх по иерархии областей, уведомляя зарегистрированного

так как нет области выше $rootScope, там не происходит пузырьков. Это абсолютно безопасно использовать $rootScope.$emit()/ $rootScope.$on() как EventBus.

однако, есть один gotcha при использовании его из контроллеров. Если вы напрямую связываетесь с $rootScope.$on() изнутри контроллер, вам придется очистить привязку самостоятельно, когда ваш локальный $scope разрушается. Это связано с тем, что контроллеры (в отличие от служб) могут быть созданы несколько раз за время существования приложения, что приведет к суммированию Привязок, в конечном итоге создающих утечки памяти повсюду:)

чтобы отменить регистрацию, просто слушать $scope ' s $destroy событие, а затем вызвать функцию, которая была возвращена $rootScope.$on.

angular
    .module('MyApp')
    .controller('MyController', ['$scope', '$rootScope', function MyController($scope, $rootScope) {

            var unbind = $rootScope.$on('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });

            $scope.$on('$destroy', unbind);
        }
    ]);

I скажем, это не совсем угловая конкретная вещь, поскольку она применяется и к другим реализациям EventBus, что вам нужно очистить ресурсы.

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

самый чистый способ, чтобы обезьяна патч $rootScope обеспечить такие $onRootScope метод будет через декоратор (блок запуска, вероятно, сделает это просто отлично, но pssst, не говорите никому)

чтобы убедиться, что $onRootScope свойство не появляется неожиданно при перечислении над $scope мы используем:Object.defineProperty() и set enumerable to false. Имейте в виду, что вам может понадобиться прокладка ES5.

angular
    .module('MyApp')
    .config(['$provide', function($provide){
        $provide.decorator('$rootScope', ['$delegate', function($delegate){

            Object.defineProperty($delegate.constructor.prototype, '$onRootScope', {
                value: function(name, listener){
                    var unsubscribe = $delegate.$on(name, listener);
                    this.$on('$destroy', unsubscribe);

                    return unsubscribe;
                },
                enumerable: false
            });


            return $delegate;
        }]);
    }]);

С этим методом на месте код контроллера сверху можно упростить кому:

angular
    .module('MyApp')
    .controller('MyController', ['$scope', function MyController($scope) {

            $scope.$onRootScope('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });
        }
    ]);

поэтому в качестве окончательного результата всего этого я настоятельно рекомендую вам использовать $rootScope.$emit + $scope.$onRootScope.

кстати, я пытаюсь убедить команду angular решить проблему в angular core. Здесь идет дискуссия:https://github.com/angular/angular.js/issues/4574

вот jsperf, который показывает, сколько воздействия perf $broadcastприносит в таблицу в приличном сценарии всего 100 $scopes.

http://jsperf.com/rootscope-emit-vs-rootscope-broadcast

jsperf results


на лучшие ответы здесь была работа вокруг угловой проблемы, которая больше не существует (по крайней мере, в версиях >1.2.16 и "вероятно, раньше"), как упоминал @zumalifeguard. Но я остался читать все эти ответы без реального решения.

мне кажется, что ответ сейчас должен быть

  • использовать $broadcast С $rootScope
  • слушайте с помощью $on местных $scope это нужно знать о событии

так публиковать

// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {

  $rootScope.$broadcast('topic', 'message');

}]);

и подписаться

// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {

  $scope.$on('topic', function (event, arg) { 
    $scope.receiver = 'got your ' + arg;
  });

}]);

Plunkers

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


используя $rootScope.$ broadcast и $ scope.$ on для связи PubSub.

Также см. Этот пост: AngularJS-Связь Между Контроллерами


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

angular.module('myservice', [], function($provide) {
    $provide.factory('msgBus', ['$rootScope', function($rootScope) {
        var msgBus = {};
        msgBus.emitMsg = function(msg) {
        $rootScope.$emit(msg);
        };
        msgBus.onMsg = function(msg, scope, func) {
            var unbind = $rootScope.$on(msg, func);
            scope.$on('$destroy', unbind);
        };
        return msgBus;
    }]);
});

и использовать его в контроллер, как это:

  • 1
    function($scope, msgBus) {
        $scope.sendmsg = function() {
            msgBus.emitMsg('somemsg')
        }
    }
    
  • 2
    function($scope, msgBus) {
        msgBus.onMsg('somemsg', $scope, function() {
            // your logic
        });
    }
    

GridLinked опубликовал PubSub решение, которое, кажется, довольно хорошо разработана. Сервис можно найти,здесь.

также схема их работы:

Messaging Service


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

Я бы предложил воспользоваться услугой. Вот как я недавно реализовал его в одном из своих проектов -https://gist.github.com/3384419.

основная идея-Регистрация шины pubsub / event в качестве службы. Затем введите eventbus, где вам нужно подписаться или публикация событий/тем.


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

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

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


    var data="";

    return{
        setData:function(str){
            data = str;
        },

        getData:function(){
            return data;
        }
    }


})


myApp.controller('FirstController',function($scope,myFactoryService){
    myFactoryService.setData("Im am set in first controller");
});



myApp.controller('SecondController',function($scope,myFactoryService){
    $scope.rslt = myFactoryService.getData();
});

в HTML HTML вы можете проверить, как это

<div ng-controller='FirstController'>  
</div>

<div ng-controller='SecondController'>
    {{rslt}}
</div>

по поводу исходного кода - похоже, вы хотите обмениваться данными между областями. Для обмена данными или состоянием между $scope документы предлагают использовать службу:

  • для запуска кода без состояния или с состоянием, общего для контроллеров-используйте вместо углового услуг.
  • создать экземпляр или управлять жизненным циклом другие компоненты (например, для создания экземпляров служб).

Ref: угловые документы ссылка здесь


Я действительно начал использовать Postal.js как шина сообщений между контроллерами.

есть много преимуществ для него в качестве шины сообщений, таких как привязки стиля AMQP, способ, которым postal может интегрировать w/ iFrames и веб-сокеты, и многое другое.

я использовал декоратора, чтобы получить почтовую установку на $scope.$bus...

angular.module('MyApp')  
.config(function ($provide) {
    $provide.decorator('$rootScope', ['$delegate', function ($delegate) {
        Object.defineProperty($delegate.constructor.prototype, '$bus', {
            get: function() {
                var self = this;

                return {
                    subscribe: function() {
                        var sub = postal.subscribe.apply(postal, arguments);

                        self.$on('$destroy',
                        function() {
                            sub.unsubscribe();
                        });
                    },
                    channel: postal.channel,
                    publish: postal.publish
                };
            },
            enumerable: false
        });

        return $delegate;
    }]);
});

вот ссылка на сообщение в блоге на topic...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/


вот как я это делаю с Фабрики / Услуги и просто инъекция зависимостей (DI).

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

# PeopleService holds the "data".
angular.module('myApp').factory 'PeopleService', ()->
  [
    {name: "Jack"}
  ]

# Controller where PeopleService is injected
angular.module('myApp').controller 'PersonFormCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
  $scope.people = PeopleService
  $scope.person = {} 

  $scope.add = (person)->
    # Simply push some data to service
    PeopleService.push angular.copy(person)
]

# ... and again consume it in another controller somewhere...
angular.module('myApp').controller 'PeopleListCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
  $scope.people = PeopleService
]

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

module.factory("eventBus",function (){
    var obj = {};
    obj.handlers = {};
    obj.registerEvent = function (eventName,handler){
        if(typeof this.handlers[eventName] == 'undefined'){
        this.handlers[eventName] = [];  
    }       
    this.handlers[eventName].push(handler);
    }
    obj.fireEvent = function (eventName,objData){
       if(this.handlers[eventName]){
           for(var i=0;i<this.handlers[eventName].length;i++){
                this.handlers[eventName][i](objData);
           }

       }
    }
    return obj;
})

//Usage:

//In controller 1 write:
eventBus.registerEvent('fakeEvent',handler)
function handler(data){
      alert(data);
}

//In controller 2 write:
eventBus.fireEvent('fakeEvent','fakeData');

вот быстрый и грязный способ.

// Add $injector as a parameter for your controller

function myAngularController($scope,$injector){

    $scope.sendorders = function(){

       // now you can use $injector to get the 
       // handle of $rootScope and broadcast to all

       $injector.get('$rootScope').$broadcast('sinkallships');

    };

}

вот пример функции для добавления в любой из контроллеров брата:

$scope.$on('sinkallships', function() {

    alert('Sink that ship!');                       

});

и, конечно, вот ваш HTML:

<button ngclick="sendorders()">Sink Enemy Ships</button>

вы можете получить доступ к этой функции hello в любом месте модуля


Я создам сервис и использую уведомление.

  1. создайте метод в Службе уведомлений
  2. создайте общий метод широковещательного уведомления в Службе уведомлений.
  3. из исходного контроллера вызовите notificationService.Метод. Я также передать соответствующий объект для сохранения, если это необходимо.
  4. в рамках метода я сохраняю данные в службе уведомлений и вызываю общий метод notify.
  5. в пункт назначения контроллер I listen ($scope.on) для широковещательного события и доступа к данным из службы уведомлений.

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

надеюсь, что это помогает


вы можете использовать AngularJS встроенный сервис $rootScope и введите эту услугу в оба ваших контроллера. Затем вы можете прослушивать события, которые запускаются на объекте $rootScope.

$rootScope предоставляет два диспетчера событий под названием $emit and $broadcast которые отвечают за диспетчеризацию событий(может быть пользовательские события) и использовать $rootScope.$on функция для добавления прослушивателя событий.


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


function mySrvc() {
  var callback = function() {

  }
  return {
    onSaveClick: function(fn) {
      callback = fn;
    },
    fireSaveClick: function(data) {
      callback(data);
    }
  }
}

function controllerA($scope, mySrvc) {
  mySrvc.onSaveClick(function(data) {
    console.log(data)
  })
}

function controllerB($scope, mySrvc) {
  mySrvc.fireSaveClick(data);
}

вы можете сделать это, используя угловые события, которые $emit и $broadcast. Согласно нашим знаниям, это лучший, эффективный и эффективный способ.

Сначала мы вызываем функцию от одного контроллера.

var myApp = angular.module('sample', []);
myApp.controller('firstCtrl', function($scope) {
    $scope.sum = function() {
        $scope.$emit('sumTwoNumber', [1, 2]);
    };
});
myApp.controller('secondCtrl', function($scope) {
    $scope.$on('sumTwoNumber', function(e, data) {
        var sum = 0;
        for (var a = 0; a < data.length; a++) {
            sum = sum + data[a];
        }
        console.log('event working', sum);

    });
});

вы также можете использовать $rootScope вместо $scope. Используйте свой контроллер.


начиная угловой 1.5 и это компонент на основе развития фокус. Рекомендуемый способ взаимодействия компонентов - использование свойства "require" и привязки свойств (ввод/вывод).

компоненту потребуется другой компонент (например, корневой компонент) и получить ссылку на его контроллер:

angular.module('app').component('book', {
    bindings: {},
    require: {api: '^app'},
    template: 'Product page of the book: ES6 - The Essentials',
    controller: controller
});

затем вы можете использовать методы корневого компонента в компоненте ребенка:

$ctrl.api.addWatchedBook('ES6 - The Essentials');

это корень компонент функции контроллера:

function addWatchedBook(bookName){

  booksWatched.push(bookName);

}

вот полный обзор архитектуры:Компонент Коммуникации