1

SOLUTION

thanks to hege-hegedus answer below. Applied it to my actual code and works great.

// NOTE : called from another service, but removed function wrap around and angular module setup for brevity sake
// article is serverObject
var articleInstance = new Article(article);
console.log(articleInstance instanceof Article)
// true
console.log(articleInstance.isProduct(article))
// true (in my case)

/*
* Create the constructor from server article object using lodash
*/
ArticleConstructor.$inject = [];
function ArticleConstructor() {
    return function(data) {
        var keys = ['app_id', 'body', 'headline', 'object_type', 'url', 'status'
        ];
        _.assign(this, _.pick(data, keys));
    };
}

/*
* Extend the iief constuctor - ArticleConstruct
*/
Article.$inject = ['ArticleConstructor'];
function Article(ArticleConstructor) {

    function ArticleExtended(data) {
        ArticleConstructor.call(this, data);
    }

    //  create the new Article object with the ArticleConstructor prototype object and properties
    ArticleExtended.prototype = Object.create(ArticleConstructor.prototype);

    // Article inherits a constructor property from its prototype i.e. ArticleConstructor
    ArticleExtended.prototype.constructor = ArticleExtended;

    ArticleExtended.prototype.isProduct = function () {
        return this.object_type == 3;
    };

    ArticleExtended.prototype.hasImage = function () {
        return _.has(this, 'image');
    };

    return ArticleExtended;
}

How do I extend the factory object below. I'm using lodash to auto hydrate the factory constructor, which works great, but now none of my original methods execute e.g. isIcon() returns an error msg - "isIcon is not a function". I've searched for an answer but most constructor examples use the traditional return service; at the end of object, which works fine but then forces me back to more manual approach to building the constructor. I feel like I'm missing something obvious.

Using AngularJS 1.4.8

FACTORY OBJECT TO EXTEND

// AJS factory - the problem child
ImageUnableToExtendFn.$inject = ['IMG_TYPE'];
function ImageUnableToExtendFn(IMG_TYPE) {

  Image.prototype.isIcon = function (img) {
    return img.type === IMG_TYPE.ICON;
  };

 return function(data) {
    var keys = ['id', 'src', 'alt', 'type'];
       _.assign(this, _.pick(data, keys));
    };
});

I've tried extending the IIEF factory with angular.extend(), but that doesn't work either (example below):

angular.extend(imageOfUnableToExtendFn, {
    isIcon: function(img) {
        return img.type === IMG_TYPE.ICON;
    }
})

MORE DETAILED OF THE ABOVE FOR REFERENCE PURPOSES

define([
   'angular',
   'lodash'

], function(angular, _) {
'use strict';

ImageService.$inject = ['ImageClassicFn', 'ImageUnableToExtendFn'];
function ImageService(ImageClassicFn, ImageUnableToExtendFn) {

    var imageService = {
        images: null,

        createInstance: function(serverImageObject) {
            var self = this,
                imageOfClassicFn,
                imageOfUnableToExtendFn,
                isIcon,

            if (angular.isDefined(serverImageObject)) {

                imageOfClassicFn = new ImageClassicFn();
                isIcon = imageOfClassicFn.isIcon(serverImageObject);
                console.log('IS ICON', isIcon);
                // >  true of false 

                imageOfUnableToExtendFn = new ImageUnableToExtendFn(serverImageObject);
                // result is a hydrated instance of ImageClassicFn with mapped keys using lodash
                isIcon = imageOfClassicFn.isIcon(serverImageObject);
                // ERROR - isIcon is not a function 

                // Attempting to extend manually fails silently 
                angular.extend(imageOfUnableToExtendFn, {
                    isIcon: function(img) {
                        return img.type === IMG_TYPE.ICON;
                    }
                })

                isIcon = imageOfClassicFn.isIcon(serverImageObject);
                // SAME ERROR - isIcon is not a function 
            }
        }
    };

    return imageService;
}

ImageClassicFn.$inject = ['IMG_TYPE'];
function Image(IMG_TYPE) {

  function Image(id, src, alt, type) {
    this.id = id;
    this.src = src;
    this.alt = alt;
    this.type = type;
  }

  Image.prototype.isIcon = function (img) {
    return img.type === IMG_TYPE.ICON;
  };

    return Image;
});

ImageUnableToExtendFn.$inject = ['IMG_TYPE'];
function Image(IMG_TYPE) {

  Image.prototype.isIcon = function (img) {
    return img.type === IMG_TYPE.ICON;
  };

return function(data) {
    var keys = ['id', 'src', 'alt', 'type'];
       _.assign(this, _.pick(data, keys));
    };
});


return angular.module('content.images', [

    ])
     .constant("IMG_TYPE", {
        "ICON": 1,
    })
    .factory('ImageClassicFn', ImageClassicFn)
    .factory('ImageUnableToExtendFn', ImageUnableToExtendFn)
    .service('ImageService', ImageService);

});

Nolan
  • 888
  • 1
  • 7
  • 18
  • Try to take a look at http://codereview.stackexchange.com/questions/40628/angularjs-extends-service – Whisher Dec 19 '15 at 20:59
  • Do you want to make a subclass of the `Image` class, or to add some new functionality to the existing class? I don't see how `ImageUnableToExtendFn` is used. – Tamas Hegedus Dec 19 '15 at 21:58
  • @Whisker thanks, looked at it and the child posts. Still cannot get it work. Again because of fact that it returns an immediate executed method whereas the above post uses the traditional return of an object with child functions. So if I add a constructor in object it wont execute immediately. I can add a method to the above mentioned traditional approach that hydrates the constructor but that is post the new instance of the object. – Nolan Dec 19 '15 at 21:58
  • @hege_hegedus apologies I must have screwed it up when adding to markup. In my actual code I have it right, but somehow ... lost in translation .. lol ... that aside, the problem still exists. If you have an immediate executing function in a factory it loses the original properties. I understand why i.e. that it is returning an immediate executed the function versus a object containing functions but I don't know the best way to do this. – Nolan Dec 19 '15 at 22:09

1 Answers1

2

Subclassing in javascript is a bit tricky. Take a look at this SO post about javascript inheritance.

Basically, this is how you usually do this, wrapped in angular 1.x modules:

ImageClassicFactory.$inject = ['IMG_TYPE'];
function ImageClassicFactory(IMG_TYPE) {

  function ImageClassic(id, src, alt, type) {
    this.id = id;
    this.src = src;
    this.alt = alt;
    this.type = type;
  }

  ImageClassic.prototype.isIcon = function (img) {
    return img.type === IMG_TYPE.ICON;
  };

  return ImageClassic;
});
module.factory('ImageClassic', ImageClassicFactory);



ImageExtendedFactory.$inject = ['IMG_TYPE', 'ImageClassic'];
function ImageExtendedFactory(IMG_TYPE, ImageClassic) {

  function ImageExtended(id, src, alt, type) {
      ImageClassic.call(this, id, src, alt, type);
  }
  ImageExtended.prototype = Object.create(ImageClassic.prototype);
  ImageExtended.prototype.constructor = ImageExtended;

  ImageExtended.prototype.isIcon = function (img) {
    return img.type === IMG_TYPE.ICON;
  };

  return ImageExtended;
});

module.factory('ImageExtended', ImageExtendedFactory);
Community
  • 1
  • 1
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
  • btw, similar to examples you link there are very generic JS OOP examples that I get, extending, decorating, inheriting, but applying them in AJS never seems to work out the same and that is frankly my knowledge level versus AJS ... thanks again! – Nolan Dec 19 '15 at 23:52