As described in Services, you should use model and collection classes to represent the data in your system rather than providing raw HTTP responses to your pages/components.

  • Why?: Using a class to represent a data model allows that data model to be easily documented across your system within the functions that data model is passed to.

  • Why?: A model's class definition immediately tells all developers exactly which properties are going to be available on that object. With good documentation (or even better, TypeScript), they also tell other developers which data types the properties hold.

  • Why?: Data models/collections as classes allows you to add functionality in the form of accessor/query methods. These methods can provide additional information, or implement logic to provide data to the consumer that the properties alone do not make obvious, or do not provide.

You'll want to at least represent each data object as a Model class for the above reasons. Optionally, on top of that, you may want to provide Collection classes to add functionality on the "array of models" level. More on that later.

Hero Model

Here's an example of a model "class" in ES5, but if you're able, use ES6 classes and Babel to transpile them into ES5. Or, even better, use TypeScript classes.

angular.module( 'heroes' ).factory( 'Hero', function() {
    'use strict';

    /**
     * @class Hero
     *
     * Represents a Hero in the app.
     * 
     * @constructor
     * @param {Object} props An Object containing the following properties:
     * @param {Number} props.id The hero's ID.
     * @param {String} props.name The hero's name.
     * @param {Moment} props.lastBattle The date/time of the hero's last
     *   battle.
     */
    var Hero = function( props ) {
        this.id = props.id;
        this.name = props.name;
        this.lastBattle = props.lastBattle;
    };


    /**
     * Determines if the Hero has battled recently.
     * 
     * @return {Boolean}
     */
    Hero.prototype.hasBattledRecently = function() {
        var threeMonthsAgo = moment().subtract( 3, 'months' );

        return this.lastBattle.isAfter( threeMonthsAgo );
    }


    return Hero;

} );

And its usage once you have an instance:

hero.id    // 11
hero.name  // 'Mr. Nice'
hero.lastBattle.format( 'MM/DD/YYYY' )  // '04/22/2016'
hero.hasBattledRecently()  // false

HeroesCollection

You may decide that it is not always necessary to return a collection class from your services, but it can be a powerful technique for encapsulating query methods that operate on an array of your data models.

Here is an example to demonstrate the technique:

angular.module( 'heroes' ).factory( 'HeroesCollection', function() {
    'use strict';

    /**
     * @class HeroesCollection
     *
     * Collection to hold {@link Hero}s, and provide query methods.
     * 
     * @constructor
     * @param {Hero[]} heroes The Heroes to populate the collection with.
     */
    var HeroesCollection = function( heroes ) {
        this.heroes = heroes;
    };


    /**
     * Retrieves all of the {@link Hero Heroes} in the collection.
     *
     * @return {Hero[]}
     */
    HeroesCollection.prototype.getHeroes = function() {
        return this.heroes;
    }


    /**
     * Retrieves the {@link Hero Heroes} that have battled recently.
     *
     * @return {Hero[]}
     */
    HeroesCollection.prototype.getRecentBattlers = function() {
        return _.filter( this.heroes, function( hero ) { 
            return hero.hasBattledRecently(); 
        } );
    }


    /**
     * Returns Heroes in the collection whose names match the given `searchStr`.
     *
     * @param {String} searchStr
     * @return {Hero[]}
     */
    HeroesCollection.prototype.queryByName( searchStr ) {
        searchStr = searchStr.replace( /[^A-Za-z0-9 ]/, '' );  // sanitize regex input

        var regex = new RegExp( searchStr, 'i' );

        return _.filter( this.heroes, function( hero ) {
            return regex.test( hero.name );
        } );
    }


    return HeroesCollection;

} );

And its usage once you have an instance:

// retrieves all Heroes in the collection
heroesCollection.getHeroes()

// retrieves just the Heroes that have battled recently
heroesCollection.getRecentBattlers()

// retrieves Hero instances for 'Narco' and 'Mr. Nice'
heroesCollection.queryByName( 'n' )   

Additional Points

  • The methods that should exist on model/collection classes are usually implemented by developers on the controller level. This often results in duplicate logic between different controllers that operate on the same data models. Be aware of this and consider moving these methods to the appropriate model/collection classes.

  • Because model/collection classes provide a common set of methods for operating on the object’s data (ex: hasBattledRecently() method, above), developers can easily find common operations they may need to leverage, without duplicating code.

Next Article: Components