I am trying to inject a provider to objects created by new. Is it possible? For example:
#Injectable()
export class SomeService {
}
export class SomeObject {
#Inject()
service: SomeService;
}
let obj = new SomeObject();
It's not working in my tests, the obj.service is undefined.
No, the dependency injection system will only work with objects that are created with it.
You can create SomeObject as a custom provider though, e.g. with useClass:
#Injectable()
class SomeObject {
constructor(private someService: SomeService) {}
}
#Module({
providers: [
SomeService,
{ provide: SomeObject, useClass: SomeObject },
],
})
export class ApplicationModule {}
or with useFactory for custom setup logic:
const someObjectFactory = {
provide: 'SomeObject',
useFactory: (someService: SomeService) => {
// custom setup logic
return new SomeObject(someService);
},
inject: [SomeService],
};
#Module({
providers: [SomeService, connectionFactory],
})
export class ApplicationModule {}
Related
I am trying to inject a service from one module to another module. I am keep on getting
Error: Nest can't resolve dependencies of the AwsSnsService (?). Please make sure that the argument function() {\n if (klass !== Object) {\n return klass.apply(this, arguments);\n }\n } at index [0] is available in the AwsModule context.
Below is the code
aws-sns.service.ts
#Injectable()
export class AwsSnsService {
constructor(private readonly awsSNS: AWS.SNS) {}
}
aws.module.ts
import { Module } from '#nestjs/common';
import { AwsSnsService } from './aws-sns.service';
#Module({
providers: [AwsSnsService],
exports: [AwsSnsService],
})
export class AwsModule {}
I want to use the AwsSnsService in my User module. I am doing it in a following way
#Module({
imports: [
SomeOtherModule,
AwsModule,
],
providers: [UserService, UserDevicePushTokensService],
exports: [UserService, UserDevicePushTokensService],
})
export class UserModule {}
#Injectable()
export class UserDevicePushTokensService {
constructor(private readonly awsSnsService: AwsSnsService) {}
}
Looks to me the dots are connected in the proper way. Still I am not figuring it out.
You must resolve dependency for AWS.SNS in AwsSnsService
Example with my s3, the same for SNS and any service:
s3.provider.ts
export const s3Providers: Provider = {
provide: S3_PROVIDER_KEY,
useFactory: async (): Promise<S3> => {
return new S3({
accessKeyId: "Key",
secretAccessKey: "AccessKey",
});
},
};
s3.service.ts
#Injectable()
export class S3Service {
constructor(
#Inject(S3_PROVIDER_KEY)
private readonly s3: S3) {}
}
You can read more here
Nestjs custom provider
I want to ensure a singleton service is created on application boot. I could add it as injection parameter to my AppComponent and not use it at all, but that looks a bit dirty. Right now I'm going with this solution:
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '#angular/core';
import { NavigationService } from './navigation.service';
#NgModule()
export class NavigationServiceModule {
public static forRoot(): ModuleWithProviders<NavigationServiceModule> {
return {
ngModule: NavigationServiceModule,
providers: [
{
provide: APP_INITIALIZER,
deps: [NavigationService],
multi: true,
useFactory: () => () => { }
}
]
}
}
}
But don't really love it, too. Any ideas how this could be achieved best?
Just use the default service setup and don't add it to the providers array - only one instance will be created (unless you explicitly provide it outside of constructors)
#Injectable({
providedIn: 'root'
})
export class NavigationService
I have a service for authentication based on JWT. To reuse this service in all my projects i created a library which should be shipped with npm.
For this service to work i need some API-Calls. In every project the API could look completely different so i don't want to provide this functionality inside my library instead inject another service which handles my API-Calls.
My idea was to create a module which contains my service and provide an interface to describe the service for API-Calls and inject it forRoot. The Problem is that my api service has some dependencies like HttpClient and i cannot simple instantiate it in my app.module.
My library looks like:
auth.module.ts
import { NgModule, ModuleWithProviders, InjectionToken } from '#angular/core';
import { AuthService } from '../services/auth.service';
import { AuthAPI } from '../models/authAPI';
import { AuthapiConfigService } from '../services/authapi-config.service';
#NgModule()
export class AuthModule {
static forRoot(apiService: AuthAPI): ModuleWithProviders {
return {
ngModule: AuthModule,
providers: [
AuthService,
{
provide: AuthapiConfigService,
useValue: apiService
}
]
};
}
}
auth-api.interface.ts
import { Observable } from 'rxjs';
export interface AuthAPI {
reqLogin(): Observable<{ access_token: string; }>;
reqRegister(): Observable<{ access_token: string; }>;
}
auth-api-config.service.ts
import { InjectionToken } from '#angular/core';
import { AuthAPI } from '../models/authAPI';
/**
* This is not a real service, but it looks like it from the outside.
* It's just an InjectionTToken used to import the config object, provided from the outside
*/
export const AuthapiConfigService = new InjectionToken<AuthAPI>('API-Service');
auth.service.ts
constructor(#Inject(AuthapiConfigService) private apiService) {}
How i am trying to implement it:
auth-rest-service.ts
import { Injectable } from '#angular/core';
import { AuthAPI } from 'projects/library-project/src/lib/auth/models/authAPI';
import { Observable } from 'rxjs';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class AuthRestService implements AuthAPI {
constructor(private http: HttpClient) {}
reqLogin(): Observable<{ access_token: string; }> {
return this.http.post<{access_token: string}>(`/login`, 'test');
}
reqRegister(): Observable<{ access_token: string; }> {
return this.http.post<{access_token: string}>(`/login`, 'test');
}
}
app.module.ts
import { AuthRestService } from './components/auth-service/auth-rest.service';
#NgModule({
declarations: [
...
],
imports: [
...
AuthModule.forRoot(AuthRestService),
...
],
providers: [AuthModule],
bootstrap: [AppComponent]
})
export class AppModule { }
I can't create an instance of AuthRestService because of the dependencies this service has (HttpClient). Is there any method to tell angular to provide me this service.
This is possible with usage of angular's Injector.
import { Injector, ModuleWithProviders, NgModule, Optional, Provider, SkipSelf } from '#angular/core';
import { isFunction } from 'lodash';
export function resolveService(cfg: SharedConfig, inj: Injector): IncompleteService {
const provider = cfg?.service;
// if service is an angular provider, use Injector, otherwise return service instance as simple value
const service = isFunction(service) ? inj.get(provider) : provider;
return service;
}
/**
* Service to be implemented from outside the module.
*/
#Injectable()
export abstract class IncompleteService {
abstract strategyMethod();
}
// Optional: A config object is optional of course, but usually it fits the needs.
export interface SharedConfig {
service: IncompleteService | Type<IncompleteService> | InjectionToken<IncompleteService>;
// other config properties...
}
/*
* Optional: If a Config interface is used, one might resolve the config itself
* using other dependencies (e.g. load JSON via HTTPClient). Hence an InjectionToken
* is necessary.
*/
export const SHARED_CONFIG = new InjectionToken<SharedConfig>('shared-config');
// Optional: If SharedConfig is resolved with dependencies, it must be provided itself.
export type ModuleConfigProvider = ValueProvider | ClassProvider | ExistingProvider | FactoryProvider;
/**
* One can provide the config as is, i.e. "{ service: MyService }" or resolved by
* injection, i.e.
* { provide: SHARED_CONFIG: useFactory: myConfigFactory, deps: [DependentService1, DependentService2] }
*/
#NgModule({
declarations: [],
imports: []
})
export class SharedModule {
static forRoot(config: SharedConfig | ModuleConfigProvider): ModuleWithProviders<SharedModule> {
// dynamic (config is Provider) or simple (config is SharedConfig)
return {
ngModule: SharedModule,
providers: [
(config as ModuleConfigProvider).provide ? (config as Provider) : { provide: SHARED_CONFIG, useValue: config },
{ provide: IncompleteService, useFactory: resolveService, deps: [SHARED_CONFIG, Injector] },
// ... provide additional things
],
};
}
/**
* In general not really useful, because usually an instance of IncompleteService
* need other dependencies itself. Hence you cannot provide this instance without
* creating it properly. But for the sake of completeness, it should work as well.
*/
#NgModule({
declarations: [],
imports: []
})
export class MostSimpleSharedModule {
static forRoot(service: IncompleteService): ModuleWithProviders<SharedModule> {
// dynamic (config is Provider) or simple (config is SharedConfig)
return {
ngModule: SharedModule,
providers: [
{ provide: IncompleteService, useValue: service },
// ... provide additional things
],
};
}
EDIT
If you really need an interface iso. an (injectable) abstract class IncompleteService, you just need to define another InjectionToken<IncompleteServiceInterface> and provide this token explicitly.
I have an injectable authentication service written for Angular 4. The code looks similar to the following:
auth.service.ts
import { CookieService } from 'ngx-cookie';
import { Identity } from './auth.identity';
export function authInit(authService: AuthService): () => Promise<any> {
return (): Promise<any> => authService.checkAuthenticated();
}
#Injectable()
export class AuthService {
identity: Identity;
isAuthenticated:boolean = false;
apiUrl: string = 'https://myUrl/api';
constructor(private _http: HttpClient, private _cookieService: CookieService) {
this.identity = new Identity();
}
checkAuthenticated(): Promise<any> {
return new Promise((res, rej) => {
let identity = this._cookieService.getObject('myToken');
if (!!identity) {
this.setAuthenticated(identity);
}
});
}
login(username: string, password: string) {
let creds = {
username: username,
password: password
};
this._http.post<any>(this.apiUrl + '/auth/login', creds).subscribe(data => {
this.setAuthenticated(data);
});
}
logout() {
}
private setAuthenticated(data: any) {
this._cookieService.putObject('myToken', data);
this.isAuthenticated = true;
// hydrate identity object
}
}
auth.module.ts
import { NgModule, APP_INITIALIZER } from '#angular/core';
import { CommonModule } from '#angular/common';
import { AuthService, authInit } from './auth.service';
#NgModule({
imports: [CommonModule],
providers: [
AuthService,
{
provide: APP_INITIALIZER,
useFactory: authInit,
deps: [AuthService],
multi: true
}
]
})
export class AuthModule { }
The idea is that when the app loads, I want to be able to check the local storage (cookies, sessionStorage or localStorage) to see if the value exists. (This is demonstrated by the commented if statement in the constructor.) Based on the isAuthenticated property I want to be able to show specific content.
Currently, if I uncomment the lines in the constructor, I'll get an exception document.* is not defined. I know what that means. Unfortunately, I don't know how to accomplish what I'm going for.
Keep in mind, this is a service and not a view component, so there's no ngOnInit method available.
EDITED
So I've added the factory provider as suggested. However, I'm still getting the exception: document is not defined
Thanks!
When you have a service that you need to have run before everything else might be initialized you can use the APP_INITIALIZER token (the documentation is sparse to say the least :)
The gist is that in your application providers array you add a factory provider:
{
provide: APP_INITIALIZER,
useFactory: authInit,
deps: [AuthService],
multi: true
}
Make sure to have provide set specifically to APP_INITIALIZER and the multi value to true. The authInit function is factory that returns a function that returns a promise. It has to return a promise and not an observable. It would be something like:
export function authInit(authServ: AuthService) {
return () => authServ.check();
}
The authServ.check() function is where you can put the logic you currently have commented in your service (just make sure it returns a promise as the result). Setting it up this way will let that logic run while the application loads.
Edit: Now that I take a look at the app.module.ts add the initialization of the cookie service and add the BrowserModule:
import { NgModule, APP_INITIALIZER } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { CommonModule } from '#angular/common';
import { CookieModule } from 'ngx-cookie';
import { AuthService, authInit } from './auth.service';
#NgModule({
imports: [BrowserModule, CommonModule, CookieModule.forRoot()],
providers: [
AuthService,
{
provide: APP_INITIALIZER,
useFactory: authInit,
deps: [AuthService],
multi: true
}
]
})
export class AuthModule { }
Also, make sure to add ngx-cookie to your systemjs.config.js (if that's what you're using as your loader).
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.