How to communicate between controllers while not using SharedService between them? - angularjs

I was reading all the answers about communication between controllers and directive, but is seems to me occurred using shared service and inject it to each one of them. When I'm developing a very large scale application, I don't know what my page is going to have in it. I may have 2 controllers need to communicate between them, and I also may have 5 directive and 2 controllers in the same page. I don't know from scratch what is going to be inside my view/page, so I need a better way how to communicate between them. I'm looking for that better way to do it, the right AngularJS way.
http://i60.tinypic.com/2z87q05.png
Above is my view example I'm working on: on the left side I have a directive tree, on the right side I have chart controller and directive grid. I may have more than this, but this is a good example of what I may have. Keep in mind, the view can have X componenets, you don't know from the beginning what will be in it.
Now, lets say each time I select node in tree on the left, I want to be able to tell the other controllers that nodeSelectedChange event happend. I don't want to inject each one of them a service that holds that info, so I thought about something like having a Page Manager Controller, which is the father of the all view. All my controllers/directives inside my page should talk with each other only by the PageManagerController, he is the only thing in page that knows what he has inside of it.
Important here to keep in mind: The tree don't know the page has chart or grid, they don't need to know each other in order to communicate. The page manager knows everything, now I want it to make the magic - Should it has a service? Should each other component has service and service can talk with PageManager service?
Help me to think. Hope I can connect all the dots to create a BETTER way for communication.
I think the sharedService way is good for small app, when you know from start what is going on in your app, but most of the time - You just don't know who and when is going to use your directive, it should be able to talk with everyone.
What I don't like about events:
The events being fired inside the controller, while it should be inside a service.
The listener controller should know the name of the event he is going to listen too. If I change the event name being $emit or $broadcast, I need to go all over the listeners $on("eventName") in all app and change to that unique name.
Directive is like a black box, I don't want to check inside of it each time and find the names of the events he is being broadcasting in order to communicate with it.
I need a way to exposed the events NAMES out of the directive, probably with a service connected to that controller.

There are a couple of good practices:
Use the $scope to communicate between directives and controllers for runtime dependencies (e.g. models you fetch from the server, instantiated classes, etc.). The way to go is to have an isolated scope with attributes mapped to the dependencies:
directive('tree', function(){
return {
scope: {
somevalue : "="
}
}
});
Use it like this:
<tree somevalue="objectFromController">
Use the injected services to communicate with static dependencies (Presentation Models, global sharable state, etc.)
directive('tree', function(treeState){
return {
scope: {
somevalue : "="
},
link: function(scope){
// some logic updating the treeState
treeState.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeState){
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
If you want to have maximum composability, stick to the first pattern:
directive('tree', function(){
return {
scope: {
model : "="
},
link: function(scope){
// some logic updating the treeState, stored as scope.model
scope.model.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeFactory){
$scope.treeModel = treeFactory.create();
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
And compose this stuff using template and binding as a communication bus:
<tree model="treeModel">
Sticking to this pattern you get:
No events
Well-defined directive "interface" (attributes in the isolated scope)
Easy composability
Reactive behavior based on scope change propagation

It really depends on the type of information you want to share from the tree directive to the other sections of the page.
You have a few options in front of you:
Use Scope Events
As someone mentioned above, you could fire an event and listen for events in various controllers and/or services. That said, it can get really ugly, really fast. Tracing events and figuring out what event listeners are active at a given point can give the best engineers a migraine!
Use a Service
Another option would be to use a Service, let's say a PageManagerService. In that case,
Each click of a tree item would set some information on the PageManagerService, saying which page, what objects, and what items it needs to display
Each component that needs to change could
Register a listener to be triggered when the PageManagerService changes
Add a watch on the service and run its code when the PageManagerService changes
The service itself is just going to be shared state, and it would be upto the directives and components on how it wants to consume and respond to changes in the state.
Use UI Router
But the more I think about this, the more it seems like a good use case from something like UI Router. UI Router allows you to define states, and have different parts of the page respond in different ways to state changes. Each section could respond to a state change in its own way, by loading
A different controller
A different template, possibly with different components and widgets
So what you would end up having is a structure with
The Tree directive on the left
A ui-view named, let's say, top
A ui-view named, let's say, bottom
Each item in your tree directive can then be a ui-sref, which is just a fancy way of saying instead of redirecting to a URL, redirect to a state.
You could then define your configurations in your application in a single place, like so:
$stateProvider.state('dashboard', {
views: {
"top": { templateUrl: 'my/dashboard.html', controller: 'DashboardCtrl'}
"bottom": { templateUrl: 'my/dashboard-grid.html', controller: 'DashboardGridCtrl'}
}
})
Similarly, you could have a state definition for each item in your tree directive, and each one just be a link to a different state.
Of course, the state definitions are done in your config section in an AngularJS application, which you would know is before the application starts. What if you needed dynamic states as well?
Well, a few answers / thoughts to lead you down the way for that as well:
The controllers and services would have to be predefined, you would not be dynamically creating HTML content and/or JS controllers
It is possible to expose the $stateProvider as a global variable in the config section, and dynamically call stateProvider.state inside a controller / service, wherever you have new state definitions.
More often than not though, the controllers and HTML remain constant, and we just need to trigger the different states with various parameters. That can easily be done by calling transitionTo function to transition to a defined state with various state parameters.

you can use $on, $emit and $broadcast to communicate among different controllers/scopes .
Please follow one of my earlier post . I have a setup a plunk . You can try out the example.
Angular Js newbie - link in a controller view that triggers another controller action
$on : setup a event handler
$emit : communicate to parent controllers
$broadcast : communicate to child controllers
To know more visit - https://docs.angularjs.org/api/ng/type/$rootScope.Scope

Related

I am understanding rootScope and how it works. [duplicate]

We have a large Angularjs 1.6 application that has $rootscope scattered throughout the app in over 200 places in filters, services, routes, etc.. so it needs to be refactored, but I'm not sure how to know when to remove it. When is it a best practice to use $rootscope in the application?
I've read everything from never, to using it for storing variables, which I assumed was for sharing data between controllers. I've since read that it's better to use factories/services for this use case instead and I also read that one valid use case is to use $rootscope as a global event bus.
I didn't really see this explained in the Angularjs docs.
From ng-book:
When Angular starts to run and generate the view, it will create a binding from the root ng-app
element to the $rootScope. This $rootScope is the eventual parent of all $scope objects.
The $rootScope object is the closest object we have to the global context in an
Angular app. It’s a bad idea to attach too much logic to this global context, in the
same way that it’s not a good idea to dirty the JavaScript global scope.
You are right, you should definitely use Services to share data and logic between your modules.
Putting a lot of logic in your $rootScope means having bad maintainability and modularity in your application, it is also very difficult to test issues.
I highly suggest you to take a look at:
Services AngularJS Documentation
Thinkster brilliant article on how to share data between controllers
Screencast by Simpulton
#Breck421 answer to this question
I know it may be easy to attach everything to $rootScope, but It is just difficult to work on it, make little changes, reusing your code for other applications or modules and test your application in general.
EDIT
Recently I had to fetch some items from API and catch these items in order to show them in a certain view. The item fetching mechanism was in a certain Factory, while the mechanism to format and show the items was in a Controller.
So, I had to emit an event in the Factory when items got fetched and catch this event in the Controller.
$rootScope way
//Factory
$rootScope.$broadcast('refreshItems', items);
//Controller
$scope.$on('refreshItems', doSomething());
It clearly worked but I didn't really like to use $rootScope and I've also noticed that the performance of that task were pretty miserable.
Then I tried giving a shot to Postal.js:
Postal.js is an in-memory message bus - very loosely inspired by AMQP -
written in JavaScript. Postal.js runs in the browser, or on the server
using node.js. It takes the familiar "eventing-style" paradigm (of
which most JavaScript developers are familiar) and extends it by
providing "broker" and subscriber implementations which are more
sophisticated than what you typically find in simple event
emitting/aggregation.
I tried using Postal.js for this kind of needs and I found out that it is really faster than using $rootScope for this purpose.
//Factory
$scope.$bus.publish({
channel : 'reloadItems',
topic : 'reloadItems'
data : items
);
//Controller
$scope.$bus.subscribe({
channel : 'reloadItems',
topic : 'reloadItems',
callback : function () {
resetAndLoadItems();
}
});
I hope I've been helpful.
From Angluar docs: Every application has a single root scope. All other scopes are descendant scopes of the root scope. Scopes provide separation between the model and the view, via a mechanism for watching the model for changes.
Of course this is going to come down to a matter of opinion and style. I tend to follow a style very close to John Papa's Angular Style Guide.
In keeping with the two, and following a good separation of concerns strategy my architecture contains factory models that are shared across the application. My controllers in turn are all bound to the services that hold the shared data.
Using $rootScope as the global event bus is exactly how Angular uses it. Should you tag along and do the same? I don't see why not. But if you are, make sure that the purpose is clearly defined and maybe even use your own service to register events to the global event bus. That way you are decoupling your app from Angular, and if you ever decide that you want to change the framework in which your global event bus lives then you can change it in one place.
This is what I'm suggesting:
Global event bus
// Angular specific: add service to module
angular.module('app').factory('globalEventBus', GlobalEventBus);
// Angular specific: inject dependencies
GlobalEventBus.$inject(['$rootScope']);
// Non framework specific.
// param: fameworkEventBus will be $rootScope once injected
function GlobalEventBus(fameworkEventBus) {
var globalEventBus = this;
globalEventBus.registerEvent(params...){
fameworkEventBus.
}
return globalEventBus;
}
Global data models
My data models are smart and tend to contain functions that provide information about themselves or retrieve/return specific data.
// Angular specific: add service to module
angular.module('app').factory('dataModel', DataModel);
function DataModel() {
var dataModel= this;
dataModel.myData = {};
dataModel.GetSpecificData = funtion(param){
return ...
}
return dataModel;
}
The controller
// Angular specific
angular.module('app').controller('MyController', MyController);
// Angular specific: inject dependencies to controller
MyController.$inject = ['dataModel'];
// By convention I use the same parameter name as the service.
// It helps me see quickly if my order of injection is correct
function MyController(dataModel) {
var myController = this;
// Bind to the service itself, and NOT to the service data property
myController.myData = dataModel;
myController.doStuff = function(){
}
}
Here is a fun post about binding to services and not to service properties.
All in all you have to be the judge of what works best for you. A good system architecture and good style have saved me countless hours of solving completely avoidable problems.
After doing some more work with Angular and more reading I found this basic rule of thumb for using $rootscope that I wanted to add to the other answers:
Only add properties that are static or constant. Anything else that
represents a changing state or a mutable value should have a
corresponding directive or controller to handle it.

Exposing directive controller to parent controller

I want to expose some of my directive's functionality through its controller (think a public API for this directive).
return {
restrict: 'E',
scope: {},
controller: function($scope) {
this.method1 = ...;
this.method2 = ...;
},
controllerAs: 'dir',
link: function (scope, element, attrs) { ... }
}
Then in my parent controller or template call dir.method1 to get stuff accomplished inside the directive. Any ideas if this is possible as of Angular 1.3?
I'd like to refrain from event passing or even function passing, I have heard this is possible although I have never seen an implementation of this.
It is possible, but your issue isn't to figure out how to get the API out. It's how to get TO it from the parent. You're creating an isolate scope through your use of the 'scope' option. You're also making an element-type directive, so I'm guessing you're doing something like this:
<my-parent>
<my-child></my-child>
</my-parent>
where <my-parent> is the parent directive, and <my-child> is the directive with the API you want to expose.
The real question is what you're trying to achieve here. There is totally a way to do what you're asking. Just because the scope is isolated doesn't mean you can't get to it. You can just iterate through the parent $scope's $$childHead/etc list to find the child whose API you want to access. Anything you define in the child like this:
$scope.myApiFunction = function() {
};
will be visible here. (Things you put into 'this' will not - use the $scope storage bucket instead.)
That means if you only had ONE child you could do something like this from the parent controller:
$scope.$$childHead.myApiFunction();
Simple. Also, very crude. There are lots of problems here: what if you have many children? What if this child with its API ends up one level down? Etc. It's breaking all kinds of OO patterns and it's going to get messy, fast.
Your question is very abstract - it might be good if you updated it with an exact example. Without that, let me guess at your goal. There are two ways to do something "like this" that are encouraged within Angular:
Services. Whenever you say "API", think Service first. A service is a singleton (automatically) so it's tailor-made for creating APIs. And services can use the Factory pattern to return objects of a type, so THOSE are tailor made for doing things like having a manager service handle, say, a buddy list in an IM client, with API methods for creating, removing, and finding buddies.
Items that add "optional" functionality to their parents when they're defined. Let's say we have three possible types of tooltips: tooltips that have a hover effect, those that have a click effect, and those that are triggered by a "walkthrough" system in some order. For this kind of thing, the easy thing to do is just reverse the API, like this:
Parent Controller:
$scope.tooltipHandler = {
showTooltip: function() {},
hideTooltip: function() {}
};
Child Controller:
$scope.$parent.tooltipHandler = {
showTooltip: function() {
// Do some real work
},
hideTooltip: function() {
// Do some real work
},
}
What happens here is if there's no tooltip defined, when the parent runs its walkthrough, nothing happens. If you add the blue tooltip display module, when the parent runs its walkthrough now, it's going to show blue tooltips.
Make sense?
I arrived here looking for a similar response. So far the best that I can figure is to do what Angular does with ngForm.
In the documentation clearly states
If the name attribute is specified, the form controller is published onto the current scope under this name.
This basically makes the form controller accessible from anywhere.
If you have the following DOM
<div ng-controller="MyCtrl as parentCtrl">
<form name="parentCtrl.frmCtrl">
<my-child-directive>
</form>
</div>
You can use require: 'ngForm' in my-child-directive to get access from an inside directive. If you are in the parent controller you can access it trough the frmCtrl variable.
Not sure if this is best practice. In ngForm the name attribute works well, but I don't even know how to call such an attribute for a custom directive.
Thats why I arrived here, I wanted to know if this is "The Angular way" and what types of convetions are on the subject.
Hope it helps!

Is passing data between directives, controllers, and services using $emit and $broadcast bad practice?

Hi I'm a beginning Angular developer and I was wondering if the way I've passed data between controllers is bad practice or not.
I'm creating a seatmap widget whereby you click on a seat and it will display data in another part of the App using a different controller.
1) I have a directive that incorporates dynamic templating. Based on the model being passed (from an ng-repeat), it will create an event listener like so:
if(scope.seat.isAvailable) {
element.bind('click', function() {
scope.$emit('clickedSeat', scope.seat);
}
element.append(seatAvailableTemplate);
}
2) In my controller, I have the following listening for the click:
$scope.$on('clickedSeat', function(event, seat) {
seatSelectionService.broadcastSeatData(seat);
}
3) I have a service where I've passed in $rootScope which allows me to broadcast the data to a different controller:
var seatSelectionService = {};
seatSelectionService.broadcastSeatData = function(clickedSeat) {
seatSelectionService.seatData = clickedSeat;
$rootScope.$broadcast('seatSelected');
}
return seatSelectionService;
4) In the other controller, I have a listener which sets the $scope attribute, which renders {{selectedSeat}} in the view:
$scope.$on('seatSelected', function() {
$scope.selectedSeat = seatSelectionService.seatData;
$scope.apply();
}
Of course I have had to pass in seatSelectionService into both controllers for it to work.
Is there a better way to do this? Or is this way of passing data valid practice?
I would say less is more- if you can get rid of code do it- I would suggest you remove this in your controller unless you are doing something you are not showing, and put it in your directive, in step one:
$scope.$on('clickedSeat', function(event, seat) {
seatSelectionService.broadcastSeatData(seat);
}
$broadcast and $emit themselves aren't bad practice, but it does look like you've over complicated your app.
You can add services directly to directives - they can have controllers built into them that depend on the services.
So in your directive, add a controller that uses the seatSelectionService, something like:
seatSelectionService.setSeat( scope.seat )
Which cuts out one of the steps at least - your setSeat method would still want to do a $broadcast when it's done down to anything listening.
Directives can be really simple or really complex - they can simply be a quick way of outputting a repeated template, or a dynamic template with a controller and access to multiple services.
Something else you might want to look at is promises - I used to use $broadcast and $emit a lot more than I needed to, until I learnt how to use promises and defer properly

AngularJS: Using services in directives

This is an angularjs app. I have a service that handles the loading of content (ajax). While the service is getting the content, a number of things throughout the app hide, later showing again (depending on the content returned). They might have the same scope, different scope, whatever. They just need to hide while content is loading, and then show when it's done. Pretty normal stuff.
Right now, I have separate controllers watching a "loading" property of the service and using regular angular directives (ng-show, ng-hide, etc.) to show/hide. But this feels like overkill. I'd prefer to write a custom "loading" directive that injects the loading service and does the watching and showing/hiding.
My question is: Is what I want to do "bad"? The controller way, I end up boilerplating a bunch of code, maybe up to like 5 or 6 times, or even more as the app grows. The custom directive way, I write it once and use an attribute where I need it. Yeah - there's a dependency on that service, but that just doesn't feel like the end of the world that some people have made me start to think I should think it is.
For what it's worth, I feel like I've heard "separation of concerns" so many times I've become paralyzed by it. It leads me to overthink everything because I want to do things the right way, but it sure doesn't feel like I'm being very productive.
If I understood correctly, you have a bunch elements that should hidden when a particular service is loading data, and then be displayed again when the data is loaded, right?
In that case, events might be a good solution:
they can be global to the appliciation (which i think is what you are aksing for).
they allow for avoiding direct coupling between elements (also one of your concerns).
So, in your service, just broadcast events when stuff happens:
$rootScope.$broadcast('loading-data');
axajStuffOrWhatever(function() {
$rootScope.$broadcast('data-loaded');
});
Then, wrap the show/hide behaviour in a directive that will listen to those events.
.directive('hideWhileLoadingData', function() {
return {
link: function(scope, el, attrs) {
scope.$on('loading-data', function() {
el.css('display', 'none');
});
scope.$on('data-ready', function() {
el.css('display', 'block');
});
}
};
});
Use the directive:
<div hide-while-loading-data>something</div>
The advantage of using events here, is that later on, they could be originated by a different service, or by multiple services, and the directive will not be affected by that as long as the events are the same.
For more complex behaviour, you could also parametrize the events and the directive, so different elements will react to different kind of stuff.
I've made an example of this.
In my opinion all scopes which depend on this service should be children of one parent scope. If you have the parent scope responsible for talking with the service then any directive of any scope can access it via $parent on the $scope.

Common Controller for ng-view

Is there a way to call a common controller everytime ng-view is changed? i.e i want a common controller to be called everytime a new $route is loaded.
If you have specified custom controllers for your different routes, then there's no way that I know of that you can also specify a common controller that always gets invoked, unless you use some kind of inheritance and always call a method in the base controller.
An alternative approach is to subscribe to the events the route service broadcasts.
Example:
function MyController($rootScope, [...]) {
$rootScope.$on('$routeChangeSuccess', function (current, previous) {
// ...
});
}
You have a list of available events and their parameters here.
I believe you also can add properties, methods etc. to $rootScope which you can use in bindings in your views thanks to how Angular's binding mechanism works. If it doesn't find it on the current scope, it checks its parent etc. up to the root scope.

Resources