Justin
Justin

Reputation: 4263

Angular service with ES6 class and Babel

I'm trying to use an ES6 class as an Angular service but when it's instantiated, the methods do not have access to the constructor variables.

class dataWrapperService {

    constructor($q, $log) {
        this.$q = $q;
        this.$log = $log;
    }

    data() {
        console.log(this.$q);
    }
}

dataWrapperService.$inject = ['$q', '$log'];

app.service('dataWrapper', dataWrapperService);

Once the service gets injected by Angular and I call the data method on it, the method fails to have access to the constructor values.

// calling the data method results in an error
dataWrapper.data();   //TypeError: Cannot read property '$q' of undefined

//  console.log output of dataWrapper:
Object
  $log: Object
  $q: Q(resolver)
  __proto__: smDataWrapperService
    constructor: smDataWrapperService($q, $log)
    data: data()
    __proto__: Object

BUT...

I can new the dataWrapperService manually and that works just fine.

var dataWrapper = new smDataWrapperService("hello", "sir");
dataWrapper.data();   // "hello"

What am I missing here?

UPDATE:


This seems to be happening only in promise callbacks:

I usually pass functions to then / catch like this:

$http.get('whatever').then(dataWrapper.data);

But only the following will work:

$http.get('whatever').then((response) => smDataWrapper.data(response))

Upvotes: 8

Views: 4661

Answers (3)

Christiaan Westerbeek
Christiaan Westerbeek

Reputation: 11137

With Angular 1.6.4, I'm getting the error when I'm trying to inject a service that is defined as a class in some controller.

Cannot call a class as a function

So instead, I want to use a function as a service. To keep it as clean as possible I would export a function that immediately returns an instantiated anonymous class. And do this in a separate file and import and use that function to create the service in another file.

And since you're asking to use ES6 with Babel, use the babel-plugin-angularjs-annotate to safely inject dependencies by adding '/* @ngInject */' above the exported function.

The injected services $q and $log or available to the class by closure. (No need to pass them in the constructor and assign to this.)

// dataWrapperService.js
/* @ngInject */
export const dataWrapperService = ($q, $log) => new class {
  constructor() {
    // do stuff
  }

  data() {
    // $q available through closure
    console.log($q)
  }
}()

// in your.module.js
import angular from 'angular'

import { dataWrapperService } from './dataWrapperService'

export const YourModule = angular
  .module('yourmodule', [])
  .factory('dataWrapper', dataWrapperService)
  .name

Upvotes: 0

SMSk
SMSk

Reputation: 705

I know this is late, but maybe somebody will stumble upon this issue like me today with similar problems, so...

Actually, it's not about promises. When you do this:

let function1 = () => {}
let function2 = function1

actually, this object is different for the functions. Therefore, when you try .then(function1), actually function1 is being copied to the param successCallback and it's this is changed.

But when you use .then(() => function1()), successCallback receives your lambda, and the actual this of function1 is not lost.

So if you do not know, what you do never assign a function to a function in javascript, use lambdas instead

Upvotes: 2

AWolf
AWolf

Reputation: 8970

Angular needs a function at app.factory('dataWrapper', dataWrapperService); and not a class.

You could add a static factory method to your class and add that to app.factory. (see update below code snippet)

The code like this should work:

class dataWrapperService {

    constructor($q, $log) {
        this.$q = $q;
        this.$log = $log;
    }

    data() {
        console.log(this.$q);
    }

    static dataWrapperFactory($q, $log) {
        dataWrapperService.instance = new dataWrapperService($q, $log);
        return dataWrapperService.instance;
    }
}

dataWrapperService.$inject = ['$q', '$log'];

app.factory('dataWrapper', dataWrapperService.dataWrapperFactory);

Update

So as mentioned in the comments your code should work because an ES6 class is a constructor function and that's what angular.service is expecting.

I'll check later if I can see any other issue with your code.

Upvotes: 3

Related Questions