/Angular /components /directives /JavaScript

Why you would want to use Angular 1.x?

You’re maybe wondering why you would need such an old framework in 2017. There are several other new frameworks which have many more benefits compared to Angular 1.x. They were developed using modern concepts, and also have good documentation and vast community support. So if you want to start a new project, Angular 4, React or Vue.js would probably be the best choice. But there is a huge number of projects (mostly enterprise) which already exist and can’t be rewritten every time a new published framework or library appears. Todd Motto has fully described this topic in his blog post here.

So who should read this article then?

It would be useful for you if you:

are working on a big web-based project;

have a mess in an Angular 1.x codebase (which you may not even be aware of);

want to improve the maintainability and scalability without the cost of switching to a new framework.

Why is Angular 1.x not so good?

Nowadays, the Angular community has come up with many great ideas on how to make web applications fast and reliable built upon clean architecture. Most of them were inspired by functional programming, utilizing composition, immutability, reactive programming, components, and etc. But a few years ago the situation was completely different. You had more breathing room for improvisation and could shoot yourself in the foot at every step. But with Angular, you could write every unit of the application (controller, service, provider or even config) as a function with a list of dependencies.

For small applications like ‘Hello World!’ it’s very elegant and graceful. But as the application grows, the codebase becomes increasingly messy. For instance, one $scope object would keep all the data in the application. Or, you would have a lot of business logic in the html page. Or, your minified bundle wouldn’t work after implicit injection. You would be lucky if you could find and fix a small bug hidden behind the mountain of $scope, $rootScope, $timeout, $watch, $apply and other cool features which Angular provides you. To overcome all these difficulties, you have to follow certain rules and standards. I'd like to suggest you some of them below.

Directive as a component

Why use component-based architecture

These days, you can hear about components from all over the place. React, Angular 4, Vue. All of them implement this model because it is quite useful to split your page into several small parts. But this idea has existed for a long time. ASP.NET WebForms, WinForms and WPF use User Controls, Android and Xamarin use Fragments, IOS uses View Controllers, Java uses Swing Components, and etc. Angular 1.x carried that further by using directives. Most developers prefer to use them just as extended html tags with certain logic. Reusable components from libraries like Bootstrap, Semantic, Material Design - datepickers, tabs, calendars - are good examples of this use case. But why not utilize all the possibilities which directives provide you?

Consider the following:

<my-menu></my-menu> <user-list users="vm.users" on-select="vm.onSelect(user)"></user-list> <user-details user="vm.selectedUser" can-save=”true” on-save="vm.save(user)"></user-details> 1 2 3 4 5 < my - menu > < / my - menu > < user - list users = "vm.users" on - select = "vm.onSelect(user)" > < / user - list > < user - details user = "vm.selectedUser" can - save =” true ” on - save = "vm.save(user)" > < / user - details >

These directives encapsulate some part of the application and can be reused anywhere on the page. Furthermore, they isolate logic and you don't have to worry about their implementation (it could be hundreds of lines of code). On this level of abstraction, you can manage bigger chunks of the application rather than html tags. The next question is: how can you split a page into several parts?

How to split the whole page into directives

I can suggest two ways - by model entities and by parts of your page. You can break up your page into parts: navigation bar, breadcrumbs, main content, and details. Or you can distinguish by domain model: products/product, customers/customer, accounts, sales, and etc. Eventually, you can mix both options together, but first you should select a main strategy.

Each part should contain some inner behaviour and points of interaction with the rest of the application. These parts are independent and interact with each other through their interfaces. It’s like Legos. You just connect bricks the way you want and it works. Moving forward, you will be able to substitute some parts without affecting others. Moreover, you will fix bugs faster because you're almost positive where to look for them. All you need to do is describe a clear interface for the component.

Pros of component-based architecture

From the paragraph above you may conclude that using component-based architecture makes it easy to maintain an application as well as scale components and remove components. If you see that your component is growing, you can simply split it into several parts. Or if you see that some component is actually related to another one, you can combine them. You will do all these manipulations without affecting the component’s neighbors because all of them communicate with each other only through an interface.

If you’re familiar with functional programming, you know how easy is it to manipulate small functions and replace or scale them. If not, I recommend you read these series of posts about FP in JavaScript from Eric Elliott.

Cons of component-based architecture

Of course, this approach has its disadvantages. First, developing components takes big effort. You have to think twice about a component's interface - what data it needs and which methods it calls. Also it takes time to wrap each part of the application behavior in a component. Sometimes you also have to adapt parents or siblings to use a new component. Finally, you can end up with tens or even hundreds of components which you should know and be able to navigate between.

Therefore, try to find some kind of trade-off. Don’t wrap every html tag, take care of the component’s structure and look at the best practices from the community. Now let’s look at several code smells and how to deal with them.

Isolate scope

Create separate scope for each directive

Dan Abramov, author of Redux, React core team member, described two kinds of components: Stateful and Pure. I recommend you read the whole article, but in short the idea is that a stateful component knows about other components around it (parent and children) and a pure component interacts with others through its own interface. I prefer to have as many pure components as possible. This approach allows you to:

Substitute a component without affecting other components;

Reuse your component throughout the app;

Focus on a small piece of code when you’re fixing a bug.

Let’s get back to the example we had above:

<user-details user="vm.selectedUser" can-save="true" on-save="vm.save(user)"></user-details> 1 < user - details user = "vm.selectedUser" can - save = "true" on - save = "vm.save(user)" > < / user - details >

It doesn’t matter where you put this component; it only requires a user as a parameter. To create an isolated scope in Angular 1.x.x, all you need is to create a scope object in your directive function:

app.directive('userDetails', function() { return { templateUrl: 'address/to/template.html', scope: { user: '=', // two-way binding canSave: '@', // one-way binding onSave: '&' // bind function } }; }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 app . directive ( 'userDetails' , function ( ) { return { templateUrl : 'address/to/template.html' , scope : { user : '=' , // two-way binding canSave : '@' , // one-way binding onSave : '&' // bind function } } ; } ) ;

I don’t want to stop on details about scope binding. What is more important is the strict interface which separates this component from the rest of the application. If you have a chance to use Angular 1.5.x, it would be better to choose a new function component instead of a directive. It has several built-in features that allow you to write a more structured and isolated component:

app.component('userDetails', { bindings: { User: '<', canSave: '@', onSave: '&' }, templateUrl: 'address/to/template.html', controller: function() { var vm = this; // to get this variable // in html template use $ctrl vm.$onInit = function() { // you can write all initialization here, angular call // this method when component is ready to use } vm.$onChanges = function(changes) { // angular call this method whenever // one-way binding changes occur } } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 app . component ( 'userDetails' , { bindings : { User : '<' , canSave : '@' , onSave : '&' } , templateUrl : 'address/to/template.html' , controller : function ( ) { var vm = this ; // to get this variable // in html template use $ctrl vm . $ onInit = function ( ) { // you can write all initialization here, angular call // this method when component is ready to use } vm . $ onChanges = function ( changes ) { // angular call this method whenever // one-way binding changes occur } } } ) ;

If you’re already working on a big project, you don’t need to rewrite the whole codebase. You can separate each component step by step without breaking other parts. If you have a lot of relationships between elements of your application, you can start from the big ones. For example, separate navigation bar from content, then separate list of items from details, and so on.

Extract the directive’s interface

In the previous paragraph, I mentioned strict interface. So what is it? Strict interface is all the dependencies of your component. If you decide to isolate a scope in the directive, you’ll quickly find a problem - tight coupling between parent and child components. That’s what we need: every object or field which comes from a parent we can put into a scope. The same applies to methods - you probably use a lot of parent’s methods in your component so you have to decide to either get rid off them or put them into the scope.

I strongly recommend the second option. It will be ok even if your directive has ten attributes. In this case, you don’t break an application and don’t lose important logic. At worst, you can always remove useless properties. Then you need to decide which properties will be set up only once and which will change periodically. For those which change from time to time, you have to create watchers. You’ll find more details about watchers below, but in short, you should avoid using $watch at all costs.

Use a controller instead of a link

After creating a wrapper, you need to put the code for your component somewhere. There are two options: a link function or a controller. If you choose the link function, you will probably have trouble with the order of rendering of your parent and children components. I recommend you use a controller instead of a link because:

It’s more common for Angular to put viewmodel behaviour into a controller and use ‘controller as’ with view and controller pairings ;

It’s more testable;

You won’t have trouble with the order which directives render.

Let’s write some code:

app.directive('userDetails', function() { return { templateUrl: 'address/to/template.html', scope: { user: '=', // two-way binding canSave: '@', // one-way binding onSave: '&' // bind bunction }, controller: ['$scope', function($scope) { var vm = this; vm.save = save; activate(); function activate() { // put here some behavior console.log(vm.user); $scope.$watch(function() { return vm.user; }, function(newValue, oldValue) { // do something }); } function save() { // prepare before saving vm.onSave({user: vm.user}); } }], controllerAs: 'userDetailsCtrl', bindToController: true // don't forget to set this option }; }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 app . directive ( 'userDetails' , function ( ) { return { templateUrl : 'address/to/template.html' , scope : { user : '=' , // two-way binding canSave : '@' , // one-way binding onSave : '&' // bind bunction } , controller : [ '$scope' , function ( $ scope ) { var vm = this ; vm . save = save ; activate ( ) ; function activate ( ) { // put here some behavior console . log ( vm . user ) ; $ scope . $ watch ( function ( ) { return vm . user ; } , function ( newValue , oldValue ) { // do something } ) ; } function save ( ) { // prepare before saving vm . onSave ( { user : vm . user } ) ; } } ] , controllerAs : 'userDetailsCtrl' , bindToController : true // don't forget to set this option } ; } ) ;

Notice that I put a new object with a user as a property into the onSave method. And don’t forget to set bindToController to true - otherwise your scope won’t be connected to vm. Now the directive is isolated from the whole application and you can change it inside a controller as you want and not be afraid of breaking anything. This freedom, however, allows you to litter the controller very quickly. Hence, the next section is all about messes in your component.

Don’t clutter up your controller

Avoid $scope

After you isolated your scope, you’re one step closer to clearing your component. But it’s not enough. The next step is to keep the controller clean inside the component. The most common mistake is using the $scope object everywhere. It’s quite easy to add every model and method to the $scope and then use it on the view. So why is it bad? There are two reasons:

You don’t really know which scope you are using. Consider the next example - you have an outer directive user-list which contains inner directive user-details. If you see $scope in user-details controller, you can’t say if it is the current scope or the scope from the user-list controller. You need to check if the directive has an isolated scope. If you use this, you always know where you are. Now let’s take a look at the view of user-details:

<input type="text" ng-model="name"></input> 1 < input type = "text" ng - model = "name" > < / input >

You don’t know if the name comes from user-details or from user-list. Moreover, you can accidentally override the existing property. It would be better to have an alias for each component:

<input type="text" ng-model="userDetailCtr.name"></input> 1 < input type = "text" ng - model = "userDetailCtr.name" > < / input >

Now you are 100% sure which scope you are using.

It’s much easier for unit testing if you separate your logic from system dependencies like $scope. In your test case, you can replace all the dependencies to whatever you want but the logic will still be inside the controller.

All you need is to use controllerAs when you bind your controller to the view. It could be in the template or in $routerConfig (ui-router also supports this technique).

<div ng-controller="UserDetailsController as userDetailsCtrl"></div> 1 < div ng - controller = "UserDetailsController as userDetailsCtrl" > < / div >

Avoid $watch

Every time you use $watch you lose control of the property. You rely on the digest loop. But you don’t really know when the digest loop calls your callback. Also, it’s quite easy to create a circular dependency from one property to another when you create an infinite loop between two watchers. Finally, it’s hard to test a controller which has a lot of watch methods. I suggest you use $watch only for arguments of your directive. For example:

<user-details user="vm.currentUser"></user-details> 1 < user - details user = "vm.currentUser" > < / user - details >

In this case, if you need to change the layout inside the directive when currentUser is changed you have to use $watch. Otherwise try to avoid $watch entirely.

function activate() { $scope.$watch(function() { return vm.user; }, function(newValue, oldValue) { if (newValue) { // update fields related to vm.user // _loadUserDetails(vm.user.id); } }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function activate ( ) { $ scope . $ watch ( function ( ) { return vm . user ; } , function ( newValue , oldValue ) { if ( newValue ) { // update fields related to vm.user // _loadUserDetails(vm.user.id); } } ) ; }

Avoid $event

Another option to track when a property is changed is to send an event. Using events is also a bad practice. When you subscribe to an event, you create a tight coupling between components, so they can’t live separately anymore. Also, it’s hard to test a controller which relies on events. Finally, you lose control of the application flow again. So avoid local events if you want to learn if some property is changed. But you can use global events which broadcast to your whole application when something happens. For example, when a user is logged in/logged out, a company changed or an order is accepted. Global events help you avoid throwing some properties through components to notify their children.

All three of these rules support Pure components. They are clean, scalable, and self-sufficient. With Pure components, you can build your application from small parts combined in a big solution. On the other hand, you need to somehow manage the data for components. Which we’ll talk about in the next section dedicated to services.

Don’t clutter up your service

Extract the service’s interface

It’s a good practice to keep all logic not related to UI in services. Once written and tested, a service can be used in any controller you wish. Also, when you test your controller, it’s easy to replace a service with a mock so you don’t need to send http requests or go to storage in your test cases. If you move your business logic to a service, you’ll quickly find that it would be hard to navigate through tons of methods which may consist of hundreds of rows. Also, it would be difficult to add or remove some of them. So there is one simple rule to make your service easier for future changes - separate the function declaration from function implementation. Let’s look at this example:

app.factory('userService', function() { var service = { getAll: getAll, getById: getById, updateUser: updateUser, removeUser: removeUser }; function getAll() { /* implementation */} function getById() { /* implementation */} function updateUser() { /* implementation */} function removeUser() { /* implementation */} return service; }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 app . factory ( 'userService' , function ( ) { var service = { getAll : getAll , getById : getById , updateUser : updateUser , removeUser : removeUser } ; function getAll ( ) { /* implementation */ } function getById ( ) { /* implementation */ } function updateUser ( ) { /* implementation */ } function removeUser ( ) { /* implementation */ } return service ; } ) ;

There are two important things you have to notice. First, the function declaration is seperated from implementation. It helps you see what the service does and the browser will help you if you forget to write an implementation for the method. Second, declare an object with methods at the top of the service function. Later, it’ll be much easier to navigate when you open the file and see all the functions in one place.

Use promises instead of callbacks

Oftentimes you send HTTP requests from services. There are a lot of examples where people put a callback in the service method and then call it in the $http service:

function getAll(successCallback, failureCallback) { $http.get('someurl.com').then(successCallback).catch(failureCallback); } 1 2 3 4 function getAll ( successCallback , failureCallback ) { $ http . get ( 'someurl.com' ) . then ( successCallback ) . catch ( failureCallback ) ; }

I think that’s because promises were a third-party feature before and people didn’t want to get one more library and dig into it. At the moment, promises are a native feature in most modern browsers, so I strongly recommend you use promise-based flow instead of callback hell. It reduces your code and makes it clearer at the same time:

function getAll() { return $http.get('someurl.com'); } // Or if you need some middleware: function getAll() { return $http.get('someurl.com').then(function(data) { // do something with data return data; }).catch(function(err) { // log error throw err; }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function getAll ( ) { return $ http . get ( 'someurl.com' ) ; } // Or if you need some middleware: function getAll ( ) { return $ http . get ( 'someurl.com' ) . then ( function ( data ) { // do something with data return data ; } ) . catch ( function ( err ) { // log error throw err ; } ) ; }

Use promise flow everywhere

Not only can http requests be asynchronous. If you have a long-run operation, you’ll probably wrap it into a timeout. If you have some old jQuery stuff, you’ll probably use callbacks. Or you could have some old code which was written using XHR before Angular was presented but you don’t want to change it now. In this case, you could have callbacks or even event handlers.

All these kinds of operations create a mess in your services. It would be much better if all of your asynchronous methods returned promises so you could handle them in the same way. For this purpose, Angular 1.x.x has the service $q which can wrap your old methods in promised-based ones. Consider the next example:

function calculateUsers() { var deferred = $q.defer(); setTimeout(function() { deferred.resolve(42); }, 2000); // imitate long term operation return deferred.promise; } calculateUsers.then(function(result) { console.log(result); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function calculateUsers ( ) { var deferred = $ q . defer ( ) ; setTimeout ( function ( ) { deferred . resolve ( 42 ) ; } , 2000 ) ; // imitate long term operation return deferred . promise ; } calculateUsers . then ( function ( result ) { console . log ( result ) ; } ) ;

Although this technique is quite useful, you shouldn’t use it everywhere you have deferred operations. There is a good article on how not to use $q.defer.

Conclusion

Despite all the negative feedback, Angular 1.x.x proved itself as a powerful framework with a large number of libraries and a low entry threshold. There are a lot of applications written in Angular 1.x.x and developers can’t rewrite them from scratch because of the huge code base, lack of knowledge or simply because there is no business need. At the same time, you have to add new features to the existing application. But since people came up with better ideas, you have to move forward as well. The tips mentioned above can help you prepare your code to transition to Angular 4 which has many more benefits. So if you are interested, I recommend you to start from these articles.

Happy coding!