Say I got this following HTML structure:
<body ng-app="demo" ng-controller="RootCtrl">
<header>
<!-- Header Material -->
</header>
<main ng-controller="MainCtrl">
<!-- Main Content -->
<nav ng-controller="NavCtrl">
<!-- Navbar -->
</nav>
</main>
<body>
Now, suppose NavCtrl
needs to manipulate a model that happens to exist under RootCtrl
's scope - under which conditions would $emit/$on
be better suited? And under which conditions would it be better to directly manipulate the model via scope inheritance?
If you're using prototypical inheritance, you need to be careful as it's easy to make errors when using same variable names in parent and child controllers. This can be avoided by making sure $scope variables always 'have a dot' in them somewhere, but needs discipline to make sure you always do this. You could also access a variable in NavCtrl set in RootCtrl using the $scope.$parent.$parent
structure, but this is brittle and essentially ties your controllers to the DOM structure which is a recipe for problems down the line.
$emit/$on have the issue of potentially silently failing if you make a typo in the name of the event, and can make it hard to follow what's happening in the event of an error. It's better to use them sparingly. http://eburley.github.io/2013/01/31/angularjs-watch-pub-sub-best-practices.html says only to use them "when you need to let multiple subscribers know about an event and those subscribers need to do more than radiate information to their view."
The normal Angular way to share data models across controllers is to make a service, and inject that into both controllers instead. This fits with the "prefer composition over inheritance" principle of OOP in general too.
app.service('dayService', function () {
var day = 'Monday';
return {
getDay: function() {
return day;
},
setDay: function(thisDay) {
day = thisDay;
}
};
})
function NavCtrl($scope, dayService) {
$scope.day = dayService.getDay();
}
function RootCtrl($scope, dayService) {
dayService.setDay('Sunday');
}
HTML:
<nav ng-controller="NavCtrl">
Today is {{day}}
</nav>
You might also find Misko's video on Angular best practices interesting, it talks about what to put in controllers vs services around the 28 min mark, and more about events ($emit/$on) towards the end. His conclusion (paraphrased) is that events are somewhat problematic best used only for situations where two things really don't need to know about each other and have to be kept very separate, or if the event is not always necessary can sometimes be ignored.
I would say basic rules are:
Use service for sharing data between two controllers, it's slightly more complex than inheritance but nothing too difficult.
Use events for sharing between multiple different subscribers in complex ways.
$scope in controllers should be 'write only' (rule taken directly from Misko's best practices video above). Scope inheritance where "NavCtrl needs to manipulate a model that happens to exist under RootCtrl's scope" would involve reading parent scope too, so I think is best avoided.
If the model in RootCtrl is an object, and NavCtrl only needs to modify few properties of the model object, use scope inheritance would be the easiest. Just make sure you know how prototypal works if you want to take this approach.
Using service to share data among controllers works, as mentioned in mikel's answer, but its setup is quite overkill for simple cases, especially when scope inheritance is available to you. And, the service will exist in the whole lifecycle of your app, which might not what you want or need.
IMHO, $emit/$on works fairly well in your case. Setup isn't too heavy, scopes are decoupled nicely and all modifications to the model are centralized in RootCtrl, which is good for maintenance I think. If you concern about mistakes you might make while typing the event names, you can make constants for event names by using module.constant()
, then use those constants instead of typing in event names as strings all the time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With