Using Injected Token to configure a lazily loaded module - javascript

Consider the following snippet. How do I access the provided value of Environment in forRoot? Is there an alternate way that I do the same?
interface Environment {
tokens: Record<string, string>;
}
const ENVIRONMENT = new InjectionToken<Environment>('ENVIRONMENT');
// `SomeModule` with `forRoot` to configure it. This module is from an external library.
// Some library module that I'm creating
#NgModule({
imports: SomeModule.forRoot() // `tokens` needs to passed here.
})
export class MyLibraryModule {}
MyLibraryModule will be consumed in some application where ENVIRONMENT will be injected. It will be lazily loaded

Related

Why and how add Model as a dependency in NestJS?

I'm trying to do this tutorial and it doesn't work for me. When I run the code, I get this error:
ERROR [ExceptionHandler] Nest can't resolve dependencies of the TestService (?, AnimalsModel). Please make sure that the argument StudentModel at index [0] is available in the TestService context.
Potential solutions:
- If StudentModel is a provider, is it part of the current TestService?
- If StudentModel is exported from a separate #Module, is that module imported within TestService?
#Module({
imports: [ /* the Module containing StudentModel */ ]
})
I believe this is the code that I need to change(?)
#Injectable()
export class TestService {
constructor(
#InjectModel('Student') private readonly studentModel: Model<Student>,
#InjectModel('Animals') private readonly animalModel: Model<Animal>,
) {}
Git Repo
you've got TestService in an imports array. DOn't do that. provides never belong in the imports array, only modules do
To create the provider for #InjectModel('Student') you need to add MongooseModule.forFeature([{ name: 'Student', schema: StudentSchema })]) to the imports array of the module that contains TestService (presumably TestModule) so that Nest can go and create the dynamic provider that you want to inject.
Edit after receiving the repository
You're using named database connections so you need to use those same connections in the #InjectModel(). #InjectModel('Student', 'myWorldDb'). Just like the docs show

Import external JS into Angular 8

I am writing application in Angular 8 (8.0.3) and I need to import external JS file which will hold a URL reference for API that can and will change. The problem I am facing is that changes I do into this file after I have compiled Angular app (with ng build --prod), changes are not being picked up inside Angular, but it keeps the data as it was when the app was built.
This is the external JS file:
export function apiUrl() {
return 'http://www.localhost.local/api/v1/';
}
The apiUrl() does return proper value, but if I change it, the updated value is not reflected inside Angular app.
I also created .d.ts. file:
export declare function apiUrl();
Imported the external JS file into index.html:
<script type="module" src="assets/js/api_url.js"></script>
Used it in angular service:
import {apiUrl} from '../../assets/js/api_url';
export class ApiService {
private api_url: string = apiUrl();
constructor(...) {
console.log(apiUrl());
}
}
I did NOT import this file in angular.json and even if I do, nothing changes.
So, how to create and then import an external JS file that after it changes, the Angular app does pick up it changes?
EDIT:
Do note that I do not need a live app reload when this file changes, but I need for the file to be loaded from server each time the Angular app starts.
EDIT 2:
To explain why I need this. The Angular app will be used on internal network to which I do not have access to. The client currently does not know the API URL and even if he would, it can change at any moment. That's the reason why I need this to work.
In that case you should use environments
You can find them in src/environments directory.
For local development you have environment.ts file, for production environment.prod.ts
Example environment.ts:
export const environment = {
production: false,
apiUrl: 'http://www.localhost.local/api/v1/'
};
environment.prod.ts:
export const environment = {
production: true,
apiUrl: 'http://www.producitonurl.com/api/v1/'
};
To use this apiUrl value you need to import environment file in typescript file like this:
You need to import environment.ts not any other environment file, because angular is replacing it while building application
import {environment} from '../environments/environment';
#Injectable({
providedIn: 'root'
})
export class MyApiService {
apiUrl = environment.apiUrl;
}
But in case you want to use method from <script type="module" src="assets/js/api_url.js"></script>, just use window object like this:
private api_url: string = (window as any).apiUrl();

Using Angular to declare a new service instance per module?

I'm using angular. I already know that when an appmodule is importing modules which declares providers, the root injector gets them all and the service is visible to the app - globally. (I'm not talking about lazy loaded modules)
But is it possible that each module will have its own instance of the service?
I thought of maybe something like this :
#NgModule({
providers: [AService]
})
class A {
forRoot() {
return {
ngModule: A,
providers: [AService]
}
}
forChild() {
return {
ngModule: A,
providers: [AService]
}
}
}
But I don't know if it's the right way of doing it
Question
How can I accomplish service per module ?
STACKBLITZ : from my testing , they are using the same service instance
When we provide a service in a feature module that is eagerly loaded
by our app's root module, it is available for everyone to inject. - John Papa
So looks like there is no way to inject at feature-module level, as there are no module level injectors other than the one at root module.
But angular has nodes at each component level for the injector, so such a scenario will have to use coponent level-injectors I guess.
You can also have a parent component inject the service for different children sharing the same instance.
One way is to provide the services at component level. Not sure if that will work for you.
Also, check the multiple edit scenario in the docs
https://angular-iayenb.stackblitz.io
import { Component, OnInit } from '#angular/core';
import {CounterService} from "../../counter.service"
#Component({
selector: 'c2',
templateUrl: './c2.component.html',
styleUrls: ['./c2.component.css'],
providers:[CounterService]
})
export class C2Component implements OnInit {
constructor(private s:CounterService) { }
ngOnInit() {
}
}
Question
How can I accomplish service per module ?
Not with the default Injector. Default Injector keeps nodes at root level and component level, not at feature-module level. You will have to have a custom Injector if there is a real scenario.
Edit: the previous answer from me is not completely correct. The correct way to make this work is to use lazy loaded modules, provide the services there. The given service should not be provided with a static forRoot() method somewhere because then the lazy loaded module will access the root injector.
There is no actual reference to do so because that is not how angular is designed but if you want it that way you have to the opposite of
https://angular.io/guide/singleton-services#providing-a-singleton-service
and
https://angular.io/guide/singleton-services#forroot
Old not completely correct:
You simple have to declare the service you want to be single instance for each and every module within the providers meta data of each and every module. Then Angular will not reuse any instance of the service.
Giving scenario: You have two modules, ModuleA and ModuleB, both need the service but different instance, then you will declare them in the providers section of ModuleA and ModuleB.
Reference:
https://angular.io/guide/singleton-services

Add providers post-declaration of AppModule

Angular 2+ registers providers in the following way:
// #NgModule decorator with its metadata
#NgModule({
declarations: [...],
imports: [...],
providers: [<PROVIDERS GO HERE>],
bootstrap: [...]
})
export class AppModule { }
I want to register application-scoped providers separately from this declaration site.
Specifically, I am using NSwag to generate service clients for my entire Web API and I want to dynamically add them all as providers. However, I'm not sure how to do that since #NgModule is an attribute applied to this AppModule class.
Is this possible?
Any DI provider needs to be included in the module at compile time.
Since Angular dependency injection works with Typescript type symbols / tokens, there's no Javascript functionality to accomplish the same task after it's compiled.
What you can do is dynamically add the provider at compile time, like so:
import { Load, SomeToken } from '../someplace';
#NgModule({
declarations: [...],
imports: [...],
providers: [
{
provide: SomeToken,
useValue: Load(someVariable)
],
bootstrap: [...]
})
export class AppModule { }
and then implement the Load function and token elsewhere:
export const SomeToken = new OpaqueToken<any>('SomeToken');
export const Load = (someVariable) => {
// logic to return an #Injectable here. Variable provided could be something like an environment variable, but it has to be exported and static
}
This approach of course has the limitation of needing to be known at compile time. The other approach is to either globally import all providers that are needed throughout the app regardless of circumstance and then lazy load components that have the appropriate provider injected for that circumstance (Angular will not initialize the provider until a component that utilizes it is initialized), or create a provider that in itself is able to perform the logic regardless of the dynamic criteria. An idea for that is to create another service that utilizes this service and resolves things based off of that dynamic criteria (i.e. you could have a method called GetLoginInfo on the first service and the second service would be able to resolve the correct API call for that method.)
If it's just API information you need (i.e. URLs), then you could possibly achieve the above by grabbing the URL information from a config.json file or API call, and inject those values into the service so the calls and tokens remain the same but use different values. See here for more information on how to accomplish that.

Angular module - Error encountered resolving symbol values statically

I've created the below module, following this guide:
#NgModule({
// ...
})
export class MatchMediaModule {
private static forRootHasAlreadyBeenCalled: boolean = false;
// The method is used for providing the
// feature module's providers only ONCE
static forRoot(): ModuleWithProviders {
if (this.forRootHasAlreadyBeenCalled) {
throw new Error('ModuleWithProviders - forRoot() should only be called once in the root module!');
}
this.forRootHasAlreadyBeenCalled = true;
return {
ngModule: MatchMediaModule,
providers: [MatchMediaService],
};
}
}
After importing the MatchMediaModule module into the CoreModule:
#NgModule({
imports: [
CommonModule,
MatchMediaModule.forRoot() // <--
],
})
export class CoreModule { }
and running ng serve, the following error is thrown:
ERROR in Error encountered resolving symbol values statically. Calling
function 'MatchMediaModule', function calls are not supported.
Consider replacing the function or lambda with a reference to an
exported function, resolving symbol CoreModule in
/Users/alex/www/mdello-port/src/app/core/core.module.ts, resolving
symbol CoreModule in
/Users/alex/www/mdello-port/src/app/core/core.module.ts
However, after making some minor change, causing the cli to recompile the app, everything runs without an issue.
Removing the forRoot() method and providing the service directly also seems to work. Nevertheless, I'd like to preserve the benefits of guarding against multiple singleton instance creation during lazy loadings of the mentioned module.
Is there a way to fix this error without compromising the aforementioned benefit?
Rewriting the forRoot() in the following way has remedied the issue:
#NgModule({
// ...
})
export class MatchMediaModule {
// ** REMOVED **
// The method is used for providing the
// feature module's providers only ONCE
static forRoot(): ModuleWithProviders {
// ** REMOVED **
return {
ngModule: MatchMediaModule,
providers: [MatchMediaService],
};
}
}
Seems like the problem was caused by side effects of the forRoot() method.

Categories