Angular - access value from a service in root module - javascript

I'm working on internationalisation of an Angular app where I need to dynamically detect the locale that the user has actively selected for the app (the user must actively switch locale for the site to be displayed in their preferred language).
In my root module I specify the following provider for TRANSLATIONS, where the required xlf file (the translations file) is determined dynamically using a factory that depends on the value of my LOCALE_ID:
app.module.ts
... imports go here
export function selectedLocaleFactory(): string {
return LocaleService.getCurrentLanguage();
}
providers:
[
LocaleService,
{
provide: TRANSLATIONS,
useFactory: (locale) => {
locale = locale || 'en'; // default to english if no locale provided
return require(`raw-loader!../locale/messages.${locale}.xlf`);
},
deps: [LOCALE_ID]
},
{
provide: LOCALE_ID,
useFactory: selectedLocaleFactory
}
]
As you can see, in the TRANSLATIONS provider, I'm determining the translation file to use based on the value of LOCALE_ID.
LOCALE_ID is determined by using another factory (selectedLocaleFactory), which simply tries to return a LOCALE_ID value using a method getCurrentLanguage from the LocalService. I'm not sure this is the correct/best way to get a value from my service - I mean selectedLocaleFactory isn't really acting as a true 'factory', and services are designed to be injected.
In any case, I get this compile error in app.module.ts:
Property 'getCurrentLanguage' does not exist on type 'typeof LocaleService'.
Here's an extract from my LocalService:
#Injectable()
export class LocaleService {
private currLanguage: string;
constructor( private router: Router ) {
// this.currLanguage is set here - based on the URL being accessed by the user
}
getCurrentLanguage() {
return this.currLanguage;
}
If I make getCurrentLanguage a static method inside the LocaleService it's still inaccessible:
static getCurrentLanguage() {
return this.currLanguage;
}
UPDATE:
I realised that I need to instantiate the service to access the appropriate method:
export function selectedLocaleFactory(): string {
var localeService = new LocaleService(null);
return localeService.getCurrentLanguage();
}
My LocaleService has a Router dependency, so I just set this to null. It still feels though like I'm trying to do this the wrong way. My LocaleService should really be a singleton and I'm concerned I may end up with issues using my current approach (for example issues with shadowing). Is there a more 'correct' I can access a property from my service inside my root module?
The answer by #Thierry Templier here - What is the best way to declare a global variable in Angular 2 / Typescript suggests bootstrapping the service to get access to one of its variables, however this approach gives me an error saying I can't use the service as an entry point.

Related

What's the difference between providing and injecting 'Window' vs Window in Angular 8 and 9?

I have two Angular projects using these versions:
9.0.0-next.6
8.1.0
In the version 9 I used this to provide and inject the window obhject:
#NgModule({
providers: [
{
provide: Window,
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(#Inject(Window) private window: Window)
}
Which works fine.
Taking this approach to version 8 throwed warnings and errors during compilation:
Warning: Can't resolve all parameters for TestComponent …
I solved it by using single quotes, like this:
#NgModule({
providers: [
{
provide: 'Window',
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(#Inject('Window') private window: Window)
}
What is the difference between both version?
What is the difference in Angular 8 and 9 that causes this thing?
In order for your app to work with Server Side Rendering I suggest you not only use window through token, but also create this token in SSR friendly manner, without referencing window at all. Angular has built-in DOCUMENT token for accessing document. Here's what I came up with for my projects to use window through tokens:
import {DOCUMENT} from '#angular/common';
import {inject, InjectionToken} from '#angular/core';
export const WINDOW = new InjectionToken<Window>(
'An abstraction over global window object',
{
factory: () => {
const {defaultView} = inject(DOCUMENT);
if (!defaultView) {
throw new Error('Window is not available');
}
return defaultView;
},
},
);
Edit: Since this is something people often need, we've used this technique to create a tiny open-source library with injection tokens for global objects, so you can use it:
https://github.com/ng-web-apis/common
It has a sister library for mocks to be used with SSR in Angular Universal:
https://github.com/ng-web-apis/universal
Overall, check out our hub for native APIs in Angular:
https://ng-web-apis.github.io/
Considering the ValueProvider interface:
export declare interface ValueProvider extends ValueSansProvider {
/**
* An injection token. Typically an instance of `Type` or `InjectionToken`, but can be `any`.
*/
provide: any;
/**
* When true, injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean;
}
The provide property is of type any. That means any object (included the Window constructor) can go inside it. The object actually doesn't matter, only the reference matters to identify which provider should be used to inject a parameter in a constructor.
It should not be considered as a good practice to use the native Window constructor as an injection token. It fails at compile time because Window exists at run time in a browser environment, it also exists as a TypeScript declare but the Angular 8 compiler cannot do static code analysis to correlate the Window in the providers and the Window in a constructor's parameters, since the assignment of Window is done by the browser, not by the code. Not sure why it works in Angular 9, though...
You should create your own injection token that represents the dependency provider. This injection token should be either:
A dedicated string (like you did with 'Window')
A dedicated InjectionToken. For example export const window = new InjectionToken<Window>('window');
Moreover, the Angular code should be platform agnostic (should be executable in a browser and on a Node.js server as well) so it would be better to use a factory that returns window or undefined/null, then handle the undefined/null case in the components.

How to use new instance for every new HTTP request in NestJS?

I have an API and was trying to send a request. That is working but I noticed that the classes were not destroyed after I received a response. I'm working with nestJS at the moment but nodeJS + expressJS also had this issue when I tried to test.
I'm using following code:
#Injectable()
export class UsersService {
s = '';
constructor() {}
async findAll(): Promise<any> {
this.s += ' haha ';
return await this.s;
}
}
This returned haha first time haha haha the second time and so on.
I'm not really sure if this is the desired behaviour or may have not configured properly, because I'm just learning nestJS now. I have previously worked with Zend Framework which did not show this behaviour.
Any guidance will be much appreciated.
Thank you.
With the release of nest.js 6.0, injection scopes were added. With this, you can choose one of the following three scopes for your providers:
SINGLETON: Default behavior. One instance of your provider is used for the whole application
TRANSIENT: A dedicated instance of your provider is created for every provider that injects it.
REQUEST: For each request, a new provider is created. Caution: This behavior will bubble up in your dependency chain. Example: If UsersController (Singleton) injects UsersService (Singleton) that injects OtherService (Request), then both UsersController and UsersService will automatically become request-scoped.
Usage
Either add it to the #Injectable() decorator:
#Injectable({ scope: Scope.REQUEST })
export class UsersService {}
Or set it for custom providers in your module definition:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
What you are looking for are request-scoped providers. They are not supported in nest v5, see this issue. As for now, all providers are singletons.
They were added with this pull request though and will be part of nest v6. With the new version, we will get transient and per-request scopes.

Runtime Configuration for Angular 6+ Applications

What is the recommended best practice for loading environment specific configuration during runtime of an Angular application? The Angular documentation mentions the use of APP_INITIALIZER, but that is still not early enough in the load process for things such as runtime configuration of imported modules that make use of the .forRoot() convention.
In my use case, I have an authentication service built and imported via a Core module, which is imported by the App module. The authentication library I am using (the angular-oauth2-oidc library) allows for configuration of the automatic appending of access tokens during when importing the module (see this segment). Since there are constraints in the build environment I am working with that only allows me to produce one common build package to deploy to all environments, I am unable to dynamically set values by using different environment.ts files.
One initial idea is to use the fetch API on the index.html page to load a JSON file containing the configuration onto a global variable, but since the call is asynchronous, there is a chance the configuration will not be fully loaded when the import of the Core module occurs.
This was part of my config setup to bring my app through the build pipeline and took me days. I ended up in a solution using the APP_INITIALIZER loading a REST service and build a AppConfigService for my App. I am using the same angular-oauth2-oidc library.
My Solution for this issue was not to setup the OAuthModule in its forRoot() method. It is called before any configs via APP_INITIALIZER are available - this results in undefined values when applied to the config object given to the forRoot() Method.
But we need a token in the http header. So I used a http interceptor for the attaching of the token like described here. The trick is to setup the OAuthModuleConfig in the factory. Obviously this is called after the app is initialized.
Configure Module
#NgModule({
imports: [
// no config here
OAuthModule.forRoot(),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useFactory: authenticationInterceptorFactory,
deps: [OAuthStorage, AuthenticationErrorHandler, OAuthModuleConfig],
multi: true
}
]
})
Factory for interceptor
const authenticationInterceptorFactory = (oAuthService: OAuthStorage, authenticationErrorHandler: AuthenticationErrorHandler, oAuthModuleConfig: OAuthModuleConfig) => {
const config = {
resourceServer: {
allowedUrls: [
// Include config settings here instead
AppConfigService.settings.apiURL,
AppConfigService.settings.anotherApiURL,
]
sendAccessToken: true
},
}
return new AuthenticationInterceptor(oAuthService, authenticationErrorHandler, config);
};
I have created a library angular-runtime-config for runtime configuration loading for Angular.
Simple usage example
Your custom Configuration class:
export class Configuration {
readonly apiUrl!: string; // only example
readonly apiKey?: string; // only example
// add some other configuration parameters
}
Registering angular-runtime-config module with declaring which configuration files to load. For example, you can determine it by application URL or you can even use Angular injector in the factory or make the factory asynchronous.
import { AngularRuntimeConfigModule } from 'angular-runtime-config';
#NgModule({
...
imports: [
...
AngularRuntimeConfigModule.forRoot(Configuration, {
urlFactory: () => [ 'config/config.common.json', 'config/config.DEV.json' ]
})
],
}
Then request your Configuration class in any injection context.
#Injectable({...})
export class SomeService {
constructor(private readonly config: Configuration) {}
}

Importing TypeScript modules with dependency injection in NestJS

In my NestJS application - I have TypeScript classes that have other classes and values injected into them. The only thing is that I'm importing the TypeScript classes with import statements, and also using the DI system to inject them. Is there some way to remove the import statements and just let the DI system handle it?
TL;DR
import -> class reference
DI -> class instantiation
Matching by string token is possible, but class reference is preferred.
Encapsulation
The dependency injection system mainly handles the instantiation of the classes. This is great, because you do not need to care about the transitive dependencies that the class you want to inject requires.
Example: I want to use the UserService in my UserController. The UserService requires the UserModel for instantiation. However, this second-level dependency is hidden in the UserController. This is great because when the UserService gets a new dependency like a LoggingService, the UserController does not have to be changed.
So instead of
class UserController {
constructor() {
const userModel = new UserModel();
this.userService = new UserService(userModel);
}
}
you can just do
class UserController {
// the transitive dependency on UserModel is hidden
constructor(private userService: UserService) {}
}
Class Reference
But for the DI to know which service to inject you need some link from the #Inject declaration to an actual class to instantiate. Of course, this mechanism depends on the implementation of the DI system. The reference could be by name (string matching), by interface (DI decides which implementation to use: UserService -> UserServiceImpl / MockUserServiceImpl) or in the default case of nestjs directly by the class to be instantiated.
Although matching by name is possible in nestjs, matching by class is preferred because it makes refactoring much easier.
When you create a custom provider you can choose what kind of token you want to use for the matching. This is needed, when you want to inject a value (no class for matching)
const connectionProvider = {
provide: 'Connection',
useValue: connection,
};
#Module({
providers: [connectionProvider],
})
or a dynamically instantiated class.
const configServiceProvider = {
provide: ConfigService,
useClass: process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
#Module({
providers: [configServiceProvider],
})

Dynamic Internationalization in Angular 5 (Date and currency)

I met problem with date and currency interpoation in my app. I tried to find out some workaround but I could not for my case.
My app works this way: It initialized with en-US locale. After user login data comes where one field is User's Region. This one is setted in local. For this purpose I do:
// app.module
providers: [
...
{
provide: LOCALE_ID,
deps: [LocaleService],
useFactory: localeProviderFactory
}
...]
export function localeProviderFactory(provider: LocaleService) {
return provider.getCurrent();
}
//my service
export class LocaleService {
locale: BehaviorSubject<any>;
constructor() {
this.locale = new BehaviorSubject<GroupModel> (this.getGroupFromLocalStorage());
}
private getGroupFromLocalStorage(): GroupModel {
let parse = JSON.parse(localStorage.getItem('currentUser'));
return parse ? parse.Region : 'en-US';
}
setLocale(val) {
this.locale.next(val);
}
getCurrent() {
return this.locale.getValue();
}
}
So after user login I call setLocale() in LocaleService and pass there new User Region.
Problem is that I don't know which region will be setted and i don't want import all locales into app. Angular 5 require all locales imported before app starts. With:
import { registerLocaleData } from '#angular/common';
import localeFr from '#angular/common/locales/fr';
registerLocaleData(localeFr);
Is it possible to import/load and register locales dynamicaly in run time? Or is there some workarounds?
No, unfortunately it's not possible. As Rob McCabe in his answer to his own question here said:
Angular only works with one language at a time, you have to completely reload the application to change the lang. The JIT support only means that it works with JIT, but you still have to provide the translations at bootstrap because it will replace the text in your templates during the compilation whereas this lib uses bindings, which means that you can change the translations at any time.

Categories

Resources