8

I am trying to wire up a basic Angular2 app that uses the Http service. (Most of the tutorials I've seen do this by having a Component consume the Http service, which seems wrong unless the basic philosophy of thin controllers has changed – but that's a different question.)

I would like to create a service that uses Angular's Http service. But I can't figure out how to inject the Http service other than this:

boot.ts:

import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {HTTP_PROVIDERS } from 'angular2/http';

bootstrap(AppComponent, [HTTP_PROVIDERS]);

myService.ts:

import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';

@Injectable()
export class aService{
    constructor(http:Http){
    }
    /** do some stuff *//
}

This works, but it seem very wrong to require the user of the service to know the service's dependencies and be required to inject them into the bootstrap process. It seems like there should be a way to directly hand a providers array to a service the same way you can a component, but I can't find it. Am I just missing something?

Mark
  • 90,562
  • 7
  • 108
  • 148
  • "Unless the basic philosophy of thin controllers has changed" -- it hasn't. To quote the [Architecture Overview](https://angular.io/docs/ts/latest/guide/architecture.html) guide: "A component's job is to enable the user experience and nothing more. It mediates between the view ... and the application logic... It delegates everything non-trivial to services." – Mark Rajcok Jan 05 '16 at 03:41
  • Thanks @MarkRajcok - this is how I think it should be done. My confusion comes from the fact that the documentation shows controllers doing a lot more than that. ( For example: https://angular.io/docs/js/latest/api/http/Http-class.html ) If the components are supposed to be really thin, they probably won't be dealing with http very often, but the docs imply that it is normal. – Mark Jan 08 '16 at 05:06
  • Well, the examples in the API docs are unlikely to follow best practices for architecting an app, and that's probably reasonable. – Mark Rajcok Jan 08 '16 at 16:02

1 Answers1

3

Update

This way if a parent injector provides an implementation for OtherService this one is used, otherwise OtherServiceImpl is used (default).

@Injectable()
class SomeService {
  OtherService _other;

  SomeService(Injector injector) {
    _other = injector.getOptional(OtherService);
    if (_other == null) {
      _other = injector.resolveAndCreateChild([
        provide(OtherService, useClass: OtherServiceImpl)
      ]).get(OtherService);
    }
    _other.doSomething();
  }
}

If you provide another one like

bootstrap(AppElement, [
  provide(OtherService, useClass: OtherServiceImpl2)
]);

OtherServiceImpl2 is used.

See also https://github.com/angular/angular/issues/5622

Original

You could just make the http service optional (using the @Optional() annotation) and if none is provided just create an instance inside the constructor with new Http(). This way the user doesn't need to know about the services dependencies, but is able to pass alternative implementations if necessary (for example for testing). If creating the dependeny inside the service requires DI itself, you can inject an injector and use it to get dependencies. See also optional dependencies in http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html

What also could work (not tried myself yet) is just to create a child injector and instruct it to skip self

From the SkipSelfMetadata documentation

  class Dependency {
  }

  @Injectable()
  class NeedsDependency {
    dependency;
    constructor(@SkipSelf() dependency:Dependency) {
      this.dependency = dependency;
    }
  }

  var parent = Injector.resolveAndCreate([Dependency]);
  var child = parent.resolveAndCreateChild([NeedsDependency]);
  expect(child.get(NeedsDependency).dependency instanceof Depedency).toBe(true);

  var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
  expect(() => inj.get(NeedsDependency)).toThrowError();

I don't know yet if this still resolves from "self" if parent can't provide the requested type.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I thought calling `new` yourself to create a dependency is frowned upon. – Mark Rajcok Jan 05 '16 at 03:56
  • You are right. If the instantiated class has dependencies, this doesn't even work. For a fully working example a child injector needs to be created and the instance acquired from this child injector. I'll update the example. – Günter Zöchbauer Jan 05 '16 at 05:13
  • 1
    A good call on [the issue](https://github.com/angular/angular/issues/5622). It looks like a basic thing from 1.x like having a reusable heap of services (aka module) still can't be solved in straightforward manner in beta0. – Estus Flask Jan 06 '16 at 11:45
  • Thanks @GünterZöchbauer - that's helpful, as is the discussion. – Mark Jan 08 '16 at 04:48