13

I have a typescript class representing a model and I would like instances to communicate with an API via angular's Http service.

But the constructor of the model needs arguments when creating instances. For example something super simple:

class SomeModel{
    constructor(public id:number, public name:string, ){
    }

I would like to inject the Http service so it is available to my instances, but it seems like the canonical way to do this commandeers the constructor with:

constructor(http:Http)

I've been digging through the Injector docs, but it's a little sparse and I haven't found anything that works. Is there a way to get a reference to a service like Http from the DI system without using the constructor pattern?

Mark
  • 90,562
  • 7
  • 108
  • 148
  • Maybe you could use `provide`, like when [testing Http](http://pastebin.com/UAJ3XeRf), but it's ugly (; I'd keep models clean and leave fetching to the components/services... – Sasxa Jan 22 '16 at 01:16
  • Thanks, I appreciate that @Sasxa. The application is a bunch of different kinds of nodes that are saved to a graph DB via an API. Each kind of node has a slightly different requirement and it works well to model this as subclasses of an abstract node class. To save I can just say `nodeInstance.save()` rather than having a bunch of logic in a service to determine what kind of node it is and how to save/retrieve it and it's relationships. It's possible it's a wrongheaded approach, but it seems sustainable to me if I could find a nicer way to get the Http reference. – Mark Jan 22 '16 at 01:30
  • I've been looking through docs, specifically [http](https://angular.io/docs/ts/latest/api/http/Http-class.html) and [xhrbackend](https://angular.io/docs/ts/latest/api/http/XHRBackend-class.html), maybe [this](http://pastebin.com/bQcJALjA) will work for you... – Sasxa Jan 22 '16 at 01:34
  • I don't think Angular was meant to be used like this, but you got me curious. Are you trying to do some kind of Domain Model Architecture in client side? As for what you are trying to do, just annotate your Model class with `@Injectable` and receive the `http` object in the constructor just like you would with any other `@Component`, there's also an `@Inject` annotation that you can use in the specific property you want to inject. – Langley Jan 22 '16 at 01:34
  • Yes, @Langley I am modeling a Neo4j DB on the client. I have a working NG-1 app, but the ability to use Typescript classes and typing has inspired me to try to make it less spaghetti-like and easier to maintain. I'm not sure I understand you suggestion though: if I receive the `http` object in the constructor, how do I pass other arguments on the same constructor? i.e I want to create an instance with `new myClass(arg1, arg2)` but the constructor is calling for `constructor(http:Http, arg1:someType, arg2:someOtherType)` – Mark Jan 22 '16 at 01:53
  • Thanks @Sasxa - I'm looking at those, now. I was hoping there was a simpler solution. I thought it would be common to use plain Typescript instances to model the various entities in the application, so I expected I was just overlooking an obvious answer. Maybe not. – Mark Jan 22 '16 at 01:56
  • I didn't know you could run Neo4j DB in the client. That's why I told you Angular isn't meant to be used like that, Injection works when you either let Angular instantiate the Objects for you, so you don't have to call `new` or when you specify a provider that tells him how to instantiate them. If you want to call the constructor yourself, then you have to give him the `http` object yourself. You'd inject it into the component that calls this model, and when you create the model you pass it as any other parameter. – Langley Jan 22 '16 at 02:04
  • @Langley I'm not running Neo4j in the client. I'm interacting with it's http API. Right now I am passing the http object to a class method and storing it on a static property from the service (where it is properly injected) that's acting as a instance factory. This is working alright, but seemed un-angular like. – Mark Jan 22 '16 at 02:09
  • 1
    Oh then it doesn't matter that you are using Neo4J, you are just accessing an http API, this works for me: `console.log(Injector.resolveAndCreate(HTTP_PROVIDERS).get(Http).post);` you might want to play around with the different options: https://angular.io/docs/ts/latest/api/core/Injector-class.html – Langley Jan 22 '16 at 03:21
  • @Langley - yes, that is the answer. Now I can add this: `this.http = Injector.resolveAndCreate(HTTP_PROVIDERS).get(Http)` to my constructor and resolve the Http dependency. I appreciate this - make it an answer so I can accept. – Mark Jan 22 '16 at 05:08

2 Answers2

4

I managed to solve the same problem using angular 4. First you create new injector that uses component injector. It knows about your SomeModel class and passes modelParams as instance of SomeModelParameters class. Then you use this newly created injector to create class instance.

@Injectable()
class SomeModel {
    constructor(http: Http, someModelParamters: SomeModelParameters) { }
}

export class MyComponent {
    constructor(injector: Injector) {
        const modelParams = new SomeModelParameters();
        const injectorWithModelParams = ReflectiveInjector.resolveAndCreate(
            [
                SomeModel,
                { provide: SomeModelParameters, useValue: modelParams }
            ],
            injector);
        this.someModel = injectorWithModelParams.resolveAndInstantiate([SomeModel]);
    }
}
Stanislav Berkov
  • 5,929
  • 2
  • 30
  • 36
2

update

HTTP_PROVIDERS is long gone. HttpClientModule is the current replacement.

original

If you inject a class that has constructor parameters the @Injectable annotation needs to be added.

@Injectable() 
class SomeModel{
   // constructor(public id:number, public name:string, ){
    // }
    constructor(http:Http) {} 
} 

For this to work HTTP_PROVIDERS needs to be added to bootstrap(AppComponent, [HTTP_PROVIDERS]);

See also Angular2 beta - bootstrapping HTTP_PROVIDERS - "Unexpected Token <"

If you need to pass other arguments from your component, youcoud pass them using a function instead.

Another way is to create the instance manually and request Http from the injector.

export class MyComponent {
  constructor(injector: Injector) {
    this.someModel = new SomeModel(Injector.resolveAndCreate(Http), 1, 2);

   } 
} 
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks @Günter, I think Langley found my answer. I'm trying to pass my own arguments to my constructor while still getting access to the Http service within the SomeModel instances. – Mark Jan 22 '16 at 05:09
  • Ahh, didn't read all comments, only saw there is no proper answer. – Günter Zöchbauer Jan 22 '16 at 05:14
  • Why not simply use a factory (`useFactory` instead of `useClass`) to inject the dependency. The benefit is that you can drop all of your annotations. – Mik378 Jul 31 '17 at 19:16
  • This answer is for an 2.0.0-alpha version or early beta and I didn't know Angular too well back then. I'm not sure what the question is about exactly. I guess I was hoping for some feedback and then iterate ;-) – Günter Zöchbauer Jul 31 '17 at 19:31