Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm trying to cleanly write an Angular custom $resource extension as a factory as a TypeScript class using DefinatelyTyped (IResource, IResourceClass and friends).

According to Misko Hevery resources are just constructor functions so I was expecting to be able to define my $resource as a regular class with some typesafe interfaces (INamedEntityResource or INamedEntity) and mixin the service definition but I can't seem to get the standard class methods on my NamedEntityResource prototype to end up on factory instances.

Is there a way of doing this with the constructor() function or should I give up and just define the service in plain JavaScript?

declare module EntityTypes {
    interface INamedEntity { }
}

module Services {

    export interface INamedEntitySvc {
        Name(params: {}, successCallback: (data: any, headers: any) => void, errorCallback: (data: any, headers: any) => void): EntityTypes.INamedEntity;
        Clear(params: {}, value: EntityTypes.INamedEntity, successCallback: (data: any, headers: any) => void, errorCallback: (data: any, headers: any) => void): EntityTypes.INamedEntity;
    }

    // WILL have correct interface definition for the resource
    export interface INamedEntityResource extends NamedEntityResource, INamedEntitySvc { }

    export class NamedEntityResource {

        // #1 DOESN'T WORK - These are on NamedEntityResource.prototype but don't end up on svc
        public someMethod() { }
        public someOtherMethod() { }

        constructor($resource) {
            var paramDefaults = {
            };

            var svc: INamedEntitySvc = $resource(getUrl(), paramDefaults, {
                Name: <any>{ method: "GET", params: { action: "Name" } },
                Clear: <any>{ method: "PATCH", params: { action: "Clear" }, headers: { 'Content-Type': 'application/json' } },
            });

            // THIS WORKS - but it's not a NamedEntityResource
            svc["prototype"].someMethod = function () { }
            svc["prototype"].someOtherMethod = function () { }
            return <any>svc;

            // #1 DOESN'T WORK THOUGH
            return; // doesn't pick up methods on prototype

            // #2 THIS DOESN'T WORK EITHER
            NamedEntityResource["prototype"] = angular.extend(this["prototype"] || {}, svc["prototype"]);
            return this;
        }
    }

    // Registration
    var servicesModule: ng.IModule = angular.module('npApp.services');
    servicesModule.factory('NamedEntityResource', NamedEntityResource);
}

Further

So The purpose of this is to allow me to write a resource class{} with methods that will be annotated on every resource I load over HTTP. In this case, my INamedEntitys.

This is the closest solution I've been able to get so far which does appear to work, but it feels really nasty.

module Services {

    export interface INamedEntitySvc {
        Name(params: {}, successCallback: (data: any, headers: any) => void, errorCallback: (data: any, headers: any) => void): EntityTypes.INamedEntity;
        Clear(params: {}, value: EntityTypes.INamedEntity, successCallback: (data: any, headers: any) => void, errorCallback: (data: any, headers: any) => void): EntityTypes.INamedEntity;
    }

    // WILL have correct interface definition for the resource
    export interface INamedEntityResource extends NamedEntityResource, INamedEntitySvc { }

    export class NamedEntityResourceBase {
        public someMethod() { }
        public someOtherMethod() { }
    }

    // extend our resource implementation so that INamedEntityResource will have all the relevant intelisense
    export class NamedEntityResource extends NamedEntityResourceBase {

        constructor($resource) {
            super(); // kind of superfluous since we're not actually using this instance but the compiler requires it

            var svc: INamedEntitySvc = $resource(getUrl(), { }, {
                Name: <any>{ method: "GET", params: { action: "Name" } },
                Clear: <any>{ method: "PATCH", params: { action: "Clear" }, headers: { 'Content-Type': 'application/json' } },
            });

            // Mixin svc definition to ourself - we have to use a hoisted base class because this.prototype isn't setup yet
            angular.extend(svc["prototype"], NamedEntityResourceBase["prototype"]);

            // Return Angular's service (NOT this instance) mixed in with the methods we want from the base class
            return <any>svc;
        }

        thisWontWork() {
            // since we never actually get a NamedEntityResource instance, this method cannot be applied to anything.
            // any methods you want have to go in the base prototype
        }
    }

    // Registration
    var servicesModule: ng.IModule = angular.module('npApp.services');
    servicesModule.factory('NamedEntityResource', NamedEntityResource);
}

The trick was to;

  1. Hoist the methods I want on the service up into a base class because this.prototype isn't initialised by the time my constructor() function is called.
  2. Return svc which is the angular $resource service from the constructor, which you can do in JavaScript of course, but it feels like really dirty duck-typing in TypeScript.
  3. In order to get the methods on svc.prototype I extend that directly from my base class. This is particularly nasty as it means setting up the prototype every time an instance is created.
  4. The final pungent aroma to this sh** sandwich is I have to call super() on the constructor for the instance I'm throwing away just to get it to compile.

However, at the end of all that, I can add methods to NamedEntityResourceBase and they'll appear in the prototype of all entities loaded from my HTTP resource.

share|improve this question

1 Answer 1

Register classes with service instead of factory:

servicesModule.service('NamedEntityResource', NamedEntityResource);

Disclaimer: my video about additional info you might find useful about service registration in angularjs + typescript : http://www.youtube.com/watch?v=Yis8m3BdnEM&hd=1

share|improve this answer
    
Thanks, but I'm not clear how that helps me extend $resource. Do you have an example? –  cirrus Feb 17 at 10:45

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.