AngularJS Best Practices
Miško Hevery – the father of AngularJS – gave a very interesting presentation on AngularJS best practices in December of last year. This presentation is available on YouTube and the actual slides are on Google Docs.
I’m going to cover a few of the points he covered in his presentation and give my take on them.
Angular suggests that you use a certain directory structure, which you can create either using the Angular-seed project or the new Yeoman tool. (Angular-seed was actually created before Yeoman was around so you’re probably better off using Yeoman now)
This is called ‘Convention over Configuration’ – a concept you will hear a lot if you delve into Rails – although this doesn’t really apply in Angular as far as I can tell. For example, you still need to manually include all your controllers in an index file (or use something like RequireJS) – if we were in a ‘Convention over Configuration’ world I would expect a controller to be picked up automatically if I put it in the correct folder.
In any case, it’s still a good idea to stick to the conventions simply to make it easier for a developer to look at your codebase and know where files should live. The default conventions also dictate that:
- Everything that gets deployed is in the app directory
- Everything outside of the app directory is for development
Dependency Injection (DI) is built-in with Angular so not only is it best practice to use Dependency Injection it’s absolutely necessary (except perhaps for the most rudimentary of applications). It becomes especially important when we delve into unit testing – you can use dependency injection to substitute actual dependencies for mock dependencies to isolate behavior.
I must confess I’m not 100% sure why we need Dependency Injection in Angular. Keep in mind that that Dependency Injection is not a requirement for testability – for example, Rails is perfectly testable and contains no Dependency Injection. I find it rather odd to use Dependency Injection in a dynamic language, but in any case – you definitely should use it in Angular.
Hiding Flashes of Unstyled Content
When your page first loads you will often see the angular code in the view before Angular is loaded (especially on older browsers) – it will looks something like this (for a moment):
We have a few options in order to get around this. One is to use ng-cloak – simply create a style which says
We usually put this on the body tag, which means the page will only be shown once Angular is loaded. This does means there is a slight delay before anything is rendered.
An alternative is to use ng-bind, so instead of:
What’s rather neat about this approach is that we can set a default value to display, before the browser has loaded Angular.
Keep in mind this is only necessary on your first page (typically index.html) if you’re building a single-page application (SPA).
Separate Business and Presentation Logic
One of the ideas behind MVC is the separation of business and presentation logic. Angular helps to accomplish this with the concepts of controllers and services.
Controllers should contain view logic, but shouldn’t actually reference the DOM (referencing the DOM should only be done through directives). The controller should answer questions such as:
- What should happen when the user clicks this button?
- Where do I get the data to display?
Services on the other hand should not contain view logic – services should contain logic independent of the view.
For example, if we are building an email management app, we might have the ability to delete an email. You might even be able to delete an email from multiple places – for example, in a list view as well as when viewing an individual email. So 2 separate controllers might handle the event of the user clicking the ‘delete email’ button, but the logic for deleting an email should actually reside in a service. Likewise, if we need to display a list of emails or an individual email it is the responsibility of the controller to ask a service for this data.
Angular allows the controller to interact with the view through the scope object. While this is a clean separation, it’s also pretty easy to make the scope object very messy. There are two general guidelines to stick to with regards to the scope:
- Treat the scope as read-only in views
- Treat the scope as write-only in controllers
These guidelines are very similar to the way you tend to interact with the view in a Rails application – you send variables to the view by writing to local fields – which Rails then magically links into the view – but you would very rarely read from local fields in the controller. In the view you can only read from fields assigned by the controller – there is no way to communicate back to the controller except through an action.
I have found these 2 guidelines to be most useful in my current application. It’s still difficult to stick with these guidelines 100% of time, but treating any exception as a code smell is a great way to go.
There’s quite a few topics I haven’t covered here – the actual presentation I mentioned covers deployment, minification, etc, but those issues aren’t really unique to Angular so I haven’t covered them. Do take a look at the original presentation – it’s definitely worth your time. Happy coding.