0

"angular2": "2.0.0-beta.17",

I'd like to be able to import { Http, Response } from 'angular2/http'; in my Base and use http in the child classes.

Is there a way to achieve this ?

P.S. I'm not looking for a "clean" solution, hacks, workarounds and things like this are welcome

The base class:

import { Http, Response } from 'angular2/http';

export class ServiceBase {
  constructor (private http: Http) {}

}

And a child class:

import { ApiServiceBase } from '../../api-service-base';
import { Injectable }     from 'angular2/core';
// import { Http, Response } from 'angular2/http';
import { AuthUser }       from './auth_user';
import { Observable }     from 'rxjs/Observable';
import { Headers, RequestOptions } from 'angular2/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class LoginService extends ApiServiceBase {
  constructor () {
    super();
  }
  private url = 'http://localhost:8080/api/signin';

  login (user: AuthUser): Promise<AuthUser> {
    let body = JSON.stringify(user);
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });
    return this.http.post(this.url, body, options)
               .toPromise()
               .then(this.extractData)
               .catch(this.handleError);
  }

  private extractData(res: Response) {
    console.log(res);
    if (res.status < 200 || res.status >= 300) {
      throw new Error('Bad response status: ' + res.status);
    }
    let body = res.json();
    return body.data || { };
  }

  private handleError (error: any) {
  }
}
waaadim
  • 409
  • 5
  • 17
  • Why don't you want the import in the child class? – Luka Jacobowitz May 09 '16 at 14:03
  • 1
    But I thought, you don't care about a "clean" solution? This is going to be way less "hacky" then the alternative, no? – Luka Jacobowitz May 09 '16 at 14:10
  • Adding all of this extra complexity just for the sake of avoiding a one line `import` seems unnecessary. In fact, you already have an import from `angular2/http` in your child class for `Headers` and `RequestOptions` - why not just add `Http` and `Response` to that? – Joe Clay May 09 '16 at 14:12
  • it's not just one line, it's one line for every child service. and this will come in handy when I'll want to add something to all my `child services` – waaadim May 09 '16 at 14:15

1 Answers1

3

Using a parent class to define dependency injection of sub classes isn't supported in Angular2.

The only thing you can do here if you want to use the http instance in the parent class:

@Injectable()
export class LoginService extends ApiServiceBase {
  constructor (http:Http) {
    super(http);
  }

  (...)
}

Edit

A workaround would consist of defining a custom decorator to set the metadata for dependency injection:

export function CustomInjectable(annotation: any) {
  return function (target: Function) {
    var parentTarget = Object.getPrototypeOf(target.prototype).constructor;
    var parentAnnotations = Reflect.getMetadata('design:paramtypes', parentTarget);

    Reflect.defineMetadata('design:paramtypes', parentAnnotations, target);
  }
}

It will leverage the metadata from the parent constructor instead of its own ones. You can use it on the child class:

@Injectable()
export class BaseService {
  constructor(protected http:Http) {
  }
}

@CustomInjectable()
export class TestService extends BaseService {
  constructor() {
    super(arguments);
  }

  test() {
    console.log('http = '+this.http);
  }
}

See this plunkr: https://plnkr.co/edit/DIMyUB6rCE5d78dPlPZB?p=preview.

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • that's why I'm looking for a hack to achieve this – waaadim May 09 '16 at 14:00
  • I'm looking for a way to avoid the `import` in the child class – waaadim May 09 '16 at 14:03
  • would it be possible to use grunt or something like this for this task. Like a preprocessor of some sort, that will import everything for me, since the `ts` has to be transformed to ES5, it should be possible to add an additional step to the transpilation process that would handle the imports – waaadim May 09 '16 at 14:06
  • The problem is that it's the `Injectable` decorator that actually configures the injection basing the constructor of the sub class... – Thierry Templier May 09 '16 at 14:13
  • An approach would be to implement a custom decorator that looks into the parent class instead of the current one to configure DI. In this case, you wouldn't need to define a constructor in the child class. I implemented something like that but for components. See this question: http://stackoverflow.com/questions/36837421/extending-component-decorator-with-base-class-decorator/36837482#36837482. Perhaps something like that could be done... Let me make a try ;-) – Thierry Templier May 09 '16 at 14:17
  • I updated my answer and provided a working plunkr for this approach. Feel free to tell me if it suits your needs... – Thierry Templier May 09 '16 at 16:41
  • wow, you know your stuff for sure. can you suggest me some books/resources to read ? I never really understood how javascript works, the books that I tried explained things very shallow.. thanks a lot – waaadim May 09 '16 at 18:35
  • Hi, I'm getting this error with your example: [ts] Supplied parameters do not match any signature of call target. function CustomInjectable(annotation: any): (target: Function) => void – Sam Jason Braddock Oct 14 '16 at 16:01
  • from your plunker console: `Array [ Http() ] main.ts!transpiled:24:13 http = [object Arguments]` if I amend super call with spread operator `super(...arguments);` then it outs: `Array [ Http() ] main.ts!transpiled:24:13 http = undefined` how do you think this should work? – slowkot Jul 04 '17 at 15:59