EXCEPTION: No provider for UserData! (LoginPage -> UserData)

That was weird. I couldn’t reproduce in the demo environment you provided a link to, so I generated a new project:

ionic start NewMyApp -t tabs --v2 --ts

Then I replaced the newly created app dir with the problematic one from my existing project, ran ionic serve and the error was gone.

Anyway, that actually takes me back to where my problems initially started, and that is when I try to inject a dependency into UserData.

I added a new service, app/providers/some-service.ts:

import {Injectable} from 'angular2/core';

@Injectable()
export class SomeService {

	public constructor() {

	}

}

And added it to UserData by changing app/providers/user-data.ts to:

import {Injectable} from 'angular2/core';
import {SomeService} from './some-service';

@Injectable()
export class UserData {

	public constructor(someService: SomeService) {

	}

}

And I get the following error:

EXCEPTION: No provider for SomeService! (MyApp -> UserData -> SomeService)

Now, I can get rid of the error by importing SomeService in app.ts and adding SomeService to @App’s providers array, but that seems wrong? Why should I need to add a dependency (SomeService) of a dependency (UserData) at @App level?

Because the dependency injection system needs to know at which level a SomeService should be held. If you put it in the providers array of UserData itself, every UserData has its own SomeService. If you put it in the providers array of the app, all instances of UserData (and all other classes that inject a SomeService) share the same instance of SomeService (which is generally what you want).

rapropos: Great! Then I think I’ve understood so far.

However, I’m a bit confused about how to add SomeService directly to UserData, if that was desired, since it doesn’t have a decorator for the providers array? I don’t see anything in angular 2 docs.

Can you give me an (additional) push in the right direction? Much appreciated!

If UserData is going to get an Ionic @Page decoration, it can hold a providers array. If not, you can use the stock Angular @Component decorator, which also takes a providers array. More detailed info and other options in this article.

1 Like

The situation you describe is in fact referenced in the Angular 2 docs:
https://angular.io/docs/ts/latest/guide/dependency-injection.html#when-the-service-needs-a-service

Please have a look at that section. You do still have to add the service to a providers list somewhere. You could even just do so at the app level if you’re concerned about reuse in different portions of the app. Also somewhat relevant: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html

1 Like

When you import services, the order is very important

I’ve read that article multiple times in my Google Quest for knowledge, actually :wink: Unfortunately I didn’t get any closer to solving my issue.

If I understood you correctly, this is supposed to work, right?

import {Component} from 'angular2/core';
import {SomeService} from './some-service';

@Component({
	providers: [SomeService]
})
export class UserData {

	public constructor(someService: SomeService) {

	}

}

I’m asking because I get the same error

EXCEPTION: No provider for SomeService! (MyApp -> UserData -> SomeService)

Indeed it is!

I wasn’t sure it was what I needed since the example looks like to be declaring a directive in Angular 1 (I’m saying that because of selector in @Component), and all the other examples either puts the services in bootstrap or at root level.

I gave it a shot, I replaced @Injectable() with with @Component({providers: [SomeService]}) in some-service.ts, and I still get the same error for some reason. Maybe I didn’t quite understand it?

Can you try to add this :

export class LoginPage {
  static get parameters() {
    return [[UserData]];
  }
}

And keep your UserData like the beginning :

@Injectable()
export class UserData {

	public constructor() {

	}

}

You can check a real example here : https://github.com/driftyco/ionic-conference-app/blob/master/app/pages/schedule/schedule.js#L13

Didn’t do a difference, unfortunately. (The code you provided is what the TypeScript transpiler is supposed to generate when it converts TypeScript to ECMA5, right?)

I think you may have been looking at the wrong thing. Look at the relationship between hero.service and logger.service. Both are services. Logger.service is used inside hero.service:

logger.service.ts

import {Injectable} from 'angular2/core';

@Injectable()
export class Logger {
  logs:string[] = []; // capture logs for testing
  log(message: string){
    this.logs.push(message);
    console.log(message);
  }
}

hero.service.ts

import {Injectable} from 'angular2/core';
import {Hero}       from './hero';
import {HEROES}     from './mock-heroes';
import {Logger}     from '../logger.service';

@Injectable()
export class HeroService {
  constructor(private _logger: Logger) {  }
  getHeroes() {
    this._logger.log('Getting heroes ...')
    return HEROES;
  }
}

There is your service in a service relationship. But then notice farther down in that same section:

We’re likely to need the same logger service everywhere in our application, so we put it at the root level of the application in the app/ folder, and we register it in the providers array of the metadata for our application root component, AppComponent.

app/app.component.ts (excerpt)
providers: [Logger]

If we forget to register the logger, Angular throws an exception when it first looks for the logger:

EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)

That’s Angular telling us that the dependency injector couldn’t find the provider for the logger. It needed that provider to create a Logger to inject into a new HeroService, which it needed to create and inject into a new HeroListComponent.

So, the point is you must put both services into a provider array somewhere in your app. Once you do that you can inject the service into another service by just importing it and using the normal constructor injection:
constructor(private _logger: Logger) { }

Let’s say my UserData service needs to make requests to a server, and I create a nice ApiService class for that purpose. In my mind the app itself shouldn’t know about ApiService since it is only going to communicate with the UserData service and shouldn’t care about the implementation details inside of UserData.

By putting ApiService in @App providers array you create a tight coupling between the app and ApiService and leak implementation details (sort of). Down the road I should be able to able to change UserData’s dependency on ApiService to something else by only changing user-data.ts, without ever touching other files, shouldn’t I?

Again, maybe I misunderstood something.

No, I think you are understanding, and I’m not disagreeing with you. I’m only trying to say that this is how Angular 2 is designed.

I can understand why though. You want a service to be a singleton. Putting your service in the providers array is creating an instance of that service. Since you want a single instance of the service to be used by all your components, it makes sense to instantiate it at the app level so it can be used everywhere. If you add it to providers array in a bunch of separate child components you will get a separate instance in each rather than them sharing the same one.

Also, you can change the UserData’s dependency on ApiService by only changing user-data.ts. Just because UserData, ApiService, and SomethingElse were all instantiated at the app level doesn’t affect where or how each is used. You could have all three in the providers array in the app, and then change the dependency of UserData on ApiService by simply changing

@import ApiService from './api-service';

export class UserData {
  constructor(private apiService: ApiService){}
}

into

@import SomethingElse from './something-else';

export class UserData {
  constructor(private somethingElse: SomethingElse){}
}

And UserData, ApiService and SomethingElse would be instantiated in the app’s provider array:
providers: [UserData, ApiService, SomethingElse]

But I do get what you are saying. In Angular 1 all services were singletons and could simply be injected without the extra step of needing to instantiate them like we do in Angular 2 with the providers array.

You’d still have to add whatever you decided should replace ApiService to @App’s providers array, though, so you’d need to edit user-data.ts and also app.ts, and if ApiService was only ever used in UserData you now have a unused component being instantiated unless you remove it.

I’ve purchased a couple of Angular 2 eBooks and am hoping they will prove us both wrong by showing it is possible to avoid the above problems somehow :wink:

By the way, do you know why I have no trouble using Angular’s Http service within UserData, without adding it to @App providers array? It works exactly like I hoped custom services/components would, but I don’t understand why.

If you can prove us both wrong please do share!

Regarding Http service, what you are saying isn’t really true. Ok, technically yes you don’t have to put Http in the providers array, but you do have to put HTTP_PROVIDERS in the providers array. As you can probably guess from the name HTTP_PROVIDERS, this puts Http on the providers array for you, along with a couple other things. So you could always group the things you want in the providers into contants like this, but the fact remains you must put them into the providers array one way or the other.

Ionic does that for you. The @App decorator adds a bunch of directives and providers automatically, and HTTP_PROVIDERS is among them.

1 Like

Oh, my bad, I was in vanilla Angular 2 land again :slight_smile:

In regular Angular 2 you do have to add HTTP_PROVIDERS, but I guess Ionic just does it for you. Thanks for adding that :thumbsup:, I was not aware.

Unfortunately it seems like it is indeed by design and I’m not holding my breath hoping for it to change.

There’s an ongoing discussion about it here: https://github.com/angular/angular/issues/5622

A sort of workaround to decouple service from inner dependencies could be to export a constant array (export constant SOME_SERVICE = [SomeService, OtherServiceUsedBySomeService …] and add that to component’s providers array instead.

I guess that is the same pattern already used in Angular2 when considering HTTP_DIRECTIVES etc

2 Likes

Same problem here, I lost almost 1 day looking for a solution and finally found your answer, quite disapointing.
It seems the @App will be quite messy with all the providers that I’ll use on sub Components.
At least I can stop looking now, thanks for the feedback.

I had the same problem. I have an app.ts and a login.ts and an user-data.ts. All started to work when I put UserData as a provider in the app.ts.

@Component({
  templateUrl: 'build/app.html',
  directives: [ROUTER_DIRECTIVES], 
  providers: [
    { 
      provide: TranslateLoader, 
      useFactory: (http: Http) => new TranslateStaticLoader(http, 'assets/i18n', '.json'),
      deps: [Http]
    },
    TranslateService,
    ROUTER_PROVIDERS,
    UserData
  ]
})

Hope it helps

1 Like