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

Hi guys,

I’m banging my head against a wall here, hoping you can help. I’m not sure if I’ve run into a bug, it’s a setup issue or if I misunderstood something.

The error I’m getting when running the app is:

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

I have imported UserData in both my app.ts and login.ts, and also included it in @App decorator providers array, which to my understanding is the only place I should define it as a provider to make sure I work with the same instance app-wide.

I’ve stripped the code way down to the smallest example I could come up with and still have the error occur.

Any idea what’s going on?

app/app.ts:

import {App} from 'ionic-angular';
import {UserData} from './providers/user-data';
import {LoginPage} from './pages/login/login';

@App({
	template: '<ion-nav [root]="rootPage"></ion-nav>',
	providers: [UserData]
})
export class MyApp {

	public rootPage: any = LoginPage;

	public constructor(protected userData: UserData) {

	}

}

app/pages/login/login.ts:

import {Page} from 'ionic-angular';
import {UserData} from '../../providers/user-data';

@Page({
	templateUrl: 'build/pages/login/login.html'
})
export class LoginPage {

	public constructor(protected userData: UserData) {

	}

}

app/providers/user-data.ts:

import {Injectable} from 'angular2/core';

@Injectable()
export class UserData {

	public constructor() {

	}

}

Ionic info

bash-3.2$ ionic info

Your system information:

Cordova CLI: 6.0.0
Gulp version:  CLI version 3.9.1
Gulp local:   Local version 3.9.1
Ionic Version: 2.0.0-beta.3
Ionic CLI Version: 2.0.0-beta.22
Ionic App Lib Version: 2.0.0-beta.12
ios-deploy version: 1.8.5
ios-sim version: 5.0.8
OS: Mac OS X El Capitan
Node Version: v4.4.0
Xcode version: Xcode 7.3 Build version 7D175

Try to put that in your login.ts

@Page({
	templateUrl: 'build/pages/login/login.html',
        providers: [UserData]
})
1 Like

Hi rodrigoreal,

Then the app will run without errors, but UserData in app.ts and login.ts will be different instances which I don’t want them to be.

See this link: http://ionicframework.com/docs/v2/faq/

Adding providers to every component when you mean to have the same provider instance injected to each component (services for example). For a class to be injectable it only needs to be in the providers array of that component or any parent component (for example @App), but not both. Putting it in both the component that has that provider injected in addition to a parent or ancestor will create two separate provider instances.

looks good… Do you sure what this example like the original code?

I can’t reproduce the problem, maybe it’s something environment-related. I created a basic demo environment. Could you check it out and update it as needed to see if you can reproduce it there?

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.