To make it easier to build services, I built an abstract class with helper methods for connecting to my API:
import { Headers } from '@angular/http';
export abstract class ApiServiceBase {
static apiHost = 'https://localhost:3000';
constructor() {}
public applyHeaders(headers: Headers=new Headers()) {
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
headers.append('Authorization', 'Bearer ' + localStorage.getItem('authToken'));
return headers;
}
public buildUrl(path: string) {
console.log(`Using ${this.apiHost} to build url for ${path}`);
return this.apiHost + path + '.json';
}
}
When I try to extend the method, like the following, I get a number of errors:
...
import { ApiServiceBase } from 'api-service-base';
@Injectable
export class TripService extends ApiServiceBase {
constructor(public http: Http) { super(); }
get() {
return new Promise((resolve, reject) => {
this.http.get(this.buildUrl('/trips'), { headers: this.applyHeaders() })
.map(res => res.json())
.subscribe(trips => {
let tripArr: [Trip] = trips.map(trip => new Trip(trip));
resolve(tripArr);
}, error => reject(error));
});
}
}
The errors I get:
- On
ApiServiceBase
from export class TripService extends ApiServiceBase
I get Type 'any' is not a constructor function type
- On calls to
buildUrl
I get Property 'buildUrl' does not exist on type 'TripService'
- On calls to
applyHeaders
I get Property 'applyHeaders' does not exist on 'TripService'
This is a project that I’m trying to upgrade from Ionic 2 beta 11 to Ionic 2.1.0 release and it worked just fine on Ionic 2 beta 11.
If you don’t get any better answers, I would rearchitect this to use composition instead of inheritance. Largely due to the lack of runtime class information in objects, I think inheritance in TypeScript is a bit of a minefield.
Mind helping me with the composition logic? In other languages, I generally do something to include the methods from the APIBaseService into my Services, but I’m not super familiar with TypeScript.
export class TripService {
constructor(private _api: ApiService, private _http: Http) {
}
get(): Observable<Trip[]> {
return this._http.get(this._api.buildUrl('/trips'), { headers: this._api.applyHeaders() }).map((rsp) => {
return rsp.json() as Trip[];
});
}
}
Everywhere you’d be using a superclass function, you delegate explicitly to the ApiService
. I actually find this much more readable than using inheritance as it’s instantly clear where various functions are defined. Also notice no need for instantiating Promises: I know that boilerplate code is omnipresent, but I’ve railed against it several times for being antipattern stew.
You are explicitly constructing Trip
objects. If you still need to do that, fine, but what I do is to simply make all of my objects that go over the wire interfaces, at which point either single ones or arrays of them just reanimate with a single json()
call.
So I switched to having ApiService
as a standard class, added it to my providers
definition in app.module.ts
and am trying to inject it into my other services. I don’t get any errors on imports, but on my constructor, which now is:
import { ApiService } from 'api-service';
constructor(public http: Http, _api: ApiService) {...}
I get an error Cannot find name 'ApiService'.
on the constructor portion. The import doesn’t error at all, so I’m not sure what I’m missing.
Is ApiService
also decorated as @Injectable()
? Also note that you’re going to need an access qualifier on _api
in order to be able to access it outside of that constructor.
This is now my ApiService
:
import { Injectable } from '@angular/core';
import { Headers } from '@angular/http';
@Injectable()
export class ApiService {
apiHost = 'http://localhost:3000';
applyHeaders(headers: Headers = new Headers()) {
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
headers.append('Authorization', 'Bearer ' + localStorage.getItem('authToken'));
headers.append('User-Agent', 'Go Rudys ' + navigator.userAgent)
return headers;
}
buildUrl(path: string) {
console.log(`Using ${this.apiHost} to build url for ${path}`);
return this.apiHost + path + '.json';
}
}
And one of my services that uses it:
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/map';
import { ApiService } from 'api-service';
@Injectable()
export class AirlineService {
data: any;
constructor(private http: Http, private _api: ApiService) {
this.data = null;
}
load() {
// already loaded data
if (this.data) { return Promise.resolve(this.data); }
// don't have the data yet
return new Promise(resolve => {
this.http.get(this._api.buildUrl('/airlines'), { headers: this._api.applyHeaders() })
.map(res => res.json())
.subscribe(data => {
this.data = data.map(airline => airline as Airline);
resolve(this.data);
});
});
}
}
And the error I get from ionic build
:
[16:01:42] typescript: src/providers/airline-service.ts, line: 10
Cannot find name 'ApiService'.
L10: constructor(private http: Http, private _api: ApiService) {
L11: this.data = null;
Try making that from ‘./api-service’ instead.
That worked. Thanks @rapropos!