1

I want to isolate http interactions by creating data access objects from a class so that in a component I might simply get data like this:

// dashboard.component
import { AppUser } from './appuser.service'

export class DashboardComponent implements OnInit {
  user: AppUser = new AppUser();

  constructor() { }

  ngOnInit() {
    let id = JSON.parse(window.localStorage.getItem('session')).userId;
    this.user.find(id) // 'find' is from base class
      .subscribe(
        // handle user data
      );
  }
}

I have defined a base class and a sub class like this:

// base-resource.service
import { HttpClient } from '@angular/common/http';
...
export class BaseResource {
  private fullpath: string;
  protected http: HttpClient;

  constructor (path: string) {
    this.fullpath = path;
  }

  find (id): Observable<Object> {
    return this.http.get(this.fullpath + '/' + id); // this line throws Error!
  }
}

// app-user.service
...
export class AppUser extends BaseResource {
  constructor(data?) {
    super('api/appusers');
  }
}

However this generates an error: ERROR TypeError: Cannot read property 'get' of undefined from within the base class function.

My 'AppUser' instance is clearly inheriting find from 'BaseResource', but find is picking up the 'AppUser' instance as the value of this and http is not available. I have tried declaring http as public and private as well as protected, but that had no effect. I imagine I'm missing some bigger picture of how to extend classes.

As specifically as possible, i think my question is in how to abstract functions to a base class when they need access to the base class's context.

(using Angular 6.0.4)

EDIT I updated the title as it became clear that this is a problem of instantiating the HttpClient service in a class.

gkl
  • 199
  • 2
  • 11
  • Is it throwing a compilation eror or a runtme error? From what I can see, you never initialise `http` anywhere, so it'll be undefined. – user184994 Jul 16 '18 at 17:52
  • The error is a runtime error. I'm importing `HttpClient` in BaseResource. It is not injected via the constructor, because (I think) that prevents "newing up" an instance. I certainly could be wrong on that particular point, though. – gkl Jul 16 '18 at 17:56
  • 1
    Yeah, importing it doesn't create an instance, it's just needed for type safety. Will you ever need to inject `BaseResource` directly, or will it always be a class that extends `BaseResource`? – user184994 Jul 16 '18 at 17:58
  • If I still need to instantiate it, maybe https://stackoverflow.com/questions/49507928/how-to-inject-httpclient-in-static-method-or-custom-class has my answer. It is my intent to never use BaseResource directly. – gkl Jul 16 '18 at 18:06

1 Answers1

1

The error is because nothing is instantiating HttpClient, so it is undefined when you come to use it.

You should inject HttpClient into AppUser, and pass it into BaseResource via the constructor

export class AppUser extends BaseResource {
  constructor(HttpClient http) {
    super(http, 'api/appusers');
  }
}

And in base-resource.service

import { HttpClient } from '@angular/common/http';
...
export class BaseResource {
  private fullpath: string;
  protected http: HttpClient;

  constructor (httpClient: HttpClient, path: string) {
    this.fullpath = path;
    this.http = httpClient;
  }

  find (id): Observable<Object> {
    return this.http.get(this.fullpath + '/' + id); // this line throws Error!
  }
}
user184994
  • 17,791
  • 1
  • 46
  • 52
  • I made a quick pass at passing it in and was able to access `http`; its definitely missing instantiation. However, it seems 'wrong' at some level to be passing in an 'HttpClient' instance in every call to an instance of this class. If I needed a dozen services, or if I needed to add one more service it seems I'd have to update every call which is what I wanted to avoid by abstracting in the first place. – gkl Jul 16 '18 at 18:23