Service can't resolve all its parameters using barrels


#1

@mhartington
I had an issue handling many services :sweat:
and solved it before finish this thread, but I’ll share it anyways :smile:

I have a heavy Page where I offer my main service,
so I decoupled it and now I have with six providers for it.
One of my services weren’t resolving all its parameters,
and I wen’t crazy trying to figure out what was the cause.
Basically I have:

Page
|- EventsService (event bus)
|- SysService (local notifications)
|- SocketService (socket.io)
|- DataService (main data provider)
|- MainService (main coordinator)

I have a thin Page now moving away mostly of the code to the services and I feel I’m able to maintain everything easily.
Basically all of them depends on the Event Bus which communicates them, and the MainService takes the DataService too to perform some business logic changes, and finally the Page takes the Event, Data and Main Services.

I had the problem on the MainService not being able to resolve the DataService, and I had to go deep into Reflect.js to see where the things went wild. Finally, now I’m aware that the declaration order in which Angular 2 loads the @Injectables is important.

I’m using a Barrel, and there the DataService was exported AFTER the MainService, that was the LESSON of the day. I’ll be aware to put the dependencies before on my barrels now

I hope that this will be useful for someone one day :sunglasses:


#2

I’m not sure what a Barrel is, but if it’s some sort of bundler, I think your problem is with it. Angular’s DI should not be tripped up by ordering of declarations, with the caveat that all classes should be declared in separate compilation units, lest you bang into this issue.


#3

I found the barrel reading Angular 2 docs:
https://angular.io/docs/ts/latest/glossary.html#!#barrel

but they doesn’t warn about dependency issues loading the metadata of the classes
if you don’t have enough care about the order inside the barrel,
now I understand why Angular’s barrels are not alpabetically sorted :sweat_smile:


#4

If you have a minimal reproduction, I’d be interested in seeing it. It would surprise me if Angular’s DI was that fragile.


#5

Ok, here some code :slight_smile:

My Page:

import ...
import { DataService, EventsService, GUIHandler, SocketService, SystemService } from '../../../services'

@Component({
  templateUrl: 'build/pages/name/page-name.html',
  providers: [ DataService, EventsService, GUIHandler, SocketService, SystemService ]
})
export class HugePage {
  constructor(
    private bus: EventsService,
    private gui: GUIHandler,
    private data: DataService,
    private nav: NavController
  ) {
    ...
  }
  ...
}

I’ve organized those services on a separated folder app/services with a barrel (index.ts) that looks like:

// sorted taking in account dependencies
export * from './events.service';
export * from './socket.service';
export * from './system.service';
export * from './data.service';
export * from './gui.handler';

When the classes are being processed and this barrel is “parsed”, I found that Reflect.js isn’t called to decorate the dependency with its @Injectable, maybe an issue with Angular’s Injector, I don’t have enough idea, but I think they know the order is important there, just have to add a big warning when using barrels.


#6

I fleshed this out:

@Injectable()
export class DataService {
  constructor(private _ebus:EventsBus) {
  }
}
@Injectable()
export class EventsBus {
}
@Injectable()
export class GuiHandlerService {
}
// these are deliberately inverted from how you listed them
export * from './gui.handler';
export * from './events.service';
export * from './data.service';
export * from './system.service';
export * from './socket.service';
@Injectable()
export class SocketService {
  constructor(private _ebus:EventsBus) {
  }
}
@Injectable()
export class SystemService {
  constructor(private _ebus:EventsBus) {
  }
}

Constructor for page class (which imports all these from the barrel):

  constructor(private nav: NavController,
    private _ebus:EventsBus, private _data:DataService, private _sys:SystemService) {
  }

I added all the services to ionicBootstrap (also imported from barrel). Worked fine. I think something else is going on here.


#7

Yep, there are more dependencies:
GUIHandler imports DataService, and being first in the barrel it fails,
I forgot to illustrate the services deeper :wink:


#8

I added the dependency on DataService to GuiHandlerService, and made the page inject GuiHandlerService. Still works fine.


#9

My english is so bad :stuck_out_tongue:
Inject DataService to GuiHandlerService and it won’t work because the “barrel haven’t decorated” DataService when processing GuiHandlerService.


#10

I did that:

@Injectable()
export class GuiHandlerService {
  constructor(private _data:DataService) {
  }
}

Works fine.


#11

Uhm weird, I don’t know what’s different here then, I just know I reordered the barrel and it finally worked.

I had to console.log stuff inside Angular (god bless ionic serve) and finally got some tips on Reflect.js, which made me understand the parsing on browser-pack's _prelude.js was processing the services on the barrel, but Reflect wasn’t decorating the DataService nor the SystemService for unknown reasons.

Maybe it’s my App size and complexity that starts to be messy, that’s why I’m reworking :slight_smile:


#12

I think I see the issue:
You haven’t injected GuiHandlerService in your Page, so it won’t be created and the problem won’t appear! I just added all the services to my Page in order to be created after my Event Bus and realised this.


#13

No, I did add it in what I think was comment #8 in the thread (the way discourse shows comment numbers is not exactly intuitive to me).


#14

To your Page Component?
The dependency between services is required, but also to instantiate all of them in the Page to see the problem:

  constructor(
    private nav: NavController,
    private _ebus: EventsBus,
    private _data: DataService,
    private _gui: GuiHandlerService,
    private _sys: SystemService) {
  }

#15

Yes.

constructor(private nav: NavController,
    private _ebus:EventsBus, private _data:DataService, private _sys:SystemService,
      private _gui:GuiHandlerService) {
  }

#16

Ok, for the record it didn’t work for my current setup:

Cordova CLI: 6.2.0
Gulp version:  CLI version 3.9.1
Gulp local:   Local version 3.9.1
Ionic Framework Version: 2.0.0-beta.10
Ionic CLI Version: 2.0.0-beta.32
Ionic App Lib Version: 2.0.0-beta.18
OS: Distributor ID:     Ubuntu Description:     Ubuntu 16.04 LTS 
Node Version: v5.12.0

#17

I think we’re going to need some kind of complete plunkr or github project to really diagnose this properly. I’m pretty confident that something outside of export ordering is going on.


#18

I had the same problem when I introduced barrels in my ionic app as per the angular style guide.

I came across this link to find a solution for this painful problem which logged as issue in angular
https://github.com/angular/angular/issues/9334

I believe this is something to do with commonjs and issue reporter claims it works with SystemJs.

I am not expert on commonjs neither Systemjs.
Does anyone know how to integrate SystemJs in a ionic2 app?


#19

Seems that change the tsconfig to system is not recommended either:

Have to take care using barrels then, and thanks a LOT for pointing out this issue @savanvadalia


#20

Any chance of getting help with a circular reference between providerA and providerB ? They both reference each other but I end up with an error: Can't resolve all parameters for...

@Injectable()
export class ProviderA {
  constructor(public providerB:ProviderB) {  }
}
@Injectable()
export class ProviderB {
  constructor(public providerA:ProviderA) {  }
}

Any help is really appreciated… I looked at this http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html but it doesn’t help.