I'm calling to you for help on what method to use here : I have tested many things to solve my problem and I have at least 4 working solutions, but I don't know which one(s) would be the best and if some of them work only because of a side effect of something else I did and should not be used...
I couldn't find anything in the documentation to point me to one of these solutions (or another one)
First, here's my situation : I'm building an app that connects to an API for data and builds different sections, each containing dataviz.
I built the sections of the site to be modules, but they share a service named DataService (each of these modules will use the DataService to get and process the data).
My DataService needs a config file with a number of options that are specific to each section and stored in it's own folder.
So I need to provide DataService separately for each section, in the providers section of the module.ts file, and it seemed like good practice to avoid copying DataService to each module...
So the architecture of the files would be :
--Dashboard module
----data.service.ts
----Section1 module
------Section1 config file
----Section2 module
------Section2 config file
I tried multiple things that all seem to work in the section module file :
Solution 1 : injection
(I couldn't get the injection to work without quotation marks, even if I didn't see this anywhere)
Module file :
import { DataService } from '../services/data.service';
import viewConfig from './view.config.json';
@NgModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{ provide: 'VIEW_CONFIG', useValue: viewConfig },
DataService,
]})
export class Section1Module { }
Section file :
@Injectable()
export class DataService {
constructor(@Inject('VIEW_CONFIG') private viewConfig : any) {}
}
Solution 2 : factory provider
Not working after ng serve reboot (see update)
Inspired from https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#factory-provider
Module file :
import { DataService } from '../services/data.service';
import viewConfig from './view.config.json';
export let VIEW_CONFIG = new InjectionToken<any>('');
let dataServiceFactory = (configObject):DataService => {
return new DataService(configObject)
}
@NgModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{ provide: VIEW_CONFIG, useValue: viewConfig },
{
provide : DataService,
useFactory : dataServiceFactory,
deps : [VIEW_CONFIG]
},
]})
export class Section1Module { }
Section file :
@Injectable()
export class DataService {
constructor(private viewConfig : any) {}
}
Solution 3 : custom provider with direct instanciation
Not working after ng serve reboot (see update)
Module file :
import { DataService } from '../services/data.service';
import viewConfig from './view.config.json';
@NgModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{ provide: DataService, useValue : new DataService(viewConfig) },
]})
export class Section1Module { }
Section file :
@Injectable()
export class DataService {
constructor(private viewConfig : any) {}
}
Solution 4 : custom provider with factory
Not working after ng serve reboot (see update)
Very similar to Solution 3...
Module file :
import { DataService } from '../services/data.service';
import viewConfig from './view.config.json';
let dataServiceFactory = (configObject) => { return new DataService(configObject) }
@NgModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{ provide: DataService, useValue : dataServiceFactory(viewConfig) }
]})
export class Section1Module { }
Section file :
@Injectable()
export class DataService {
constructor(private viewConfig : any) {}
}
Solution 5 Use of a factory exported from the service file AND and injection token
Module file :
export const VIEW_CONFIG = new InjectionToken<any>('');
@NgModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{ provide: VIEW_CONFIG, useValue: viewConfig },
{ provide : DataService,
useFactory: dataServiceFactory,
deps : [VIEW_CONFIG] }
]})
export class Section1Module { }
Section file :
@Injectable()
export class DataService {
constructor(private viewConfig : any) {}
}
export function dataServiceFactory(configObject) {
return new DataService(configObject);
}
I'm open to any criticism, idea, new lead of any kind on "What would be the right solution ?" or even "Is the right solution among these ones ?"
Thanks a lot ! :D
UPDATE At one point, I realised my Angular-CLI was behaving strangely, and that some of these methods weren't working if I killed my local server and did an "ng serve". What is even stranger is, it doesn't work juste after killing the CLI local server and rebooting it, but next time I save a file and the CLI recompiles, it works fine...
Solution 1 : still working
Solution 2 / Solution 3 : fails with error :
Error encountered resolving symbol values statically. Calling function
'DataService', function calls are not supported. Consider replacing the
function or lambda with a reference to an exported function
Solution 4 : fails with error:
Error encountered resolving symbol values statically. Reference to a non-exported function
Added solution 5
Before an Angular service can be consumed, it has to be registered with either an Angular module or an Angular component. There are multiple ways to register a service in Angular. In this guide, you will learn about the different ways to register an Angular service and consume it in other services and components.
If you want to pass additional parameters to an Angular service, what you are looking for is @Inject decorator. It helps you pass your parameters to the service through Angular's dependency injection mechanism. @Inject() is a manual mechanism for letting Angular know that a parameter must be injected.
By default, this decorator has a providedIn property, which creates a provider for the service. In this case, providedIn: 'root' specifies that Angular should provide the service in the root injector.
When you add a service provider to root module (root injector), it is available for whole application. That means if you have a feature module with service in providers and that service is also provided in root module, in this case both modules will work with the same instance of service (singleton pattern).
My approach of using the view's config data is this:
My config file:
export const IBO_DETAILS_GENERAL = {
iboCode: 'IBOsFormCodeField',
iboGivenName: 'IBOsFormGivenNameField',
iboFamilyName: 'IBOsFormFamilyNameField',
iboEmail: 'IBOsFormEmailField',
};
export const IBO_DETAILS_ACCOUNT = [];
My view component:
import { IBO_DETAILS_GENERAL } from './ibo-details-formfields';
@Component({
selector: 'ibo-details-general',
templateUrl: './ibo-details-general.component.html',
styles: [],
})
export class IboDetailsGeneral extends FilterDataMappingManipulator implements OnInit, OnDestroy {
// Initial constants,vectors and component mapping
private formFields = IBO_DETAILS_GENERAL;
So, this way, I have my config
in formFields
. And I can use it anywhere within this component's scope.
I am currently using this for generating dynamic form fields (input, select, etc.) depending on which data I get from my API call.
But you can use that object (formFields
in my case) to look for what you need and send it to your service as a parameter.
Also, good job on putting the shared service on a higher level. I believe is the best approach. But I would not add it to providers
on each component, I would add it on the component's module. Like this:
@NgModule({
declarations: [
ChildComp1,
ChildComp2
],
imports: [
],
providers: [
SharedService
]
})
export class SectionModule {
}
This way, ChildComp1
and ChildComp2
will have access to SharedService
without having to add it inside the component's code.
This works fine when, for example, you are defining a User's section. Inside, per say, UsersModule
you declare in providers
your UsersSharedService
and then in declarations
you declare your UsersLeftPanel
and UsersRightPanel
(examples).
Update 1:
Example of usage of config within service.
Immagine that the shared service has this method:
getData() {
// do Stuff
let myData = {}; // Result of the stuff we did
this.dataSubject.next(myData);
}
In your component you call this like this:
this.myService.getData();
Right?
Now, remember we had our config declared? Add it to the call.
this.myService.getData(this.formFields);
And, in our service:
getData(currentConfig) {
// do Stuff depending on currentConfig obj received as parameter
let myData = {}; // Result of the stuff we did
this.dataSubject.next(myData);
}
This way, you call getData()
method of our shared service passing different configurations. This way, you don't have to include your service in many providers
and you don't have to copy/paste the logic that handles the configuration, you have it in your shared service and, therefore, all your children have access to it.
Update 2:
Following your Solution 5 approach, I think you are missing multi: true
.
Try this:
export function dataServiceFactory(configObject) {
return () => new DataService(configObject);
}
providers: [
{ provide: VIEW_CONFIG, useValue: viewConfig },
{ provide : DataService,
useFactory: dataServiceFactory,
deps : [VIEW_CONFIG],
multi: true }
]})
The return
in the exported function is key: return () =>
.
This is how I have it in my project:
export function initConfigFactory(userConfig: UserConfig) {
return () => userConfig.getUsersConfig();
}
Where userConfig.getUsersConfig()
is a service call that gets the user's config.
providers: [
{
provide: APP_INITIALIZER,
useFactory: initConfigFactory,
deps: [UserConfig],
multi: true
}
]
This is very close to your Solution 5, try it out!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With