NOTE: This article is using $scope, but can easily be changed to use controllerAs syntax and bindToController instead. Please do.
I recently found the need for two controllers to communicate a lot more than normal events would easily enable. I needed one controller to ask another for information, without them being too strongly coupled (because nobody likes that).
There are several ways of solving this problem, but I found that using a delegate pattern was the most elegant solution for my problem.
So the way this works is that a delegate object is sent to the controller that needs to be ‘asking the questions’, so that it knew where to get answers.
The delegate object comes from another controller that can help answer those questions.
The way this works is fundamentally very simple.
The Sub Controller needs someone to answer questions or do something that it cannot do by itself.
The reason it cannot do it, is probably because it shouldn’t be concerned with with how to get the answers. It’s job is to get a hold of the answer and display it (or use it for something at least).
Someone else knows how to answer the question. So it needs a delegate to do the job for it.
The Main Controller creates an object that is designed to work for the Sub Controller. How it gets the answer is not important. It can use a service to search for it elsewhere. It doesn’t matter. The point is that when the Sub Controller asks the question, the delegate is the one who will ultimately answer the question for the Sub Controller.
For this to work, the Main Controller needs to send the delegate object to the Sub Controller. This is done using a directive isolate scope and bind a parent scope property to the isolate scope:
.directive('myDirective', function () { return { scope: { delegate: '=' }, ... }
<my-directive delegate="someObjectThatCanWorkAsDelegate"></my-directive>
The ‘=’ allows the controller for myDirective to access the delegate on its isolate scope.
The rest is just a matter of wiring things up and calling the correct stuff. I will show a simple example, that I have also implemented in a plunker: http://plnkr.co/edit/xNxScz8yy7SWQNk0r6RA?p=preview
Main Controller:
.controller('MainController', function ($scope) { var vm = this; vm.delegateObject = { getSeriousAnwser: function () { return "INSERT SERIOUS ANSWER HERE"; }, getJokeAnswer: function () { return "INSERT FUNNY JOKE HERE"; } }; })
Directive with Sub Controller:
.directive('myDirective', function () { return { restrict: 'E', scope: { delegate: '=' }, controller: function ($scope) { var vm = this; vm.answer = 'no answer yet...'; vm.getAnswer = function () { if (vm.joke) { vm.answer = $scope.delegate.getJokeAnswer(); } else { vm.answer = $scope.delegate.getSeriousAnwser(); } } }, controllerAs: 'vm', template: 'Joke <input type="checkbox" ng-model="vm.joke" >' + '<br><button ng-click="vm.getAnswer()">Get Answer</button>' + '<br>' }; });
HTML:
<body ng-controller="MainController as vm"> <my-directive delegate="vm.delegateObject"></my-directive> </body>
I know there is not only one way to solve this sort of problem. You can in theory use events back and forth, but that will soon get very ugly and strongly coupled.
Another way, which seems to be popular, is to use a service like a messaging bus. I didn’t think it would be a good fit for the situation I was in, but it would certainly be better if you were not able to control the scopes and use directives in the same way as I do here.
It all depends on the situation.
Follow me on Twitter: @gjermundbjaanes