Thursday, October 29, 2015

Angular 1.x services as ES6 classes

Following up on my post of an angular 1.x directive implemented as an ES6 class, I worked up a pattern for services.  It's even simpler than the directive, in that the service has no link function to worry about.

I still make an initClass() method that describes the constructor parameters that need to be injected, but note that the factory method only injects an $injector that it uses to create the service.  As with anything, there are many different ways you can do this.  At the end I'll show you a more brief way to do this.

import 'angular';
/*
 * Angular service as a class
 */
export class DataService {
    static initClass() {
        DataService .$inject = ['$resource', '$q'];
    }

    constructor($resource, $q) {
        this.$resoure = $resource;
        this.$q = $q;
        this.latest = $resource('/data/latest/:name');
        this.tables = $resource('/data/config/default_table');
        this.tags = $resource('/data/tags', {},
            {get: {method: 'GET', isArray: true}});
    }

    getLatestValue(key) {
        var deferred = this.$q.defer();

        this.latest.get({"name": key}).$promise.then( (result) => {
            deferred.resolve(angular.fromJson(angular.toJson(result)));
        }, (error) => {
            deferred.reject(error);
        });

        return deferred.promise;
    }

    getTableConfig() {
        var deferred = this.$q.defer();

        this.tables.get().$promise.then((result) => {
            deferred.resolve(angular.fromJson(angular.toJson(result)));
        }, (error) => {
            deferred.reject(error);
        });

        return deferred.promise;
    }

    getAllTags() {
        var defer = this.$q.defer();
        /* This is an example of how to cache the result */
        if (angular.isDefined(this.tag_cache)) {
            /* Resolve from the cache */
            defer.resolve(tag_cache);
        } else {
            /* Not cached, so resolve from the backend server */
            tags.get().$promise.then( (allTags) => {
                this.tag_cache = {};
                alltags = angular.fromJson(angular.toJson(allTags));
                angular.forEach(allTags, function (t) {
                    tag_cache[t.name] = t;
                });
                defer.resolve(tag_cache);
            }, (error) => {
                defer.reject(error);
            });
        }

        return defer.promise;
    }


    static register(module) {
        module.factory('dataservice', [ '$injector', function($injector) {
            return $injector.instantiate(DataService);
        }]);
    }
}

If you're not like me (and you don't care about having the injectables defined in the source close to the constructor) you can skip initClass() and setting up DataService.$inject and just set them when you call instantiate():

    static register(module) {
        module.factory('dataservice', [ '$injector', function($injector) {
            return $injector.instantiate(DataService, [ '$q', '$resource' ] );
        }]);
    }


Back in app.js you do just as before:

import { DataService } from 'services/dataservice';
...
DataService.register(mainModule);
...

I'm doing all of this with System.js but it's on my short list to try it all with webpack.  I will let you know when that works.

By the way .. if you're wondering what all this fromJson(toJson()) stuff is: the object that comes back from $resource has some extra items in it (beyond what the server returns).  That's the recommend trick for stripping them out.  It's not really necessary to strip them out - in particular, if you use ngResource to POST them back it strips things like $promise out before POSTing.  Again, though, I'm a little OCD about this, especially if I display the raw result using the angular json $filter, so I take the trouble to clean up the object.  It's an optional step.


No comments:

Post a Comment