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 {}
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.
I am developing an Angular library where there is an authentication module that provides an HttpInterceptor. The main idea is to have this interceptor working automatically in any app that imports this authentication module without having to do any extra setup at it.
What I have so far is the following:
AuthenticationModule
#NgModule({
imports: [ConcreteAuthModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: BearerInterceptor,
multi: true
}
]
})
export class AuthenticationModule {
static forRoot(config: AuthConfig): ModuleWithProviders {
return {
ngModule: AuthenticationModule,
providers: [
{
provide: AUTH_CONFIG,
useValue: config
}
]
};
}
}
ConcreteAuthModule
#NgModule({
imports: [ThirdPartyLibModule],
providers: [
{
provide: AuthenticationService,
useClass: ConcreteAuthService
}
]
})
export class ConcreteAuthModule { }
BearerInterceptor
#Injectable()
export class BearerInterceptor implements HttpInterceptor {
constructor(private authService: AuthenticationService) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const headers: any = {};
if (this.authService.isUserAuthenticated()) {
headers.Authorization = `Bearer ${this.singleSignOnService.getUserToken()}`;
}
const authReq = req.clone({ setHeaders: headers });
return next.handle(authReq);
}
}
And from a test Angular app I am importing this module the following way at the AppModule:
#NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
AuthenticationModule.forRoot({ /* config stuff */ })
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
I checked how some third party libraries did this, also came across a couple of Stack Overflow questions that discussed about this and they all suggested having an Angular module created (I already have it: AuthenticationModule), then provide the http-interceptor on it (already have it too) and finally importing this module from an Angular app (also did this).
But still, none of the http requests in my app are being intercepted.
Tried importing the BearerInterceptor directly from my test app and providing it on the AppModule like this:
import { BearerInterceptor } from 'my-lib':
#NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
AuthenticationModule.forRoot({ /* config stuff */ })
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: BearerInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
And that works! But this workaround is not what I am looking for...
You're very close to a working solution.
The key is to look at how the module is being imported by the AppModule.
imports: [
BrowserModule,
HttpClientModule,
AuthenticationModule.forRoot({ /* config stuff */ })
],
That is how the AuthenticationModule is imported by the AppModule, but that NgModule does not provide the HTTP_INTERCEPTORS.
You've provided the token in the #NgModule() decorator, but that module is not being used by your application. It's the module defined by the forRoot() function.
Move the declaration of the HTTP_INTERCEPTORS to the forRoot() function.
Try this instead:
#NgModule({
imports: [ConcreteAuthModule]
})
export class AuthenticationModule {
static forRoot(config: AuthConfig): ModuleWithProviders {
return {
ngModule: AuthenticationModule,
providers: [
{
provide: AUTH_CONFIG,
useValue: config
}, {
provide: HTTP_INTERCEPTORS,
useClass: BearerInterceptor,
multi: true
}
]
};
}
}
The problem was because I was installing the library locally for testing purposes like the following:
$ npm i --save my-lib_path/dist/my-lib
After I published it and installed it from the npm registry it worked fine:
$ npm i --save my-lib
I declare directive in two module then return error is Type PermissionDirective is part of the declarations of 2 modules and i declare only one module then return error is Can't bind to 'isPermission' since it isn't a known property of 'button'. What is the problem here.
Please understand my app structure.
permission.directive.ts
import { Directive, Input } from '#angular/core';
import { TemplateRef, ViewContainerRef } from '#angular/core';
import { LoginInfoService } from '../service/auth.service';
#Directive({ selector: '[isPermission]' })
export class PermissionDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private _auth: LoginInfoService
) {
}
#Input() set isPermission(_module_action: Array<string>) {
let permission = this._auth.isPermission(_module_action[0], _module_action[1]);
if (permission) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
layout.route.ts
import { ModuleWithProviders } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { LayoutComponent } from './layout.component';
const layoutRoutes: Routes = [
{
path: '',
component: LayoutComponent,
children: [
{
path: '',
loadChildren: () => import('app/dashboard/dashboard.module').then(m => m.DashboardModule),
},
{
path: 'users',
loadChildren: () => import('app/users/users.module').then(m => m.UsersModule),
}
]
}
];
export const layoutRouting: ModuleWithProviders = RouterModule.forChild(layoutRoutes);
layout.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { PermissionDirective } from 'app/common/directives/permission.directive';
#NgModule({
imports: [
CommonModule,
layoutRouting
],
exports:[],
declarations: [
PermissionDirective
],
providers:[]
})
export class LayoutModule { }
dashboard.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
...
import { PermissionDirective } from 'app/common/directives/permission.directive';
#NgModule({
imports: [
CommonModule,
dashboardRouting,
....
],
declarations: [
DashboardComponent,
PermissionDirective
],
providers: []
})
export class DashboardModule { }
SharedModule
import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
.........
import { PermissionDirective } from 'app/common/directives/permission.directive';
#NgModule({
imports: [
....
],
declarations: [
....
PermissionDirective
],
exports: [
...
PermissionDirective,
...
],
providers: [
...
]
})
export class SharedModule {
constructor (#Optional() #SkipSelf() parentModule: SharedModule, private dateAdapter:DateAdapter<Date>) {
if (parentModule) {
throw new Error(
'SharedModule is already loaded. Import it in the AppModule only');
}
this.dateAdapter.setLocale('en-in'); // DD/MM/YYYY
}
}
I would recommend you create a shared module that import the directive then you can import that module to your module like this
shared.module.ts
#NgModule({
exports: [PermissionDirective],
declarations: [PermissionDirective]
})
export class SharedModule {}
then import SharedModule to your module
#NgModule({
imports: [
CommonModule,
dashboardRouting,
SharedModule
],
declarations: [
DashboardComponent
],
providers: []
})
export class DashboardModule { }
#NgModule({
imports: [
CommonModule,
layoutRouting,
SharedModule
],
exports:[],
declarations: [
],
providers:[]
})
export class LayoutModule { }
Declare your Directive in parent module ie(app.module.ts)
and export the directive to use it over the complete project
#NgModule({
....
export:[PermissionDirective]
})
export class AppModule { }
I remove some code in shared module.
import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
.........
import { PermissionDirective } from 'app/common/directives/permission.directive';
#NgModule({
imports: [
....
],
declarations: [
....
PermissionDirective
],
exports: [
...
PermissionDirective,
...
],
providers: [
...
]
})
export class SharedModule {
// I remove this code
/*
constructor (#Optional() #SkipSelf() parentModule: SharedModule, private dateAdapter:DateAdapter<Date>) {
if (parentModule) {
throw new Error(
'SharedModule is already loaded. Import it in the AppModule only');
}
this.dateAdapter.setLocale('en-in'); // DD/MM/YYYY
}
*/
}
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.