There is the following standard NestJS code:
import { Controller, Get } from '#nestjs/common';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): string {
return this.appService.getHello();
}
}
Also there is the code for module app:
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
#Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
I don't understand how app understands that app controller expects to get app service as the first argument? At first I thought that app just looks at providers array and put all services from this array in controller's constructor. But I could change the code in this way:
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
#Module({
imports: [],
controllers: [AppController],
providers: [AppService, NewService],
})
export class AppModule {}
Now I can use this NewService not only in AppController class, but in AppService class. How app knows that now I could use NewService in AppService class? Now I don't understand how we could do it, because TypeScript is just syntax sugar and exists only until compilation will be done and as I know app couldn't look at type of argument in AppService (for example fileService: FileService) and put the right service as argument when AppService will be created. I need technical details under the cover of this mechanism, not only use cases from official tutorial.
By registering it in the providers array you register it to the Nest IoC container.
If you check compiled js code, then you will find something like
WeatherHttpProvider = __decorate([
(0, common_1.Injectable)(),
__param(1, (0, common_1.Inject)(weather_parser_1.WEATHER_PARSER)),
__metadata("design:paramtypes", [axios_1.HttpService, Object])
], WeatherHttpProvider);
which in this case tells the WeatherHttpProvider about injected constructor params. This way it knows which class from the IoC container to use.
Related
Can't figure out what's the problem of my code. (I'm new with nestjs, I'm trying to learn it by passing some apps to it). Console log says:
Nest can't resolve dependencies of the UrlsAfipService (?). Please
make sure that the argument at index [0] is available in the ApiModule
context.
UrlsAfipService
import { Injectable } from '#nestjs/common';
import { AfipUrls } from './urls'
#Injectable()
export class UrlsAfipService {
constructor(
private readonly afipUrls: AfipUrls,
) {}
getWSAA () {
return this.afipUrls.homo().wsaa; // <- change to prod() for production
}
getService (service: string) {
return this.afipUrls.homo().service.replace('{service}', service)
}
}
AfipUrls
export class AfipUrls {
homo() {
return {
wsaa: 'https://url.com',
service: 'https://url.com'
}
}
prod() {
return {
wsaa: 'url.com',
service: 'url.com'
}
}
}
ApiModule
import { Module } from '#nestjs/common';
import { ApiController } from './api.controller';
import { UrlsAfipService } from './urls-afip.service'
import { WsaaService } from './wsaa.service'
import { DescribeService } from './describe.service';
#Module({
controllers: [ApiController],
providers: [UrlsAfipService, WsaaService, DescribeService]
})
export class ApiModule {}
AppModule
import { Module } from '#nestjs/common';
import { ApiModule } from './api/api.module';
import { AppController } from './app.controller';
import { AppService } from './app.service';
#Module({
imports: [ApiModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
You have declared AfipUrls as a dependency for UrlsAfipService but it is not provided in any module.
So you have to add AfipUrls to the providers array of your ApiModule. Then it can be injected.
providers: [UrlsAfipService, WsaaService, DescribeService, AfipUrls]
// ^^^^^^^^
Note though, that encoding environment specific values in your code base might be a code smell. Consider creating a ConfigService that encapsulates environment specific variables that are read from environment variables or .env files using dotenv. See this answer for more information.
I'm trying to inject a provider inside a guard that is wrapped in a decorator, but Nest is not being able to resolve dependencies, giving me the next error:
[ExceptionHandler] Nest can't resolve dependencies of the SecuredGuard (Reflector, ?). Please make sure that the argument at index [1] is available in the SecuredGuard context.
The main purpose of my approach is to avoid using two separate decorators like this:
#Controller()
export class AppController {
#Get()
#Secured('admin')
#UseGuards(SecuredGuard)
getHello(): string {
return this.appService.getHello();
}
}
And instead insert the #UseGuards(SecuredGuard) inside my custom decorator #Secured('admin') so it ends up like this:
#Controller()
export class AppController {
#Get()
#Secured('admin')
getHello(): string {
return this.appService.getHello();
}
}
This is how I'm implementing my custom decorator:
export function Secured(...roles: string[]){
const setMetadata = ReflectMetadata('roles', roles)
const setupGuard = UseGuards(SecuredGuard)
return (target: any, key?: string, descriptor?: any) => {
setMetadata(target, key, descriptor);
setupGuard(target, key, descriptor);
}
}
And this is my SecuredGuard, the AuthService is the dependency that couldn't be resolved:
#Injectable()
export class SecuredGuard implements CanActivate {
constructor(
private readonly _reflector: Reflector,
private readonly _authService: AuthService
) { }
async canActivate(context: ExecutionContext): Promise<boolean> {...}
}
Both secured.guard.ts and secured.decorator.ts are part of secured.module.ts
#Module({
imports: [
SecuredGuard,
AuthModule
],
exports: [
SecuredGuard
],
providers: [
AuthService
]
})
export class SecuredModule {}
Which is using the AuthService being exported from auth.module.ts
#Module({
controllers: [
AuthController
],
providers: [
AuthService
],
imports: [
EmailModule
],
exports: [
AuthService
]
})
export class AuthModule { }
And secured.module.ts is being imported by app.module.ts
#Module({
imports: [
SecuredModule
],
controllers: [
AppController
],
providers: [
AppService
],
})
export class AppModule { }
I don't know if I'm using the appropriate approach, or even if it's possible what I'm trying to do, any clues would be really appreciated!
In general, your solution seems to work, see this running example:
However, there are some mistakes in your module declarations:
1) In your AppModule, the AuthService is not available, since neither is the AuthModule imported directly or exported by the SecuredModule. That's why you're getting the error.
2) You don't have to declare your guards in any module, they will just be available globally. Only put modules in your imports array.
3) You're providing the AuthService multiple times, so you will have different instances of it. You should only provide it once and then only export (or re-export) your provider, but not provide it again.
4) ReflectMetadata was deprecated in v6; use SetMetadata instead.
I am using Angular Universal. I have created a PlatformService to detect which platform I am currently working on.
/* platform.service.js */
import { Injectable, Inject, PLATFORM_ID } from '#angular/core';
import { isPlatformBrowser, isPlatformServer } from '#angular/common';
#Injectable({
providedIn: 'root'
})
export class PlatformService {
constructor(
#Inject(PLATFORM_ID) private platformId: Object
) {
this.platformId; // this is coming out undefined
}
isBrowser() {
return isPlatformBrowser(this.platformId);
}
isServer() {
return isPlatformServer(this.platformId);
}
}
I am creating a BaseComponent for common handling of my route binded components.
/* base.component.ts */
import { Component, OnInit, Inject } from '#angular/core';
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#Component({
selector: 'app-base',
template: '',
})
export class BaseComponent implements OnInit {
protected platformService: PlatformService;
constructor() {
this.platformService = InjectorHolderService.injector.get(PlatformService);
console.log(this.platformService);
}
}
Since this component will be inherited by many components, I didn't want to pass the PlatformService through super(). So I decided to go with creating an Injector.
/* app.module.ts */
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#NgModule({ ... })
export class AppModule {
constructor() {
InjectorHolderService.injector = Injector.create({
providers: [
{
provide: PlatformService,
useClass: PlatformService,
deps: [], // I think i need to put something here, but not sure.
}
]
});
}
}
And a service which can hold all the injected module for future use.
/* injector-holder.service.ts */
import { Injectable, Injector } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class InjectorHolderService {
static injector: Injector;
}
But #Inject(PLATFORM_ID) private platformId: Object is giving out undefined, because of which I am not able to detect the platform.
What am I missing here? or If there is a better approach to achieve the above functionality.
Please let me know if you guys need to see any other file.
I am not sure whether the following approach is good or bad, currently, it is the only thing working for me. Would love to hear any new approach to it.
Since PlatformService needed #Inject(PLATFORM_ID) which is provided only from AppModule, the new Injector I created was not able to find any value for #Inject(PLATFORM_ID) and hence undefined.
So, instead of using class PlatformService in Injector, now I am using PlatformService's instantiated object and hence was able to access everything fine in BaseComponent.
Modified my AppModule like following:
/* app.module.ts */
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#NgModule({ ... })
export class AppModule {
constructor(
private platformService: PlatformService,
) {
InjectorHolderService.injector = Injector.create({
providers: [
{
provide: PlatformService,
useValue: this.platformService, // notice the change of key, using value not class
deps: [],
}
]
});
}
}
Will try to add a minimal repo to recreate this issue and share with you guys, If anyone wants to explore more.
I am learning Angular 2 and trying to follow their tutorial.
Here is the code of the service that returns "Promise" of a mock object Folder.
import {Injectable, OnInit} from "#angular/core";
import {FOLDER} from "./mock-folder";
import {Folder} from "./folder";
#Injectable()
export class FolderService {
getFolder():Promise<Folder>{
return Promise.resolve(FOLDER);
}
}
It is declared in providers of my FolderModule
import {NgModule} from "#angular/core";
import {CommonModule} from "#angular/common";
import {FolderComponent} from "./folder.component";
import {MaterialModule} from "#angular/material";
import {FolderService} from "./folder.service";
#NgModule({
imports:[CommonModule, MaterialModule.forRoot()],
exports:[FolderComponent],
declarations:[FolderComponent],
providers:[FolderService]
})
export class FolderModule{
}
Folder component should import FolderService and use it to obtain the Folder object.
import {Component, OnInit} from "#angular/core";
import {Folder} from "./folder";
import {FolderService} from "./folder.service";
#Component({
selector: 'folder',
moduleId: module.id,
templateUrl: "./folder.component.html"
})
export class FolderComponent implements OnInit {
folder:Folder;
constructor(private folderService:FolderService) {
}
ngOnInit():void {
this.getFolder();
}
getFolder() {
this.folderService.getFolder().then((folder) => this.folder = folder);
}
}
And yes, i do import my FolderModule in the root module
#NgModule({
imports: [BrowserModule, CommonModule, MaterialModule.forRoot(), FolderModule, AppRoutingModule],
providers:[],
declarations: [AppComponent, LifeMapComponent, MyPageNotFoundComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
Here is the folder component template
<md-grid-list cols="3" [style.background] ="'lightblue'" gutterSize="5px">
<md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile>
</md-grid-list>
And here is the error i get in the console
EXCEPTION: Uncaught (in promise): Error: Error in
http://localhost:3000/app/folders/folder.component.html:1:16 caused
by: Cannot read property 'cards' of undefined TypeError: Cannot read
property 'cards' of undefined
export class Folder {
public id:number;
public title:string;
public cards:Card[];
}
export class Card{
id :number;
title:string;
}
Voland,
This can be solved by using the "Elvis" operator on the collection being iterated over.
<md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile>
Should instead be:
<md-grid-tile *ngFor="let card of folder?.cards">{{card?.title}}</md-grid-tile>
Note the "?" after folder -- this will coerce the whole path to 'null', so it won't iterate. The issue is with the accessor on a null object.
You could also declare folder to an empty array [] to prevent this.
EDIT: To any onlookers, note that the Elvis operator is not available in your Typescript code, as it's not supported by the language. I believe that Angular 2 supports it though, so it is available in your views (really useful for AJAX requests, where your data has not arrived at the point of component instantiation!)
Use *ngIf directive:
<md-grid-list *ngIf="folder" cols="3" [style.background] ="'lightblue'" gutterSize="5px">
<md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile>
</md-grid-list>
Angular tries to render the html before the promise is resolved, therefore folder is undefined and the exception is thrown.
With *ngIf="folder" you tell Angular that it should ignore child elements if the expression is falsy.
<md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile> will be added to the DOM if folder is not undefined.
Your error is in the classes:
export class Folder {
public id:number;
public title:string;
public cards:Card[];
}
export class Card{
id :number;
title:string;
}
You have forgotten the constructor:
export class Folder {
constructor(
public id:number,
public title:string,
public cards:Card[])
{}
}
export class Card{
constructor(
public id :number,
public title:string)
{ }
}
And probably as previously suggested use the elvis operator or ngIf.
I use this seed application to narrow down an error that keeps popping up (so debugging is easier...)
I keep getting this error when trying to add a data model to my shared module (in my browser console):
Error: (SystemJS) Can't resolve all parameters for Member: (?).(…)
The Member Class in question:
import * as _ from 'lodash';
import { Injectable } from '#angular/core';
#Injectable()
export class Member {
private id: string;
[key: string]: any;
constructor(private data?: any) {
if (data) {
this.id = data.id;
_.extend(this, data.attributes);
}
}
}
My SharedModule (the Member Class isn't referenced anywhere else for now):
import { NgModule, ModuleWithProviders } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { RouterModule } from '#angular/router';
import { ToolbarComponent } from './toolbar/index';
import { NavbarComponent } from './navbar/index';
import { NameListService } from './name-list/index';
import { Member } from './models/member.model';
#NgModule({
imports: [CommonModule, RouterModule],
declarations: [ToolbarComponent, NavbarComponent],
exports: [ToolbarComponent, NavbarComponent,
CommonModule, FormsModule, RouterModule]
})
export class SharedModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [NameListService, Member]
};
}
}
When I get rid of the constructor in class Member the error disappears:
import * as _ from 'lodash';
import { Injectable } from '#angular/core';
#Injectable()
export class Member {
private id: string;
[key: string]: any;
}
I am not using barrel imports as you can see since the order of the imports can cause the same error.
I am a bit stuck on how to solve this... Thanks
If the class is just to be used as a model, then don't add it to the #NgModule.providers and don't try to inject it. Just import the class into the class file where you need it, and just use it like you would any other normal class
import { Member } from './member.model';
#Component({})
class MyComponent {
member = new Member();
}
See Also:
Add Models/Entities/Objects to NgModule in Angular 2
Classes with the #Injectable() decorator get instantiated once by Angular as service providers. Angular uses reflection/type hinting to supply the instance with its dependency's.
Angular doesn't know what to give your Member class's constructor since its type is defined as any.