16

I'm a huge fan of angular but it's got some tricky concepts with extremely nuanced differences between them and this is one of them.

I just want to create an class that I can use to create custom objects in my Angular controllers and factories. It surely shouldn't be that hard but I can't figure out how to do it. I want to have a custom, ResultSet class which I can instantiate to create instances of ResultSet. However for the life of me I can't figure out the correct syntax of factory v. service to use.

This is all I want:

ResultSet = function(dataSet){ 
  this.filter = function(){ 
    # filters and returns dataSet
    # ...
  }
}

and then I want to be able instantiate an instance of ResultSet inside a controller etc:

MyApp.controller('pageCtrl', ['ResultSet',  (ResultSet) ->
  # ...
  rs = ResultSet.new(dataToFilter)

How can I create a service that allows me to create instances of my custom object?

It seems more correct to use an Angular Service rather than a Factory since a service returns an instance of an object (which is exactly what I want). But I can't figure out how to do this...

How would I use a service to declare my custom ResultSet class and then how would I instantiate an instance from it?

Flavien Volken
  • 19,196
  • 12
  • 100
  • 133
Peter Nixey
  • 16,187
  • 14
  • 79
  • 133

3 Answers3

14

Maybe you were looking for something like this:

.factory('User', function (Organisation) {

  /**
   * Constructor, with class name
   */
  function User(firstName, lastName, role, organisation) {
    // Public properties, assigned to the instance ('this')
    this.firstName = firstName;
    this.lastName = lastName;
    this.role = role;
    this.organisation = organisation;
  }

  /**
   * Public method, assigned to prototype
   */
  User.prototype.getFullName = function () {
    return this.firstName + ' ' + this.lastName;
  };

  /**
   * Private property
   */
  var possibleRoles = ['admin', 'editor', 'guest'];

  /**
   * Private function
   */
  function checkRole(role) {
    return possibleRoles.indexOf(role) !== -1;
  }    

  /**
   * Static property
   * Using copy to prevent modifications to private property
   */
  User.possibleRoles = angular.copy(possibleRoles);

  /**
   * Static method, assigned to class
   * Instance ('this') is not available in static context
   */
  User.build = function (data) {
    if (!checkRole(data.role)) {
      return;
    }
    return new User(
      data.first_name,
      data.last_name,
      data.role,
      Organisation.build(data.organisation) // another model
    );
  };

  /**
   * Return the constructor function
   */
  return User;
})

From this post by Gert Hengeveld.

Amrit Kahlon
  • 1,286
  • 1
  • 18
  • 38
  • How would you access a `self`/`this` variable inside a local function, for example, `checkRole()`? Or would that function have to be part of the User prototype? – Basil Jun 08 '17 at 01:07
  • Does "this" not work? Maybe "User", sorry I'm using typescript now so its a little different. – Amrit Kahlon Jun 08 '17 at 22:41
10
myApp.factory('ResulSet', function() {
    function ResultSetInstance(dataSet) { 
        this.filter = function(){ 
            // ...
        }
    }

    return {
        createNew: function(dataSet) {
            return new ResultSetInstance(dataSet);
        }
    };
});

and then

myApp.controller('pageCtrl', function(ResultSet) {
    var someData = ...;
    var rs = ResultSet.createNew(someData);
}

Edit (from the question asker)

On experimenting with this further I found that you didn't even need to have the createNew method.

myApp.factory('ResultSetClass', function() {
    ResultSetClass = function(dataSet) { 
        this.filter = function(){ 
            // ...
        }
    }

    return ResultSetClass
});

works just fine and then you can call new ResultSetClass(args).

Note for those using Coffeescript

Coffeescript will return the last variable or method in your class instance so if you are using coffeescript (as a general rule), it's imperative to return this at the end of the class definition

myApp.factory 'ResultSetClass', () ->
  ResultSetClass = (dataset) ->
    this.filter = () ->
      # do some stuff
    return this

  return ResultSetClass

If you don't return this explicitly then you'll find that when you call

myApp.factory 'ResultSetClass', () ->
  ResultSetClass = (dataset) ->
    this.filter = () ->
      # do some stuff

then you'll simply be left with the last thing the coffeescript returns which is the filter method.

Community
  • 1
  • 1
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Thank you and this works. I've got a question though - isn't the point of a service that it returns a new instance of an object - wouldn't a service be the correct route to take here? – Peter Nixey Nov 11 '14 at 13:58
  • This defines a service. A service is a singleton. This singleton has a single method createNew() allowing to create a new instances of ResultSetInstance. You could use service() instead of factory() to define the service, but the service would still be a singleton, and it would thus still have to provide a method creating new instances of ResultSetInstance. – JB Nizet Nov 11 '14 at 14:07
  • eesh, ok. This is still a nuance deeper than I'm able to fully grok but I can see from this that you're right - ta! http://stackoverflow.com/questions/15666048/service-vs-provider-vs-factory – Peter Nixey Nov 11 '14 at 15:43
0

I recently has do do something like that because I wanted to implement a factory of class instance, and being able to configurate my instances and benefit from Angular Dependency injection. I ended up with something like that

// Implem
export class XAPIService {
  private path: string;
  /* this DO NOT use angular injection, this is done in the factory below */
  constructor(
    private seed: XAPISeed,
    private $http: ng.IHttpService,
    private slugService: SlugService
  ) {
    const PATH_MAP: Map<Y, Z> = new Map([
      ['x', id => `/x/${id}`],
      ['y', id => `/y/${id}`],
    ]);
    this.path = PATH_MAP.get(this.seed.type)(this.seed.id);
  }

  list() {
    /* implem that use configured path */
    return this.slugService
    .from(this.path + `/x`)
    .then(url => this.$http.get<IX>(url))
    .then(response => response.data)
  }
}

export type IXAPIFactory = (s: XAPISeed) => XAPIService;
export function XAPIFactory(
  $http: ng.IHttpService,
  myService: SlugService
) {
  'ngInject';
  return (seed: XAPISeed) =>
    new XAPIService(seed, $http, myService);
}

// angular 

angular.module('xxx', [])
  .factory('xAPIFactory', XAPIFactory)

// usage in code 

export class XsController implements ng.IComponentController {
  /* @ngInject */
  constructor(
    private xAPIFactory: IXAPIFactory,
  ) {}

  $onInit() {
    this.xService = this.xAPIFactory({ id: 'aaabbbaaabbb', type: 'y' });
    return this.xService.list()
      .then(xs => {
        this.xs = xs;
      })
  }  
}
Xavier Haniquaut
  • 1,003
  • 9
  • 22