0

The problem

For the past week I've been working on a authentication library for an Angular2 project. This library is mostly service based and should work almost directly out of the box. The only thing you need to do is include it in your app and set the config object for it.

The main service here is the AuthService. This service includes both the Http and another custom service. The thing is that both these dependencies require the developer to point out the providers for these classes. I'm not sure If I've got this right, but I've seen solutions where they need to import HTTP_PROVIDERS in order to use Http.

So if I import the AuthService as a dependency, I still need to import both the HTTP_PROVIDERS and custom service to make the DI work.

bootstrap(App, [AuthService, HTTP_PROVIDERS, CustomService]);

This messes up my idea of the module being easy to include. I'd like to only supply the import AuthService.

Thing I tried so far

First of all I started out reading some of the blogs about DI in Angular2. As most of you may know Thoughtram is doing a great job on supplying this information (also EggHead.io). I ended up reading these two posts:

http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html http://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html

After reading them I thought the solution was simple, but after a while this seemed not the case. I guess I'm missing the point here. Also working Spring boot where DI is the main selling point, it seemed odd that I did not manage to get it working (might be the fact that Angular2 is still in Alpha and documentation is scarce).

Solutions I tried

So I tried some solution I came up with, but none of them worked.

Providing a constant

My first thought was to look at the HTTP_PROVIDERS constant that was like a provider for the Http dependency. I ended up looking at the source code for it.

https://github.com/angular/angular/blob/b0009f03d510370d9782cf76197f95bb40d16c6a/modules/angular2/http.ts#L151

After that I thought that I'd best try to implement this for my own class.

export const AUTH_PROVIDERS: any[] = [
  provide(AuthService, {
    useFactory: (http, customService) => new AuthService(http, customService),
    deps: [Http, CustomService]
  })
  ,
  provide(CustomService, {
    useFactory: () => new CustomService(),
  })
];

Once completed I included it in my bootstrap function, after which I was greeted with another error.

Token must be defined!

I took some time trying to find the problem, but I did not manage to do so.

Using an annotation

One of the new things in Angular2 is annotations. So I hoped one of these would be able to help me out with my problem. The @Component and @View seemed not the ones I needed. I believe they focus more on visual aspects of an object that functional (as the View is not something I need, but is mandatory).

The next part can seem odd as I could not find if this was an annotation or not (probably not...). I tried giving @Provider a shot. That resulted in the following "possible" solution.

@Provider({
  token: 'AuthService',
  useFactory: (http, customService) => new AuthService(http, customService)
  ...
})
export class AuthService{
  constructor(http: Http, customService, CustomService){...}
}

This just gave me another error, but this time it was thrown by the Typescript compiler.

error TS2348: Value of type 'typeof Provider' is not callable. Did you mean to include 'new'?

Anyways can someone please help me with this. Is one of the solutions above a step in the right direction or am I doing something horribly wrong?

Thanks in advance.

1

1 Answer 1

2

Well, I found the problem, but it's something I hadn't thought of before (probably cause I work mainly with Java). One of the things Typescript (and eventually JavaScript) does not like is referencing a Class that's defined below the piece of code where you need it. I guess that's where the 'Token must be defined!' error comes from. Anyways switching the code pieces solved the problem.

export class AuthService{...implementation...}

export const AUTH_PROVIDERS : any[] = [
  HTTP_PROVIDERS,
  provide(AuthService, {
    useFactory: (http, customService) => new AuthService(http, customService),
    deps: [Http, CustomService]
  }),
  provide(CustomService, {
    useFactory: () => new CustomService()
  })
];

Hope this may help others in the future.

Sign up to request clarification or add additional context in comments.

1 Comment

That's right, class declarations aren't hoisted. To use a class before it's defined you should use forwardRef(): angular.io/docs/js/latest/api/core/forwardRef-function.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.