Splitting functionality across controllers that must interact - angularjs

We are using Angularjs and ui-router. We generally have a layout of each page that utilizes views. We have a filter view, sort view, and pagination view; as well as display views that can be swapped in and out.
Logically when changes are made we need to any of theses controllers we need to update the displayData as appropriate. Changes to filter should run the filterMethod, but also need to run sort and then pagination logic afterwards, while changes to sort should run just pagination after, making a clear order of operations for when each controller needs to it's update.
My problem comes when I consider that in some cases we may not want to utilize all 3 controllers. We may want filtering, but not pagination for example.
We are having trouble finding a clean way to make these controllers 'just work', so that we can plug in whichever control we want in uirouter and have them function. The problem is mostly one of scoping. If I do the obvious thing, and have each controller define their own updateData method when changes are made to it, I run into scoping problems if I want them call the next controller's update afterwords. The filter controller can't call sort because the two controllers don't share a scope. I can use broadcasts, but what if I want a filter and a pagination controller, but not a sort? How do I ensure that sort runs before pagination if both are present, but if sort controller doesn't exist pagination knows to run after filter?
I could instead move everything up to my top level controller, and then things just work. However then I end up with a controller that feels like it's doing way to much, It's cleaner to have one controller for each type of control if possible.
We have other approaches we could use, but they feel like their making pretty strong presumptions about our controller scheme. If I later added some fourth controller I would have to modify everything because each controller is really hard coded very explicitly with presumptions about how the other's run.
This seems like a common issue. Is there a a best practice or convenient technology for handling splitting of functionality across controllers?

Hoist the data, not the display logic. There are only two ways to share data between controllers cleanly: a service, and a parent controller.
If what you were sharing was data (eg: displayData), I might suggest a service object, but sounds more like application state (eg: orderBy), so I think these "settings" should live on a parent controller.

Related

Should I call parent directive controller or create service

Let's say there is a "conversation" directive, made of "message" directives.
The message directive has a button to delete it.
The conversation controller has a method to remove a message from its list.
In Angular documentation, they say we can use "require" to access a parent controller from a directive controller.
And here, the answer suggests to use require too, and to create a service only if you can't use require because the 2 directives are not related.
Isn't it a bad practice to call a controller from another controller ?
I thought service was typically used to share information between controllers.
Don't you think it would be better to create a service with the deleteMessage method, and inject this service in the message controller ?
I'm personally not a fan of calling a function on the parent or root or stuff like that, as its coupled in a kind of implicit way. I prefer to use a service as you mentioned or injecting the concrete function(s) into your directive.
Below are some pros and cons for both approaches. Please note they are based on my personal experience (and I'm not an expert).
Services
Services are singletons, meaning there is only one instance in your whole application. This can have several advantages, if you need them:
it's always there (you don't need to create it explicitly)
it can cache data which can be usefull, e.g. to maintain the state of a view/page
it can be used to exchange data between different components
it can easily be injected where ever you need it
This can also lead to some disadvantages though:
if different components share the same service, they might mess up each others data
components (i.e. directives) will depend on that service, leaving the user of the directive no flexibility in how he wants to use that directive (e.g. what should happen when a user clicks a button). Meaning that basically the concrete functionality is "hard-coded" in to the directive.
I personally like to use services, if they have a direct link to the directive (and to nothing else) or do not store any state, for example if they only contain simple helper functions.
If in your application you have one conversation list, then I would consider implementing that in a service that exposes functions like addMessage, deleteMessage, sortBy, etc.. Then it can be injected in to any component which needs access to that (central) list.
Passing functions as arguments
On the other hand, if you have a message-directive, you might want to use it for other kind of messages as well..? That is messages which are not from the conversation but maybe from a mailbox (just a stupid example ;)). Then I wouldn't couple the service and the directive. You could inject the deleteMessage-function, which in one case would remove it from the conversation and in the other case remove it from the mailbox.
I think both approaches are valid, but always depend on the scenario and how the components are shared/reused over your application.
The second approach might be nicer from a "coupling"-perspective, but might get complicated in case of lots of parameters and when passing parameters over several levels of components (e.g. to child-child-child directives).
The first approach is easy to implment and can have several advantages, but results in the component being directly coupled to that service, lowering its reusability.
Do you really need to put the delete button in the message directive ?
Since the delete button is to suppress the message from the conversation controller list, I'd rather put the button in the conversation view.
That being said, if you really want to have the delete button on the messagedirective, I would simply pass the delete method as an argument in your directive. When doing this you don't have to worry about which controller defined the function, you simply call it from your child directive (and if you want the parent controller to execute it, simply pass a binded function to your directive).

Putting presentation data in angular controller?

Got a webapp I'm building in Angular.
This app walks a user to authorizing accounts, presenting specific instructions based on the users choices.
I've implemented this as HTML that is shown or hidden based on values in the model, so for 3 different choices, I have 3 different sets of HTML sections that are similar but with different texts.
In the spirit of DRY, I should instead have one set of HTML sections, and instead switch the text based on the values of the model. This means putting text data inside the model, including small snippets of markup, like anchor and strong tags.
Does putting presentation data into the controller violate the principals of Angular?
There are quite a number of options to avoid repeating code depending on what you are looking to do. The following ideas are things I would consider and use when they make sense (I placed these from simple to complex, so you probably can skip the first few):
ng-bind -- Put it on a span/div. Simple & works to bind the model to the display
ng-switch, ng-if, ng-hide, ng-show -- Work to conditionally show an element
custom directive -- use this when you want to alter the behavior of an element or if you want to alter the dom based on a template. If you use "ng-transclude" the contents of the element you template will be included in the result. This can be very elegant but it works best when you have a single format. I can provide examples but angular's documentation also has excellent examples.
service -- I generally use this just to provide data only. This could be via a restful api and $resource or via $http calls. Either way, I wouldn't recommend doing much more than load/save data here.
$scope method -- In other words:
$scope.myMethod = function(x,y,z) { /* code making decisions based on the model */ }
Then you can call this method from one of the previous either via a prebuilt directive (ng-show, etc) or via a custom directive that manipulates the dom for how you expect it to be.
ng-bind-html -- Last option I know to suggest is to use this directive combined with the $sce service to bind whatever you want to the DOM. If you are binding something with angular code in it - make sure to use the $compile service as well. I generally don't favor this approach except as a last resort because it makes it harder to find where elements in the DOM are coming from and it can make debugging + testing a real pain. That said, these tools wouldn't exist if people didn't need them.
I'm sure that this isn't complete and maybe others have suggestions but that is where I would start. Best of luck!
I would put the text data in a separate angular service. This article gives an example: http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/
Then if you decided at some point to move it to some other storage, your service would still be the single access point for the rest of the app.

Is it an antipattern to use angular's $watch in a controller?

In my never ending quest to do things the "proper" angular way, I have been reading a lot about how to have controllers observe the changes in models held in angular services.
Some sites say using a $watch on a controller is categorically wrong:
DON'T use $watch in a controller. It's hard to test and completely unnecessary in almost every case. Use a method on the scope to update the value(s) the watch was changing instead.
Others seem fine with it as long as you clean up after yourself:
The $watch function itself returns a function which will unbind the $watch when called. So, when the $watch is no longer needed, we simply call the function returned by $watch.
There are SO questions and other reputable sites that seem to say right out that using a $watch in a controller is a great way to notice changes in an angular-service-maintained model.
The https://github.com/angular/angular.js/wiki/Best-Practices site, which I think we can give a bit more weight to, says outright that $scope.$watch should replace the need for events. However, for complex SPA's that are handling upwards of 100 models and REST endpoints, choosing to use $watch to avoid events with $broadcast/$emit could end up with lots of watches. On the other hand, if we don't use $watch, for non-trivial apps we end up tons of event spaghetti.
Is this a lose/lose situation? Is it a false choice between events and watches? I know you can use the 2-way binding for many situations, but sometimes you just need a way to listen for changes.
EDIT
Ilan Frumer's comment made me rethink what I'm asking, so perhaps instead of just asking whether it is subjectively good/bad to use a $watch in a controller, let me put the questions this way:
Which implementation is likely to create a performance bottleneck first? Having controllers listen for events (which had to have been broadcast/emitted), or setting up $watch-es in controllers. Remember, large-scale app.
Which implementation creates a maintenance headache first: $watch-es or events? Arguably there is a coupling (tight/loose) either way... event watchers need to know what to listen for, and $watch-es on external values (like MyDataService.getAccountNumber()) both need to know about things happening outside their $scope.
** EDIT over a year later **
Angular has changed / improved a lot since I asked this question, but I still get +1's on it, so I thought I would mention that in looking at the angular team's code, I see a pattern when it comes to watchers in controllers (or directives where there is a scope that gets destroyed):
$scope.$on('$destroy', $scope.$watch('scopeVariable', functionIWantToCall));
What this does it take what the $watch function returns - a function that can be called to deregister the watcher - and give that to the event handler for when the controller is destroyed. This automatically cleans up the watcher.
Whether watches in controllers are code smell or not, if you use them, I believe the angular team's use of this pattern should serve as a strong recommendation for how to use them.
Thanks!
I use both, because honestly, I view them as different tools for different problems.
I'll give an example from an application that I built. I had a complex WebSocket Service that received dynamic data models from a web-socket server. The service itself doesn't care what the model looks like, but, of course, the controller sure does.
When the controller is initiated, it set up a $watch on the service data object so that it knew when it's particular data object had arrived (like waiting for Service.data.foo to exist. As soon as that model came into existence, it was able to bind to it and crate a two-way data-bind to it, the watch became obsolete, and it was destroyed.
On the other side, the service was responsible for broadcasting certain events as well, because sometimes the client would receive literal commands from the server. For instance, the Server might request that the client send some metadata that was stored in the '$rootScope' throughout the application. an .on() was set up in the $rootScope during module.run() step to listen for those commands from the server, gather needed information from other services, and call the WebSocket service back to send the data as requested. Alternatively, if I had done this using a $watch(), I would have needed to set up some sort of arbitrary variable for it to watch, like metadataRequests which I would need to increment every time I receive a request. A broadcast achieves the same thing without having to live in permanent memory, like our variable would.
Essentially, I use a $watch() when there is a specific value that I want to see change (especially if I need to know the before-and-after values), and I use events if there are more high-level conditions that have been met that the controllers need to know about.
With regards to performance, I couldn't tell you which one is going to bottleneck first, but I feel like thinking of it this way will let you use the strengths of each feature where they are strongest. For instance, if you use $on() instead of $watch() to look for changes in data, you will not have access to the values before and after the change, which could limit what you are trying to do.
All that two-way data-binding is, is a $watch on whatever scope property you give to ng-model, which has a controller that allows other directives like input, and form to sync the ng-model value to render the view on a change. Which is detected by their registration of events in the DOM.
In essence,ng-model's $watch compares the value in the model, to the value it has internally. The value it has internally is set by supporting directives (input,form etc).
IMHO The only "events" you should react to in an angular application are user created (ie DOM events). These are solved with directives on the DOM and ng-model linking to the ..model
Also naturally there is async, for which angular provides $q for which the callbacks invoke a $digest.
As for performance, it says it really well in the angular docs. Its run on every $digest. So make it fast. Whats every $digest? Angular traverses all of your active scopes. Each scope has watches. which it executes. and performs comparisons in those. If there are diffs, it will run again. (the next loop around) Its not that simple because its optimized but all of your "angular code" executes in this $digest loop. A lot of directives might invoke a digest with scope.$apply(...) That will cause watches of whatever value they changed to notice and do their thing.
So your original question. Is it an anti-pattern? Absolutely not if you know how to use it. Though I'd just use ng-model. Just because it has had 1.2.10+ iterations of pretty smart people working on it... All of the other 'reactive-ness' of your app can be handled by $q, $timeout and the like.
I think they all have their proper place and, for me, it would be difficult to say stop using one for the others.
Data binding should always be used to keep your data model in sync with changes from the view. I think we can all agree on that.
I think using a watch on a controller to trigger some action based on a data change is useful. Like watching a complex data model to calculate a running total for an invoice. Or watching a model to trigger it as dirty.
I have used broadcast/emit/on when sending a message or an indication of some change from one scope to another that may be several layers away. I have created a custom directive where a broadcast event has been used as a hook to take some action in a controller.

Angularjs: Find all instances of a directive

I'm trying to create a directive to allow the user to navigate the page with arrow keys by section. But I also want to be able to have those sections be scattered around the dom, and to have this not break when stuff gets added and removed. I can think of several ways to do this, but none of them are satisfactory:
Create a directive with a controller that lets other directives register themselves (and unregister on $destroy). But this will be out of order if I add something in the middle later. Also, I've tried writing it this way, and it seems like way more code than necessary.
Whenever the user hits an arrow key, make an empty array, and $broadcast an event, with a callback for directives to register themselves on that list. Then, once that list is full, advance or go backwards on it. They (should?) come back in the order they're in on the DOM, but I'm not sure since this way seems crazy and hackish.
Mark things that are 'tabbable' with css, and write this the simple way in jquery, something like this: On a new click event, var all = $('.tabbable'), and then do the obvious with that. But I really don't want to do it that way, because it's not 'the angular' way. Not out of some sense of purity, but because I'm building this as part of a larger library of widgets, and I want this functionality to be accessibly to them.
So, is there any way for me to get the scopes of all directives of a certain type, without resorting to weird hacks, or spreading the logic out all over the place?
This is a good question. +1
First, finding all directives or nodes by type goes against the Angular way. The View is the official record in AngularJS, so directives should say what they do and do what they say. Coding some process somewhere to scan for DOM nodes and act accordingly is problematic for several reasons, not the least of which are separation of concerns and testability.
I'm glad to see you're looking at other options, but I agree that the other options you provided are sub-optimal for the very reasons you mentioned. But I have one more. This is one that I've used for a different application, but that required knowledge of scattered DOM nodes.
First, we create a service to manage the state of this component. It's simple. Let's call it SectionsService. Next, we create a directive to register sections. Let's call that section for simplicity. The section directive registers the DOM node's ID (maybe created programmatically to ensure uniqueness) with the SectionsService during its linking phase. Since the DOM is processed (mostly) in order, the nodes added to the SectionsService will also be in order. So the DOM looks something like this (irrelevant stuff omitted):
<div section>...</div>
<!-- other stuff -->
<div section>...</div>
<!-- other stuff -->
<!-- etc. -->
(Though out of scope here, it would not be very difficult to program it in such a way that the order wouldn't matter, but it'd be based on specifics of your app that I don't know.)
Next, you create your triggers, like an arrow key handler. On these events, you simply tell the SectionService to go to the previous/next node in the list. AngularJS comes with a service called $anchorScroll that can be used to emulate the browser's hash-based positioning we're familiar with. You could obviously also use a jQuery plugin to animate the scrolling if you wanted to.
And that's it! A very simply directive, a fairly simple service, and whatever trigger(s) you need. All told, I'd guess less than 100 lines of code including tests. All components are decoupled and easily testable, but still really quite simple. The view remains The Truth. The Angular Way is preserved.
And there was much rejoicing.
I hope this sets you on the right direction, but of course feel free to ask a follow-up question. We can also talk code specifics too if you'd like; as I said, they wouldn't be very complicated.
AngularJS services are singletons and can be required via dependency injection. You could have your directives require a state manager service and call incrementers/decrementers.
Alternatively, a little easier but more brittle, you could keep an array in $rootScope. It's more idiomatic "angular" (but not by much) than a jquery selector global, but probably not the best route if you're building a widget library.

How do I create a new, uh, thing in Angularjs?

I think the vagueness of the question is part of the problem, so my real first question is, what do you call, in Angular, the thing.
The thing I'm trying to name is the view plus the controller, over the model of a single object. I don't even know what to call it. For things I know ahead of time I'm going to need, I've been creating directives, but what do you call one instance of the thing that a directive creates?
I have several situations where all of a sudden (in response to some external event), I have a new object in the model and I want to show it on the screen. Angular seems to want me to list all the possible views ab initio in their parent view, but that isn't really reasonable in my case. How, for example, would I list all the pop-ups and tool-tips and other stuff.
I'm down in some little edge case, deep in the controller code, and it needs to add something to the current view. What's the accepted practice.
Incidentally, the $route/ng-view is one case of exactly this. The view containing the ng-view, and the ng-view DIV itself, have no idea what the $route module is going to put in the ng-view. I need the more general case of this strategy.
EDIT
People keep asking for an example. How about this: I'm making an equipment-requisition app. When a user asks that one of the 1000 different type of equipment be sent to him, I need to display a pop-up that gathers addition information specific to that type. If he asks for a screwdriver, the pop-up will ask about blade size, neck length, and handle composition; if he asks for an airplane, it will be a wizard ask him about engine size, fuel tanks, seating arrangement. All the app knows on start-up is the list of all equipment types, and the name of the UI element that gathers all subsequent information about each particular type.
I'm down in some little edge case, deep in the controller code, and it needs to add something to the current view. What's the accepted practice.
Somewhere, you need to define all of the views you'll need -- e.g., all of the equipment popups. You could put each view into a separate file and use ng-include to dynamically pull in the one you currently need to display. Define a property on your $scope (e.g., $scope.equipmentTypeViewUrl), then
<div ng-include src="equipmentTypeViewUrl"></div>
Since ng-view can only appear once per page, ng-include is probably not what you need to use if you need multiple levels of routing.
See also
create a single html view for multiple partial views in angularjs
https://groups.google.com/forum/#!topic/angular/xIIyGpW8KUk/discussion
App design using Angular js
Client Side Template with view per role
I think the problem is that you think that you need to create the "thing" in controller, but actually you don't. The way two-way data binding works is that you change some attribute value, and the view changes based on that. I've never seen a use case where that's not enough, pop-ups and tooltips notwithstanding.
However, if you really must have the controller show something, you could utilize angular's events to do that. You would need two parts: a directive responsible for showing stuff (modifying DOM), and the controller. The controller would $broadcast an event with some parameters, and the directive would listen to those events using $on and react accordingly.
I'd just make sure I had some useful code coming in as the model...
<div class="row" ng-repeat="attribute in attributes">
<div class="widget" ng-repeat="input in attribute.inputs">
<input type="{{input.type}}" ng-model="input.value" />
</div>
</div>
I'm extremely limited in my knowledge, but all I know is if you have a definite structure to your model you can build a view that reacts to it dynamically.
If all of those things are related to your original object (properties or in some way other) you could loop through the data, display the properties and if need use the keys and filters for a label. Imho it's not really an angular question, more one if your data structure. If you have a good data structure you could use a service for creating a related data-object.
For a related popup, you can use a directive and even process the model data there (only recommended if it has a consistent structure).
If you dislike this approach, you can process the data directly in the template.
But without more specific details, there will be no definite answer.

Resources