Angular — Keeping your application DRY

Gerard Sans
AngularZone Community
5 min readMar 5, 2015

Using AngularJS utility functions so you don’t have to re-invent the wheel.

thedotisblack

Follow me on Twitter for latest updates @gerardsans.

Updated (21/6/15): added examples without using angular.

Don’t Repeat Yourself principle (DRY) is pretty self-explanatory. By knowing what features are already in Angular we are getting closer to be able to apply it successfully. Check out these useful utility functions for:

  • object manipulation: copy, extend, *merge* (new in 1.4)
  • type checking and equality: equals, isArray, isDate, isDefined, isElement, isFunction, isNumber, isObject, isString and isUndefined
  • working with JSON: fromJson, toJson
  • string manipulation: lowercase, uppercase
  • functional programming: bind, identity, noop

If you prefer reading test specs and code better than reading a post, you can skip straight to the link below

tests for copy, extend, merge, equals, bind and identity (Jasmine)

Let’s start!

Object manipulation

You might have used some of them already. They can be very handy to handle data manipulation or dealing with config objects.

Let’s refresh the basic structure of an object with an example:

var person1 = {
name: 'John',
address : {
description: 'Oxford Street'
}
}
var person2 = {
id: 1,
address : {
postcode: 'SW1'
}
}

We are using the literal notation to create the person1 object with two properties: name and address. address property is another object literal containing only one key: description. We will be using person2 object in the following examples.

angular.copy(source, [destination])

Let’s start with angular.copy. This function creates a deep copy of the source object. If no destination object is provided a copy of source is returned. Note that it will remove all properties from destination (if any) before copying values over.

angular.copy(source, [destination]);

Attention. Check parameters order for copy as extend and merge use the opposite order!

Some different notations:

var destination = angular.copy(source);
//angular.copy(source, destination); // equivalent

Example

var person2 = angular.copy(person1);
//angular.copy(person1, person2); // equivalent

Resulting person2 object after copy

{
name: 'John',
address : {
description: 'Oxford Street'
}

}

All previous properties are removed from person2 like person2.id before copying person1 properties.

Without using angular you would be doing the following or using a third party library like lodash.

// option 1: manually copy fields
var person2 = {}; // creates new object
person2.name = person1.name;
person2.address = person1.address;
// option 2: copy fields automagically
var person2 = mycopy(person1);
// refactored copy
function mycopy(source){
var destination = {}; //always creates new instance
for(var key in source){
destination[key] = source[key];
}
return destination;
}

angular.extend(destination, source)

Properties are copied from source overriding any existing child properties on destination. As an extra feature, we can also use multiple source objects that will be applied following parameters order.

angular.extend(destination, source);
angular.extend(destination, source [, source]);

If you want to preserve original source objects you can use an alternative notation like

var extended = angular.extend({}, object1, object2)
//angular.extend(extended, object1, object2) // equivalent

Resulting extended object

{
id: 1,
name: 'John',
address : {
postcode: 'SW1'
}
}

Notice the id property, and how we have lost the description property. This is because angular.extend copied object2.address into extended.address. If you need to copy child properties as well, you can use the new angular.merge.

☞ Read this post by Todd Motto considering using angular.extend in Controllers.

Without using angular you would be doing the following.

// option 1: manual extend
person2.name = person1.name;
person2.address = person1.address;
// option 2: extend fields automagically
myextend(person2, person1);
// refactored extend
function myextend(destination, source){
for(var key in source){
destination[key] = source[key];
}
}

angular.merge(destination, source) *new in 1.4*

This function does a deep copy of all properties so all properties are copied from source overriding any existing properties on destination. It will preserve properties in child objects when copying properties tough. As with extend, we can also use multiple source objects as well that will be merged in order.

angular.merge(destination, source);
angular.merge(destination, source [, source]);

You can preserve source objects by using this notation

var merged = angular.merge({}, object1, object2)
//angular.merge(merged, object1, object2) // equivalent

Attention. Remember parameters are reversed in angular.copy.

The resulting merged object

{
id: 1,
name: 'John',
address : {
description: 'Oxford Street',
postcode: 'SW1'
}
}

This time all properties are copied over, created or overwritten depending on the source and destination objects.

Without using angular you would be doing the following.

// option 1: manual merge
person2.name = person1.name;
person2.address.description = person1.address.description;
// option 2: merge fields automagically
mymerge(person2, person1);
// refactored merge
function mymerge(destination, source){
for(var key in source){
if (angular.isObject(source[key])){
mymerge(destination[key], source[key]);
} else {
destination[key] = source[key];
}
}
}

Type checking and equality

It’s always a good practice to check upon types. These methods will make your code rock solid and you don’t even need to code them again, keeping your code base DRY. This is the full list:

  • equals, isArray, isDate, isDefined, isElement, isFunction, isNumber, isObject, isString, isUndefined

Let’s look into angular.equals (notice final ‘s’) in more detail. In order to see implementation details for the other methods you can look here.

angular.equals(object1, object2)

Tells if two objects are equal. Supports value types, regular expressions, arrays and objects. For object properties, it will ignore functions and properties prefixed with $ (angular properties for internal usage).

Attention. Favour angular.equals in your applications as it will avoid false negatives (due to internal properties like $$hashKey).

Working with JSON

angular.fromJson and angular.toJson are very handy for JSON manipulation. For example, they are used by the $http service. Let’s explore them a little more.

angular.fromJson(json)

It parses the input parameter if it’s a string or returns it AS-IS otherwise. It uses JSON.parse internally.

angular.toJson(object, pretty)

It converts the object into a JSON string. It uses JSON.stringify internally. We can use pretty with true to add two spaces, set a value between 1 and 10, or skip it altogether for a raw output. Besides default implementation, it will avoid prefixed keys starting with $$.

String manipulation

These methods are wrapped around native str.toUpperCase and str.toLowerCase and return same results. They are used as follows.

console.log(angular.uppercase('Hello world!')); //HELLO WORLD!
console.log(angular.lowercase('Hello world!')); //hello world!

Functional programming

Angular has it’s own functional methods that you can use instead of creating your own:

  • angular.bind(self, fn, [, arg1[, arg2[, …]]])
  • angular.identity(object)
  • angular.noop()

Let’s see them in more detail.

angular.bind(self, fn, [, arg1[, arg2[, …]]])

This is a Function.prototype.call() wrapper. It let’s us bind the self object to the function fn so this can be set to self. If we use null or undefined for self, this will be set to the global object (usually window in the browser). See an example below:

var bindedLogMe = angular.bind({a:1}, logMe);
function logMe(){
console.log(this);
}
bindedLogMe(); // {a:1}

angular.identity(object)

This will return the object you sent in unmodified.

function identity(object) {return object;} console.log(angular.identity(2)); // 2

angular.noop()

This is a little gem of angular with an empty function definition.

function noop() {} angular.noop();

That’s all! Hope you have enjoyed this overview of Angular utility methods! If you want you can proceed to a suite of tests around them here. Thanks for reading! Have any questions? Ping me on Twitter @gerardsans.

If you want to go deeper I recommend you the link below.

Resources

Angular.js (latest version)

--

--

Gerard Sans
AngularZone Community

Helping Devs to succeed #AI #web3 / ex @AWSCloud / Just be AWSome / MC Speaker Trainer Community Leader @web3_london / @ReactEurope @ReactiveConf @ngcruise