Adopting AngularJS in Existing Applications


Alexandra Atzl | @alexandraatzl

UI Developer at EffectiveUI | Rochester, NY

May 7, 2015

Disclaimer

The Problem

Common Issues

  • Placing multiple objects in a single file.
  • Using global variables
  • Not using Angular's built-in services.
  • Putting business logic in controllers
  • Repeating base objects, constant values, etc.
  • Repeating nearly-identical functionality across services or controllers.

Take Advantage of Everything Available

If you're given an entire toolbox with which to build a bookshelf, why would you insist on doing everything using only the hammer?

So where do we start?

Break up files.

Every component should have its own folder, and every controller/service/directive should have its own Javascript file.

Group by feature, not type

Instead of this:


	app/
		app.js
		controllers/
			login.ctrl.js
			search.ctrl.js
		services/
			login.svc.js
			search.svg.js
		directives/
			header.dir.js
		views/
			login.html
			search.html
			header.html

							

Try this:


	app/
		app.js
		components/
			login/
				login.ctrl.js
				login.svc.js
				login.html
			search/
				search.ctrl.js
				search.svc.js
				search.html
		common/
			header/
				header.dir.js
				header.html
							

Create Services

Eliminate global variables by wrapping them in services.

Instead of this:


	angular.module('app')
		.controller('MyCtrl', function($scope) {
			// What is moment? How do I know this exists and is the object I want?
			$scope.startTime = moment();
			$scope.endTime = moment().add(1, 'h');
		});
						

Try this:


	angular.module('app')
		.service('moment', function($window) {
			return $window.moment;
		})
		.controller('MyCtrl', function($scope, moment) {
			$scope.startTime = moment();
			$scope.endTime = moment().add(1, 'h');
		});
						

It works for custom objects, too!

Use this method for any custom global objects you've created as well.

Instead of this:

							
	var zoo = {
		animals: null,
		getAnimals: function() {
			return this.animals;
		}
	}

	angular.module('app')
		.controller('ZooCtrl', function($scope) {
			// hope that zoo exists somewhere
			$scope.myAnimals = zoo.getAnimals();
		});
							
						

Try this:

							
	angular.module('app')
		.factory('zoo', function(){
			var animals = null;
			return {
				getAnimals: function() {
					return animals;
				}
			};
		})
		.controller('ZooCtrl', function($scope, zoo) {
			$scope.myAnimals = zoo.getAnimals();
		});
							
						

Use all the tools available to you

Angular's built-in services make life easier. Use them!

Instead of this:

							
	angular.module('app')
		.factory('addressBookService', function(){
			var data = null;

			return {
				getAddresses: function() {
					$.ajax({
						url: '/path/to/data',
						dataType: 'json',
						success: function(result) {
							data = result;
						},
						error: function(error) {}
					);
				}
			};
		});
							
						

Try this:

							
	angular.module('app')
		.factory('addressBookService', function($http) {
			return {
				getAddresses: function() {
					return $http({
						method: 'GET',
						url: '/path/to/data'
					});
				}
			};
		})
							
						

frequently forgotten built-in services

  • $http
  • $window
  • $document
  • $location
  • $animate
  • $filter
  • $q
  • $timeout

Utilize Angular Values & Constants

If you commonly need to use the same values, don't add them to every controller individually!

Intead of this:


	angular.module('petStoreApp')
		.controller('petStoreOneCtrl', function($scope) {
			$scope.myCat = {
				name: 'Grumpy',
				type: 'cat'
			};

			$scope.myDog = {
				name: 'Lassie',
				type: 'dog'
			};
		})
		.controller('petStoreTwoCtrl', function($scope) {
			$scope.myOtherCat = {
				name: 'Fluffy',
				type: 'cat'
			};
		});
						

Try this:

							
	petStoreApp.value('petTypes', {
			CAT: 'cat',
			DOG: 'dog'
		})
		.controller('petStoreOneCtrl', function($scope, petTypes) {
			$scope.myCat = {
				name: 'Grumpy',
				type: petTypes.CAT
			};

			$scope.myDog = {
				name: 'Lassie',
				type: petTypes.DOG
			};
		})
		.controller('petStoreTwoCtrl', function($scope, petTypes) {
			$scope.myOtherCat = {
				name: 'Fluffy',
				type: petTypes.CAT
			};
		});
							
						

Take things a step further

Do you have multiple services that do almost the same thing? Try creating a base service and extending it for each use case.

Instead of this:


	petStoreApp.service('catService', function($http) {
			var svc = {
				getList: function() { // return all cats at the store
					$http({ method: 'GET', url: 'cats/all' })
						.then(successFn, errorFn);
				}
				getInfo: function(catId) { // get info for a specific cat
					$http({ method: 'GET', url: 'cats/' + catId })
						.then(successFn, errorFn);
				}
			};

			return svc;
		})
		.service('dogService', function($http) {
			var svc = {
				getList: function() {
					$http({ method: 'GET', url: 'dogs/all' })
						.then(successFn, errorFn);
				},
				getInfo: function(dogId) {
					$http({ method: 'GET', url: 'dogs/' + dogId })
						.then(successFn, errorFn);
				}
			};

			return svc;
		});
						

Try this:


	petStoreApp.service('petService', function($http) {
			var petSvc = function(_endpoint_) {
				var endpoint = _endpoint_ + '/';

				return {
					getList: getList,
					getInfo: getInfo
				};

				function getList() {
					$http({ method: 'GET', url: endpoint + 'all' })
						.then(successFn, errorFn);
				}

				function getInfo(petId) {
					$http({ method: 'GET', url: endpoint + petId })
						.then(successFn, errorFn);
					}
				}
			};

			return petSvc;
		});
						

Now our services are DRY


	petStoreApp.service('catService', function(petService) {
			var catSvc = petService('/cats');

			return catSvc;
		})
		.service('dogService', function(petService) {
			var dogSvc = petService('/dogs');

			return dogSvc;
		});
						

What about controllers?

We can also create a base controller and extend it.

Instead of this:


	petStoreApp.controller('catCtrl', function($scope, catService) {
			$scope.getData = function(catId) {
				catService.getInfo(catId)
					.then(successFn, errorFn);
			};

			$scope.addCat = function(newCat) {
				catService.add(newCat);
			};
		})
		.controller('dogCtrl', function($scope, dogService) {
			$scope.getData = function(dogId) {
				dogService.getInfo(dogId)
					.then(successFn, errorFn);
			};

			$scope.addDog = function(newDog) {
				dogService.add(newDog);
			};
		});
						

Try this:


	petStoreApp.controller('petCtrl', function($scope, animalService) {
			$scope.getData = function(petId) {
				animalService.getInfo(petId)
					.then(successFn, errorFn);
			};

			$scope.addPet = function(newPet) {
				animalService.add(newPet);
			};
		});
						

Much better!


	petStoreApp('petStoreApp')
		.controller('catCtrl', function($scope, $controller, catService) {
			$controller('petCtrl', {
				$scope: $scope,
				animalService: catService
			});

			$scope.addCat = $scope.addPet;
		})
		.controller('dogCtrl', function($scope, $controller, dogService) {
			$controller('petCtrl', {
				$scope: $scope,
				animalService: dogService
			});

			$scope.addDog = $scope.addPet;
		});
						

Takeaways

  • Stop using global objects.
  • Use the tools Angular provides you.
  • Keep business logic out of controllers.
  • Extend base services & controllers to minimize code duplication
  • DON'T. REPEAT. YOURSELF.

Thank You!

Alexandra Atzl
alexandraatzl.com
@alexandraatzl