Directives are, essentially, the most powerful building blocks we have in Angular, yet for beginners they are incredibly easy to get messed up.
Here are some guidelines that I’m pleased with, and that can help new comers. I would love any feedback from fellow coders!
My assumptions: The main problems I have in maintaining “magical” code are lack of explicitness and traceability. Explicitness means I would rather write a bit more code in order for it to be clearer what my intention was and what exactly I’m trying to use. Traceability is making an effort that from any piece of code it would be clear what it depends on or what depends on it. Even with thoroughly unit-tested code I believe the following extra steps are necessary.
Favor isolated-scope directives
I pretty quickly realized that what I should use about 90% of the time are isolated directives. These are directives that have a clean slate as a scope. Their scope is clear except for those bindings which are explicitly made in the directive’s definition:
1 2 3 4 5 6 7 8 9
The major win here is that never again will I be stuck with opening a directive and sifting through the different properties referenced, trying to understand what, if any, the directive is using from its parent. This means I can more safely and quickly refactor, change and delete code.
Explicitly passing dependencies with require
Let’s start with an example. Here is the screen for editing mailboxes on the iPhone:
This might have been written in Angular like so:
1 2 3 4 5 6
In the above example
editContext is wired through the
mailboxLine directive even though it doesn’t care about it at all, just so it can pass it along to
mailboxEdit. Once these extra wirings start getting popular in your app, sometimes across several levels deep just to pass some object, you won’t like it. Take my word for it.
What are you to do?
Requires to the rescue!
Angular’s directives have an amazing ability, though not as widely spread as it should be. A directive can require that other directives will be present either on the element it is placed, or in one of its parents:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
As you can see above, using
require means our directive can get a parent’s controller and reference it (
mailboxesCtrl), as specified in the Angular docs here. The simpler HTML would now be:
1 2 3 4 5 6
This is awesome in so many ways:
- No annoying wiring of things all the way down
requirewill throw an exception if the requirement fails, making it impossible to wire wrong
- The nested directive explicitly tells us what it depends upon
- The parent directive explicitly defines an exposed API
I find this way makes maintaining a large system a lot easier and more straightforward.