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
I'm creating a #Log() Decorator Function for debugging purposes;
I want that Decorator to delegate some of it's logic to a LoggingService that in turn depends on other services from the app...
I've been trying a lot of different things, the simplest/most straightforward way was to cache the Main (or Shared) Module's Injector as a static prop on the module itself (see StackBlitz example linked below), and that works for lazy-loaded modules, but not for eagerly loaded ones...
Non-working poc: https://stackblitz.com/edit/angular-j1bpvx?file=app%2Fdecorator.ts
Is there a way I could mkae use of that Service in there??
Thanks!
Class decorator is executed once on class definition. In order to avoid race condition when calling AppModule.injector.get(LoggingService) it should be moved to the place where AppModule.injector is already defined, i.e. class method.
It should be:
constructor.prototype[hook] = function (args) {
const loggingService = AppModule.injector.get(LoggingService);
loggingService.log({ ... })
...
This also creates tight coupling with AppModule and prevents the units from being reused or tested separately from it. It's recommended to use another object to hold injector property, e.g. assign injector not in main but in child module that is imported into AppModule:
export class InjectorContainerModule {
static injector: Injector;
constructor(injector: Injector) {
InjectorContainerModule.injector = injector;
}
}
Try stackblitz fixed
This will print
LoggingService: HelloComponent - ngOnInit was called
Minor changes - basically using ReflectiveInjector as in angular Injector#example
import { ReflectiveInjector } from '#angular/core';
const injector = ReflectiveInjector.resolveAndCreate([
{provide: 'loggingService', useClass: LoggingService}
]);
const loggingService = injector.get('loggingService');
I am sure you can use useExisting and use LoggingService as provider in your app module.
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.
While working inside Angular (Angular 4, 5), if a component raises Error (TypeError or null or undefined error or so), whole application breaks onward.
How can we deal with this, to catch errors on component level and possibly show a fallback UI, like React16 does using Error Boundaries.
I would approach it by handling the error at Component level and have a service that listens to any errors happening at Component or Service level.
Ex:
Throw the error from the service
catch the error in component
Handle the error, process it and send the Error event with details to ErrorService.
You can have a app level component "errorBannerComponent" which takes input from ErrorService and paint your UI.
As soon as the error is received in ErrorService, The errorBannerComponent should display the error on screen.
Hope it helps.
Also By default, Angular comes with its own ErrorHandler that
intercepts all the Errors that happen in our app and logs them to the
console, preventing the app from crashing. We can modify this default behavior by creating a new class that implements the ErrorHandler:
You can find more details and example here:
As the proposed solutions are rather dull. I tried to recreate it myself. The easiest solution would be to provide a module scoped custom ErrorHandler class.
Thanks to this, you could even create a multiple different ErrorBoundaries.
My proposed solution can be seen here: https://stackblitz.com/edit/angular-ivy-brb143?file=src/app/widget/widget.module.ts
What is really important for this solution to work (atleast it didn't work otherwise for me). Was to provide the custom error handler as a part of a module rather than a component directly.
The important bits from the solutions:
module:
/**
* This is really imporant as this allows us to provide a module scoped ErrorHandler
*/
#NgModule({
imports: [CommonModule],
declarations: [WidgetComponent],
providers: [{ provide: ErrorHandler, useClass: WidgetErrorHandler }],
exports: [WidgetComponent],
})
export class WidgetModule {}
component where we can throw, and catch error
#Component({
selector: 'app-widget',
templateUrl: './widget.component.html',
styleUrls: ['./widget.component.css'],
})
export class WidgetComponent implements OnInit {
constructor(#Inject(ErrorHandler) public widgetError: WidgetErrorHandler) {}
ngOnInit() {
this.widgetError.isError$.subscribe((error) =>
console.log('component can act on error: ', error)
);
}
public handleThrowErrorClick(): void {
throw Error('Button clicked');
}
}
and the handler iself
#Injectable()
export class WidgetErrorHandler implements ErrorHandler {
public isError$: Subject<Error | any> = new Subject();
handleError(error) {
console.log('Intercepted error', error);
this.isError$.next(error);
}
}
I am using TypeScript 1.6 with ES6 modules syntax.
My files are:
test.ts:
module App {
export class SomeClass {
getName(): string {
return 'name';
}
}
}
main.ts:
import App from './test';
var a = new App.SomeClass();
When I am trying to compile the main.ts file I get this error:
Error TS2306: File 'test.ts' is not a module.
How can I accomplish that?
Extended - to provide more details based on some comments
The error
Error TS2306: File 'test.ts' is not a module.
Comes from the fact described here http://exploringjs.com/es6/ch_modules.html
17. Modules
This chapter explains how the built-in modules work in ECMAScript 6.
17.1 Overview
In ECMAScript 6, modules are stored in files. There is exactly one
module per file and one file per module. You have two ways of
exporting things from a module. These two ways can be mixed, but it is
usually better to use them separately.
17.1.1 Multiple named exports
There can be multiple named exports:
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
...
17.1.2 Single default export
There can be a single default export. For example, a function:
//------ myFunc.js ------
export default function () { ··· } // no semicolon!
Based on the above we need the export, as a part of the test.js file. Let's adjust the content of it like this:
// test.js - exporting es6
export module App {
export class SomeClass {
getName(): string {
return 'name';
}
}
export class OtherClass {
getName(): string {
return 'name';
}
}
}
And now we can import it in these three ways:
import * as app1 from "./test";
import app2 = require("./test");
import {App} from "./test";
And we can consume imported stuff like this:
var a1: app1.App.SomeClass = new app1.App.SomeClass();
var a2: app1.App.OtherClass = new app1.App.OtherClass();
var b1: app2.App.SomeClass = new app2.App.SomeClass();
var b2: app2.App.OtherClass = new app2.App.OtherClass();
var c1: App.SomeClass = new App.SomeClass();
var c2: App.OtherClass = new App.OtherClass();
and call the method to see it in action:
console.log(a1.getName())
console.log(a2.getName())
console.log(b1.getName())
console.log(b2.getName())
console.log(c1.getName())
console.log(c2.getName())
Original part is trying to help to reduce the amount of complexity in usage of the namespace
Original part:
I would really strongly suggest to check this Q & A:
How do I use namespaces with TypeScript external modules?
Let me cite the first sentence:
Do not use "namespaces" in external modules.
Don't do this.
Seriously. Stop.
...
In this case, we just do not need module inside of test.ts. This could be the content of it adjusted test.ts:
export class SomeClass
{
getName(): string
{
return 'name';
}
}
Read more here
Export =
In the previous example, when we consumed each validator, each module only exported one value. In cases like this, it's cumbersome to work with these symbols through their qualified name when a single identifier would do just as well.
The export = syntax specifies a single object that is exported from the module. This can be a class, interface, module, function, or enum. When imported, the exported symbol is consumed directly and is not qualified by any name.
we can later consume it like this:
import App = require('./test');
var sc: App.SomeClass = new App.SomeClass();
sc.getName();
Read more here:
Optional Module Loading and Other Advanced Loading Scenarios
In some cases, you may want to only load a module under some conditions. In TypeScript, we can use the pattern shown below to implement this and other advanced loading scenarios to directly invoke the module loaders without losing type safety.
The compiler detects whether each module is used in the emitted JavaScript. For modules that are only used as part of the type system, no require calls are emitted. This culling of unused references is a good performance optimization, and also allows for optional loading of those modules.
The core idea of the pattern is that the import id = require('...') statement gives us access to the types exposed by the external module. The module loader is invoked (through require) dynamically, as shown in the if blocks below. This leverages the reference-culling optimization so that the module is only loaded when needed. For this pattern to work, it's important that the symbol defined via import is only used in type positions (i.e. never in a position that would be emitted into the JavaScript).
Above answers are correct. But just in case...
Got same error in VS Code. Had to re-save/recompile file that was throwing error.
How can I accomplish that?
Your example declares a TypeScript < 1.5 internal module, which is now called a namespace. The old module App {} syntax is now equivalent to namespace App {}. As a result, the following works:
// test.ts
export namespace App {
export class SomeClass {
getName(): string {
return 'name';
}
}
}
// main.ts
import { App } from './test';
var a = new App.SomeClass();
That being said...
Try to avoid exporting namespaces and instead export modules (which were previously called external modules). If needs be you can use a namespace on import with the namespace import pattern like this:
// test.ts
export class SomeClass {
getName(): string {
return 'name';
}
}
// main.ts
import * as App from './test'; // namespace import pattern
var a = new App.SomeClass();
In addition to A. Tim's answer there are times when even that doesn't work, so you need to:
Rewrite the import string, using the intellisense. Sometimes this fixes the issue
Restart VS Code
I had this issue and I had forgotten to export the Class.
In addition to Tim's answer, this issue occurred for me when I was splitting up a refactoring a file, splitting it up into their own files.
VSCode, for some reason, indented parts of my [class] code, which caused this issue. This was hard to notice at first, but after I realised the code was indented, I formatted the code and the issue disappeared.
for example, everything after the first line of the Class definition was auto-indented during the paste.
export class MyClass extends Something<string> {
public blah: string = null;
constructor() { ... }
}
Just in case this may works for you as it did form me, i had this files
//server.ts
class Server{
...
}
exports.Server = Server
//app.ts
import {Server} from './server.ts'
And this actually raised an error but i changed server.ts to
//server.ts
export class Server{
...
}
and it worked 😎👌
Note: i am using this config
"target": "esnext",
"module": "commonjs",
I faced the same issue in a module that has no exports. I used it for side-effects only. This is what the TypeScript docs say about importing side-effects modules:
Though not recommended practice, some modules set up some global state that can be used by other modules. These modules may not have any exports, or the consumer is not interested in any of their exports. To import these modules, use:
import "./my-module.js";
In that situation, you can fix the "File is not a module" error by simply exporting an empty object:
// side-effects stuff
export default {};
I faced the same issue ("File is not a module error") for import js in vue component
import handleClientLoad from "../../../public/js/calendar.js"
I do this and solve it
// #ts-ignore
import handleClientLoad from "../../../public/js/calendar.js"
The file needs to add Component from core hence add the following import to the top
import { Component } from '#angular/core';