i have a problem with providing different services to modules.
I have three modules: ModuleA, ModuleB and ModuleShared. I want ModuleA and B to provide to ModuleShared different service using Injectin Token.
I am trying to do this, but as You can see, components from module A and B are using only service B. How to provide to shared module different services ?
--- edit ---
ModuleA:
#Injectable()
export class ServiceA implements IMyService {
getName(): string {
return 'service A';
}
}
#Component({
selector: 'component-a',
template: `
<div>
Cpomonent from Module A:
<shared-component></shared-component>
</div>
`,
})
export class ComponentA {
}
#NgModule({
imports: [
ModuleShared
],
declarations: [
ComponentA
],
exports: [
ComponentA
],
providers: [
{
provide: MY_SERVICE,
useClass: ServiceA
}
]
})
export class ModuleA {}
ModuleB:
#Injectable()
export class ServiceB implements IMyService {
getName(): string {
return 'service B';
}
}
#Component({
selector: 'component-b',
template: `
<div>
Component from Module B:
<shared-component></shared-component>
</div>
`,
})
export class ComponentB {
}
#NgModule({
imports: [
ModuleShared
],
declarations: [
ComponentB
],
exports: [
ComponentB
],
providers: [
{
provide: MY_SERVICE,
useClass: ServiceB
}
]
})
export class ModuleB {}
SharedModule:
export interface IMyService {
getName: string
};
export const MY_SERVICE = new InjectionToken<IMyService>('MyService');
#Component({
selector: 'shared-component',
template: `
<div>
<h3>Shared component, provided: {{serviceName}}</h3>
</div>
`,
})
export class ComponentShared {
constructor(#Inject(MY_SERVICE) private myService: IMyService) {}
get serviceName(): string {
return this.myService.getName();
}
}
#NgModule({
declarations: [
ComponentShared
],
exports: [
ComponentShared
]
})
export class ModuleShared {}
https://plnkr.co/edit/Lbr23I4wC2A0HruvMU6m?p=preview
This can only work if ModuleA and ModuleB are lazy loaded modules,
otherwise they all share the same provider scope and subsequently registered providers with the same key (MY_SERVICE) will override the previously registered one.
Lazy-loaded modules introduce a new sub-scope and therefore can provide different providers which won't override each other.
Related
I would like use provider value defined in other module. Here is example:
app.module.ts
...
import { ThemeModule } from '../shared/modules/theme/theme.module';
...
#NgModule({
declarations: [
RootComponent,
LoginScreenComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
BrowserAnimationsModule,
AppRoutingModule,
ConfigModule,
ThemeModule,
....
],
providers: [
...
{ provider: "THEME_NAME", useValue: "VALUE" },
],
bootstrap: [RootComponent]
})
export class MDMToolModule {}
theme.module.ts
import { NgModule } from '#angular/core';
import { ThemeService } from './services/ThemeService';
#NgModule({
imports: [],
declarations: [],
providers: [
{provide: "ThemeService", useFactory: (THEME_NAME) => {
return new ThemeService(THEME_NAME)
}},
],
exports: []
})
export class ThemeModule {}
Is there possibility to pass VALUE defied not in module like above example (THEME NAME)?
if you're providing value in root module then it will be available to all other modules too so you can then simply ask for that value using Inject decorator like this:
#Injectable()
export class ThemeService {
constructor(#Inject("THEME_NAME") theme: string) {}
}
otherwise you can import your MDMToolModule inside ThemeModule though judging by the code you provided I'm assuming this MDMToolModule is your root module,
you can also use Injection Token to avoid using those magic strings like this:
const ThemeName = new InjectionToken("token to inject theme name");
and then use it inside theme.module.ts
#NgModule({
providers: [
{ provide: ThemeName, useValue: "DarkKnight" },
ThemeService
]
})
export class ThemeModule {}
theme.service.ts
#Injectable()
export class ThemeService {
constructor(#Inject(ThemeName) theme: string) {}
}
How do I Access AppModule imports from Lazy-loaded Modules ?
My Angular10 App imports AngularMaterial and NXTranslate Modules in to the AppModule.
NxTranslate calls an ApiService to get a large Lookup object of thousands of translations.
This is translated at the initial loading of the AppModule.
The App has multiple lazy-loaded routes that also need to use the AnagularMaterial and NXTranslate Modules in their features.
If I use a SharedModule to load the Modules then the ApiService is called multiple times. This is obviously not good.
It should only call the ApiService & AngularMaterial once and be available for all modules.
How do I resolve this? I am struggling.
Thanks.
Update
(sorry for the long post)
This is the NXTranslate implementation - it uses a custom class.
import { environment } from './../../../../environments/environment';
import { OSCITranslateService } from './translate.service';
import { NgModule, Injector } from '#angular/core';
import { CommonModule } from '#angular/common';
import {TranslateLoader, TranslateModule} from '#ngx-translate/core';
import {TranslateHttpLoader} from '#ngx-translate/http-loader';
import {HttpClient, HttpClientModule} from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
export class CustomLoader implements TranslateLoader {
localeResourcesUrl =
`${environment.baseUrl}${environment.apiUrl.localeResources}`;
constructor(private http: HttpClient) {}
getTranslation(lang: string): Observable<any> {
let options;
const uri = `${this.localeResourcesUrl}${options && options.key ?
'/' + options.key : ''}`;
let mapped = this.http.get(uri).pipe(
map((response: any) => {
let localeData = {};
let languageCode = response?.languageVariantCode;
response.resources.forEach(item => {
localeData[item.keyName] = item.keyValue;
});
return localeData;
})
);
return mapped;
}
}
#NgModule({
declarations: [],
imports: [
CommonModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: CustomLoader,
deps: [HttpClient]
}
})
],
exports: [ TranslateModule ]
})
export class NxTranslateModule {
constructor(private http: HttpClient) {
}
}
This is the sharedRootModule that imports the AngularMaterial & NXTranslate
import { SharedModule } from './shared.module';
import { NgModule, ModuleWithProviders } from '#angular/core';
#NgModule({
})
export class SharedRootModule {
static forRoot(): ModuleWithProviders<SharedModule> {
return {
ngModule: SharedModule
};
}
}
In AppModule SharedRootModule is imported
...
#NgModule({
declarations: [
AppComponent
],
imports: [
...
SharedRootModule.forRoot()
],
exports: [
...
SharedRootModule
]
....
Are you concerned about the multiple ApiService instances you might end up with? Provide the ApiService within AppModule only, or even better, use the providedIn property right in your service's decorator so it gets injected at application level. (https://angular.io/api/core/Injectable#providedIn)
I would just use a SharedModule that exports the mentioned lazy loaded modules.
Code example
Third party library
#Module({
providers: [AService]
exports: [AService]
})
export class AModule {
}
#Module({
imports: [AModule],
providers: [BService]
exports: [BService]
})
export class BModule {
}
My code
#Module({
imports: [BModule],
providers: [CService]
})
export class CModule {
}
Question
How can I override/replace the AService provider from my code? (without third party library patching)
Following on from my comment, this is how you would go about making a dynamic module with a dynamic provider
export interface ProviderInterface {
handle(): void;
}
#Injectable()
class SomeHandlingProvider {
constructor(#Inject('MY_DYNAMIC_PROVIDER') private readonly dynamicProvider: ProviderInterface) {}
handle(): void {
this.dynamicProvider.handle();
}
}
#Module({})
export class AModule {
public static forRoot(provider: ProviderInstance): DynamicModule {
return {
module: AModule,
providers: [
{
provide: 'MY_DYNAMIC_PROVIDER',
useClass: provider,
},
SomeHandlingProvider,
],
};
}
}
Then you can use like this
class GenericDynamicProviderExample implements ProviderInterface {
handle(): void {
console.log('hello');
}
}
#Module({
imports: [
AModule.forRoot(GenericDynamicProviderExample),
],
})
export class BModule {}
I'm making a modal component for a component library. I made a 3rd party modal library that I'm using within my component library. A main feature is being able to pass a component via a service and dynamically adding it to the modal.
My modal lib has a static method that allows you to add your component to the module's entry components. It looks like:
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
Cool, that works. I can pass components into it when I import the module like this: A11yModalModule.withComponents([ModalContentComponent])
My problem occurs when I abstract this out another level. So now instead of 2 modules I have 3. I need to pass a component like I did above from the lib consumer's module, to my component module, and then into the modal module.
How can I pass components from the lib module to the modal module?
I think I'm getting close. Here are my 3 modules
// app module
#NgModule({
declarations: [AppComponent, ModalContentComponent],
imports: [
LibModalModule.withComponents([ModalContentComponent])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
// lib module
#NgModule({
imports: [CommonModule],
declarations: [LibModal],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
imports: [CommonModule, A11yModalModule.withComponents(components)]
};
}
}
// a11y modal module
#NgModule({
imports: [CommonModule],
declarations: [ModalComponent],
exports: [],
providers: [ModalService, DomService],
entryComponents: [ModalComponent]
})
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
withComponents method should return ModuleWithProviders object which is just wrapper around a module that also includes the providers.
It can't have imports property or something else because it doesn't understand those properties. Here's an excerpt from angular source code that is responsible from reading metadata from ModuleWithProviders:
else if (importedType && importedType.ngModule) {
const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
importedType));
}
}
As we can see angular compiler takes providers from the object that will returned in withComponents method.
So, in order to merge your modules you can either use your approach(provide ANALYZE_FOR_ENTRY_COMPONENTS in LibModalModule.withProviders) or reuse A11yModalModule.withComponents like:
#NgModule({
imports: [CommonModule, A11yModalModule],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
providers: A11yModalModule.withComponents(components).providers
};
}
}
(Tested with AOT)
Also A11yModalModule has to be imported in LibModalModule if we want its providers to be included in our root module injector (And i suppose you're going to use ModalService and DomService that are declated in A11yModalModule). The reason of this is that angular includes all providers from transitive module in root module injector.
See also:
Avoiding common confusions with modules in Angular
What you always wanted to know about Angular Dependency Injection tree
I had a bug that was giving me a false flag. Turns out you can just add the same withComponents method to the component library module and it passes the component through. I'd love an explanation on how this works if anyone knows.
// app module
#NgModule({
declarations: [AppComponent, ModalContentComponent],
imports: [
LibModalModule.withComponents([ModalContentComponent])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
// lib module
#NgModule({
imports: [CommonModule, A11yModalModule],
declarations: [LibModal],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
// a11y modal module
#NgModule({
imports: [CommonModule],
declarations: [ModalComponent],
exports: [],
providers: [ModalService, DomService],
entryComponents: [ModalComponent]
})
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
I'm writing an AWS security groups module clone with Angular and ngrx. I'm trying to pass a method into the dumb component for creating a new security group.
...
const schemas: any[] = [];
#NgModule({
imports: [
CommonModule
],
declarations: [
SecurityGroupsComponent,
SecurityGroupsTableComponent,
SecurityGroupsListComponent
],
schemas: schemas,
exports: [SecurityGroupsComponent]
})
export class SecurityGroupsModule { }
import { Component } from '#angular/core';
#Component({
selector: 'app-security-groups-table',
templateUrl: './security-groups-table.component.html',
})
export class SecurityGroupsTableComponent {
onCreateSecurityGroup() {
console.log('Create Security Group');
}
}
<app-security-groups-list
[createSecurityGroup]="onCreateSecurityGroup"></app-security-groups-list>
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-security-groups-list',
templateUrl: './security-groups-list.component.html',
})
export class SecurityGroupsListComponent {
#Input()
createSecurityGroup: Function;
selectSecurityGroup(securityGroupId: string) {
this.securityGroupSelected.next(securityGroupId);
}
}
and when the template is rendered I get this exception:
Can't bind to 'createSecurityGroup' since it isn't a known property of 'app-security-groups-list'.
What am I doing wrong?
It seems you have missed to add SecurityGroupsListComponent to NgModule.
Please make sure you do below step before use it:
#NgModule({
imports: [...],
declarations: [SecurityGroupsListComponent]
})
export const DECLARATIONS = [SecurityGroupsComponent,
SecurityGroupsTableComponent,
SecurityGroupsListComponent]
and then
declarations: DECLARATIONS,
schemas: schemas,
exports: DECLARATIONS