Best practice for nesting controllers, scopes and models? - angularjs

I've seen (and read) a lot of information on how nested scopes work and I seem to have understood it well and use/abuse it a lot. My question is not about how it's working, it's just a question of how to best use it. Let me give an example.
<body ng-controller="MainCtrl">
<div class="container" ng-view>
The view will load something like this:
<div ng-controller="CreateEventCtrl">
...
<div class="panel-body" ng-include src="'partials/eventinfo.html'">
That partial will also have a controller. This all makes great sense to me. I got a MainCtrl that know about your login information, you preferences etc. the CreateEventCtrl will know what is needed to handle creating, editing etc of the event and the specific partial (part of an accordian will need to display some specific information.
I might need to access stuff from one of the parent controllers, which is easy but my problem is; it can be done in so many different ways that I can't seem to figure out what the best way is.
So far I've tried the following implementations.
Have MainCtrl create a $scope.model that holds basic information and let all other child controllers just extend their own properties onto that model.
Have MainCtrl create a $scope.model and let child controllers append an extra model like this $scope.model.createEventModel = { ... } or just add the model directly on the scope like this $scope.createEventModel = { ... }
Have MainCtrl create a $scope.shared model and let the first level childcontrollers create a $scope.model that its next child can either extend, overwrite or add a new $scope.eventInfoModel on it, depending on what is best for the current view. All child controllers can opt to extend/overwrite properties on $scope.shared as well
It seems there are tons of variations and I just wonder if there is a best practice on this. I've tried google it but end up with a ton of posts trying to explain 'the dot', prototypal inheritance and how nested scopes work.
At the moment I'm going with nr.3 but I also know I will probably challenge my own decision at some point and rework it again. I would really want to shortcut all that extra work and learn what other people are doing.
UPDATE: I read everywhere that i should use Services to share data. But I don't get it. The data i want to share might be fetched through a Service but it's a one-way "get/fetch" operation. Also some of the data is user choices that reflect/filter the rest of the data

I listened to you guys and tried it out. I ended up creating a few services that holds my shared data and then have all controllers needing that information, have it injected.
While i really didn't like this approach to begin with, I find that it makes it clear what controllers will be using the data and what controllers will not. It also kind of helps with separation of concern as i can put all the store/load/modify/cache into the service and not worry about it in the controllers. All i need to do in the controllers is either get the data or put it back.

Related

AngularJS: Why not write logic in controller?

Pardon me if this sounds stupid but I have been using AngularJS for a while now and everywhere I have seen people telling me to wrap my logic in a directive(or service ?) instead of my controller and keep only the bindings in my controller. Apart from the reusability aspect of a directive is there any other reason ?
Until now I haven't actually understood why this is the case. Doesn't writing a directive come with a lot of overhead ? I haven't faced any kind of problems writing logic in my controller and it is EASY. What ARE the downfalls of this approach ?
The controller is the right place to do all and everything that is related to the scope. It is the place where you write all the
$scope.$watch(...)
and define all the $scope functions that you need to access from your views ( like event handlers ). Generally, the event handler is a plan function which will in turn call a function a service.
$scope.onLoginButtonClick = function(){
AuthenticationService.login($scope.username,
$scope.password);
};
On very rare occasions you can add a promise success handler in there.
DONT: Write business logic in controllers
There was a very specific reason why the earlier example was like that. It showed you a $scope function that was in turn calling a function in a service. The controller is not responsible for the login mechanism or how login happens. If you write this code in a service, you are decoupling the service from the controller which means anywhere else that you want to use the same service, all that you need to do is, inject and fire away the function.
Rules for the Controller going forward:
Controllers should hold zero logic
Controllers should bind references to Models only (and call methods returned from promises)
Controllers only bring logic together
Controller drives Model changes, and View changes. Keyword; drives, not creates/persists, it triggers them!
Delegate updating of logic inside Factories, don't resolve data inside a Controller, only update the Controller's value with updated Factory logic, this avoids repeated code across Controllers as well as Factory tests made easier
Keep things simple, I prefer XXXXCtrl and XXXXFactory, I know exactly what the two do, we don't need fancy names for things
Keep method/prop names consistent across shared methods, such as this.something = MyFactory.something; otherwise it becomes confusing
Factories hold the Model, change, get, update, and persist the Model changes
Think about the Factory as an Object that you need to persist, rather than persisting inside a Controller
Talk to other Factories inside your Factory, keep them out the Controller (things like success/error handling)
Try to avoid injecting $scope into Controllers, generally there are better ways to do what you need, such as avoiding $scope.$watch()
There two good reasons for me for keeping logic out of a controller:
Reusability
If your application has multiple controllers and each do pretty much the same thing with some differences then keeping logic in the controller means you will be repeating the code you write. It's better if you Don't Repeat Yourself. By putting that logic into a service you can inject the same code into multiple controllers. Each service (really a Factory) is created as a new instance of itself each time it is injected into a controller. By pushing logic into a service you can modularise your code, which keeps it easier to maintain and test (see below)
Testing
Good code is tested. Not just by people but by the unit tests you write. Unit tests give you as a developer assurance that your code does what you expect it too. They also help you design your code well.
If your controller has 20 different methods each with their own logic, then testing (and your code) is turning into spaghetti.
It's easier to write unit tests that are narrow i.e. they test one thing at a time. And fortunately it's also good (for the reasons outlined above) to break your code up into encapsulated pieces i.e. they do one thing and can do it in isolation. So unit tests (especially if you write your tests first) force you into thinking about how to break up your code into maintainable pieces, which leaves your application in a good state if you want to make changes in the future (you run the unit tests and can see where things break).
Example
Form application:
You have a form application serving multiple forms. You have a controller for each form. When the user submits the form the data is sent via a proxy to a CRM that stores the information in a database.
If a customer already exists in the CRM you don't want to create duplicates (yes the CRM should handle data cleansing but you want to avoid that where possible). So once the user submits their form data something needs to implement logic that goes something like:
search for the user in the CRM via an API endpoint
if the user exists get the user ID and pass it with the form data to another endpoint
if they don't exist hit another endpoint and create a new user, get the user ID and send it and the form data to associate it with the user
NB: Arguably all of the above should be done by a back-end service but for the sake of example let's go with it.
Your application has multiple forms. If you hardcode the same logic in each controller for each form (yes you should have a controller per form i.e. one per view) then you are repeating yourself multiple times. And writing tests that need to check the controller can do the basics (post data, manage changes to the view) but also test all of that of that logic for each controller.
Or instead write that logic once, put it in a service, write one test for it and inject it wherever you like.
References
Look at the Angular documentation, and look at the patterns that Angular implements and why these are good to follow (Design Patterns - the big ones being modular, dependency injection, factory and singleton).
The biggest problem with controllers is that you don't define the html it works on.
When you use...
<div ng-controller="myController"></div>
... then you have to inject your html in your controller, which is basicly old fashioned jQuery thinking.
When you use...
<div ng-controller="myController">... some html ...</div>
... your directive and your html it works on are defined in different places. Also not what you want.
Using directives forces you to put your piece of html and the code that it needs in the same place. Because the directive also has it's own scope, there will not be any interference with other variables elsewhere in your code. If you need variables from elsewhere you also have to explicitly inject them, which is also a good thing.
The word I use for why this is a good thing is 'atomic' but I'm not sure if this is the right word. Meaning: all the things that should work together are in one file. With templateUrl this isn't exactly true anymore, still the template is defined in the directive.
So in my controllers there is no code that does anything with the dom. Just the bare minimum like some page/view counting code, or connecting API data to the scope, or doing something with $routeParam data. All other code is put in either Services/Factories (business logic) or Directives (dom logic).
BTW: it is possible to define a controller for your directive, but is normally only used for 'inter-directive communication' (so they can share state), but you only use this with directives that always work together (like a tab directive that is repeated inside a tabs directive).
The main reason why you don't write logic in controllers is all $scopes in the controller get garbage collected with $destroy() on route changes. In the ngView directive when a $routeChangeSuccess broadcast is received, there is a function that only keeps $scope for the currently active view, all other $scopes are destroyed.
So for example, if you have a shopping cart app and your business logic is the controller using $scopes, the user will lose the product and all form data already entered on the order page, if they use the back button, etc.

Correct method for using/implementing services

We're trying to lock down the best method for using services and have come up with three possibilities. This is a much smaller example of a much bigger problem but I hope it gets the point accross. The service in these examples is very simple:
.service("markerService", function(){
var self = this;
self.showMarkers = false;
self.toggleMarkers = function(value){
self.showMarkers = angular.isDefined(value) ? !!value : !self.showMarkers;
};
})
And the dom after it's compiled is also very simple:
<div ng-controller="ParentCtrl" class="ng-scope">
<div class="assetsDirective">
<div>
<div class="assetDirective">Hidden Name: 1</div>
<div class="assetDirective">Hidden Name: 2</div>
</div>
</div>
<div class="toolBarDirective">
<div>
<button>Toggle Markers</button>
</div>
</div>
</div>
The options we've come up with are:
Option A: http://jsfiddle.net/nxhn31yv/ (both assets and toolbar have service)
Option B: http://jsfiddle.net/oszop5j4/ (assets has service, parent of toolbar has service and toolbar updates it)
Option C: http://jsfiddle.net/a7nf31d3/ (Parent of all of them has service, passes it down)
The first one injects the server into two sibling controllers and one of them calls a toggle function to update the value stored within that service.
The second option has it injected into two services that have a parent/child relationship. The parent passes the value to one child which can update it (like an ngmodel) while the child controller with the injected service passes it's value onto it's children which use it.
The third option has the parent pass the value all the way through to both siblings, which in turn has the one sibling pass it to it's children as well.
Which of these fall in line with angulars methodology of how to implement services?
Option A is the clear winner for me. It more closely matches the idea of discrete, decoupled web components - the direction in which Angular and web development generally seems to be heading.
For Angular 1.x this means using directives to encapsulate all markup/logic that is bound together. Typically ng-controller in your markup should NOT be required. It should be possible to identify the dependencies (injected or related directives) for any directive by looking ONLY at the declaration of that directive.
Option B and C violate these simple principles and therefore (assuming actual implementations are more complex than these simple examples) are likely to be much more painful in terms of maintenance and debugging.
I would say option A is the best way to do it.
I believe one of the greatest things Angular teaches you is how to modularize your code. Programming in such a way where if you wanted to, you could remove, add any piece anywhere. So with that, you would try to avoid using anything that is dependent upon another piece. Your Option A succeeds in this way.
Of course sometimes it is unavoidable, but I use that as a general rule. It makes things easier to understand and follow, keeps things clean.
edit: It also protects yourself from changes to scope in the future.
According to Pro Javascript Development by Den Odell,
applying a set of methods and properties from one Object directly to another or a prototype of a Class can be a "hack", especially for that developers approaching Angular from Object Oriented Background.
Neverthless, when it is being used carefully, can simplify development and code mainteance.
That is a mixin design pattern, which avoids extensing subclassing and inheritance chains.
Injecting service into encapsulated directives, will help you avoid parent controllers and inheritance.
That is why i choose option A, but use it carefully.
More about mixin.
Another article

Communicate to another, specific controller

I'm a bit lost... I'm quite new to Angular and writing a small app for practicing.
I've two views with a table and each view has it's own controller. The tables use some equal functions, like edit, save... To avoid duplicate code I've put these functions in a seperate controller. Now if in one view e.g. the data in a table-row is edited and saved I want to fire an event to inform the specific controller for that view and send the new object with that event. I already got that running but not sure if it is the right way to do it...
So right now my TableEditController is parent of each table-view-specific Controller and I can send the event with $emit. By doing some research I've read, that you should use a service for the communication, but in the examples $rootScope is used and I think that's not the right way to do it, as I only want to inform the specific controller about the change so that it can do further handling (like saving to database or whatever).
Any thoughts or ideas?
Put your business logic in a service and share the service with you controllers to avoid code duplication.
If you have the same controller methods as well (and a lot of them) , then create a parent controller (or inherit via prototype) with the same logic.
If you want to controllers communicate, you show create parent controller as below
<div ng-controller="parent_controller">
<div ng-controller="child_controller_1"></div>
<div ng-controller="child_controller_2"></div>
</div>
child_controller_1 and child_controller_2 will communicate via parent_controller.

Angular docs: how can one share stateless/stateful code between controllers?

Been reading the Angular.js' Controller docs and stumbled across:
Sharing stateless or stateful code across Controllers — Use angular
services instead.
But this leaves me uncertain. How do one can share a stateless/stateful code between controllers? Or what does the "code" mean here? A model? Besides, controllers do not refer to each other, as far as I understood. Can anyone clear things out for me(others) please? Thanks.
I think what they are referring to might be one of the methods to "persist" data, sharing it between controllers or between route-changes. One way to do that is to put it in your rootScope, another is to use a service. If you define a service like this:
.factory("MyDataObject", function() {
return {};
})
Then MyDataObject will be the same object anywhere you call it, allowing you to save things into it in order to share data, functions and states between controllers (or directives, or other services, etc).
You never know with the Angular documentation, but I would guess that is what they are talking about :)
See for example this answer: Angularjs, passing scope between routes
Here is my view on subject. As angular guys have always tried to explain, scope is not your model. Angular "services" are way to do it, but word service is such and overloaded term. Coming from DDD background, I cannot reconcile word service with a state or statefulness, it just does not make sense to me. What makes more sense is ViewModel or whatever you want to call it. Since I've worked with Silverlight using MVVM pattern, I call them ViewModel. As it is a job of a "Controller" to provide Scope for a View, my controllers have been so far very lean. Bulk of logic is in a ViewModels that get associated with a View through a $scope that controller creates. Does that make sense? So my controller might take a dependency of let's say mySearchViewModel, bulk of the logic is in there and can be shared between controllers, and to associate it with a view you would do something like $scope.vm = mySearchViewModel in mySearchController.

When to use nested controllers instead of services in angularjs?

I just started to use AngularJS, so I'm not an expert.
I have a div that represent the right area of my html view. In that div I have a controller, i.e.
<div class="rightContainer" ng-controller="rightContainerCtrl">...</div>
Inside that div I have a table, a search region, etc. Each region inside that div has its own controllers, it looks like this:
<div class="rightContainer" ng-controller="rightContainerCtrl">
...
<div class="search" ng-controller="searchCtrl">...</div>
...
<div class="table" ng-controller="tableCtrl">...</div>
</div>
the search region for example has its own controller and it is a child of rightContainerCtrl because it needs to alter some content in the parent (rightContainerCtrl), but the rightContainer div is growing and now it's big, and contain several nested controllers.
I think that using this nested controllers it's bad in this context because all the nested controllers share the parent scope, and not all controllers needs to access all the parent scope variables, also all the controllers are "prisoners" of the rightContainerCtrl, so they are highly coupled with their parent controller.
It looks like a God object anti-pattern (God controller in this case), so I think that instead of using nested controllers I can refactor my code to eliminate the rightContainerCtrl controller and use a service instead (like in a facade design pattern), that service then will be used by the controllers instead of sharing scope variables.
but since I'm not an AngularJs expert I'm not sure if I'm right or if it's better to leave this parent controller, maybe I'm missing something, so my question is
When is better to use nested controllers (nested scopes) and when it's better to use services instead in angularjs?
Controller/scope hierarchy should not dictate how data/models are shared in an application. When you think of data sharing in Angular, think dependency injection.
In the video that is referenced in #shaunhusain's answser, Misko states that scope should refer to the model, not be the model -- so don't model/put your data into scopes. Your models/data should normally be in a service.
When writing an Angular app, first think about your models. Put them in services with APIs to get/edit/manipulate the models. Then design your views. Each view should project/use/manipulate some subset of your models. Define a controller for each view that simply glues the needed subset of the models to the view. Make your controllers as thin as possible.
(Naming a controller rightContainerCtrl is also not recommended. The controller should not know about presentation/layout.)
This is 100% judgement call, and should be based on a couple of points.
Using events creates extremely loosely coupled components, they literally do not need to be aware of one another, if you have a situation where some parent controller would alleviate the need to communicate between a bunch of controllers (via services) then it is probably a better solution.
However if you're okay with the controllers each depending on the service (not really a problem) then you could just use the service as a means of communicating changes between the controllers. I've seen tons of arguments against the singleton (of which the service is a flavor, an injected singleton, but singleton nonetheless) I find these arguments to mostly be moot and generally lack a truly elegant and concise solution. If an argument spouts on and on about how when you go from A - D you don't want to take road B but they never seem to offer road C I don't really see the point.
http://www.youtube.com/watch?v=ZhfUv0spHCY&t=30m34s
I couldn't find the exact point in the video, but somewhere at the end here he discusses the usage of controllers vs services (he also reviews bi-directional data binding which will stop you from needing to pollute the global scope so to speak).

Resources