Angular 2 Material: same icon from same file displays in one component, not the other - svg

I'm using Angular 2 rc 5 with ng2-Material alpha 7-2.
The basic problem is that using <md-icon svgIcon='icon'> to show the same icon from the same svg file will work in one component but not the other even though MdIconModule and MdIconRegistry are provided to the root AppModule
To replicate the problem
Open this plunker
Notice that MdIconModule and MdIconRegistry are imported as part of MaterialModule.forRoot() into the main AppModule
Open the AppComponent and notice the call to addSvgIconSet() to register icons
In the template, <md-icon svgIcon='ic_account_box_24px'> is used to display the icon. In the view the icon is shown successfully above the nav bar.
Open app/crisis-center/crisis.list.component and open the Crisis Center in the view.
Note in the template, the same <md-icon> is present. However, no icon is displayed above the crisis list in the view. The DOM inspector from browser dev tools shows that the angular parser didn't even recognize it as an angular component (in the dom, it's left exactly as in the code)
Because I imported the icon module and service into the root module I expected the service to be a singleton available to the whole app. Since I use that service to register the icons with iconRegistry.addSvgIconSet() in my bootstrapped AppComponent, I expected the icons to be accessible throughout the app.
PS: this may be related to what I reported yesterday, although in that case the app crashes; in this case the icon is just not shown.

import { NgModule, ModuleWithProviders } from '#angular/core';
import { MdButtonModule } from '#angular2-material/button';
import { MdCardModule } from '#angular2-material/card';
import { MdCheckboxModule } from '#angular2-material/checkbox';
import { MdCoreModule } from '#angular2-material/core';
import { MdInputModule } from '#angular2-material/input';
import { MdProgressCircleModule } from '#angular2-material/progress-circle';
import { MdToolbarModule } from '#angular2-material/toolbar';
import { MdMenuModule } from '#angular2-material/menu';
import { MdIconModule} from '#angular2-material/icon';
#NgModule({
exports: [
MdButtonModule,
MdCardModule,
MdCheckboxModule,
MdCoreModule,
MdInputModule,
MdProgressCircleModule,
MdToolbarModule,
MdMenuModule,
MdIconModule
]
})
export class MaterialModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MaterialModule,
providers: [
]
};
}
}

(this is a reproduction of my post here because the two issues are really the same)
I figured out that because MdIconModule itself has MdIconRegistry service in its providers array, every time another module imports it, a new instance of the service is provided. As a result, all components that are loaded at bootstrap time and that belong to the same AppModule share the same instance of this service. However, components loaded later (via lazy-loading) have a different instance of the service and as a result cannot see the icons registered at bootstrap time.
With help from James, I've used the workaround of a specially crafted module that does not use MdIconModule at all, but rather declares the MdIcon class alone. I then provide the MdIconRegistry service separately, and only to the root AppComponent. The result is that there is only one instance of the service app-wide and icons registered at bootstrap time are available everywhere.
Modified MdIconFixedModule
#NgModule({
imports: [CommonModule, HttpModule],
declarations: [MdIcon],
exports: [MdIcon],
providers: [],//leave empty to avoid multiple instances of MdIconRegistry
})
export class MdIconFixedModule {
static forRoot() {
return {
ngModule: MdIconFixedModule,
//will be available only to whoever calls .forRoot()
providers: [MdIconRegistry]
};
}
}
Modules that need to just use icons can import MdIconFixedModule as this doesn't contain the MdIconRegistry. The AppModule that also needs to register icons imports MdIconFixedModule.forRoot() which does contain the service.
The details of this implementation can be seen here.
This limitation of ng-Material Modules is slotted to be fixed with the alpha 8 release

Related

NestJS dynamic module registration

I have a question regarding dynamic modules importing.
Let's say I have #Injectable service which depends on the parameter that came from the configuration:
#Injectable()
export class StatsService {
constructor(#Inject(SERVICE_NAME) private serviceName: string) {}
...
}
Let's say I expose this service through the Dynamic module:
#Module({})
export class CoreServicesModule {
static register(coreConfig: CoreServicesConfig): DynamicModule {
const { serviceName } = coreConfig;
return {
module: CoreServicesModule,
providers: [
{
provide: SERVICE_NAME,
useValue: serviceName
},
StatsService
],
exports: [
StatsService
]
};
}
}
Let's say my application is pretty big, and I have a lot of different modules.
But I need StatsService in every one of them.
So for example to be able to use StatsService in one of the modules i need to do something like:
#Module({
imports: [CoreServicesModule.register({ serviceName: 'test', ... })]
})
export class SomeModule { ... }
And I need to do it for each module...
Do we have a way to do it only once in NestJS?
Or how we can re-use the already registered module?
#Global decorator also not help me here cause I still need to import the module in each place...
#Global() decorator will help you if you are interested in using StatService in all other modules without the need to import that module!
BUT from your code it seems that each module that import CoreServicesModule would also make a small adjustment to it by dynamically specifying the providers it has (such as ValidationPipe).
in that case you should NOT make it #Global() since global modules should be registered only once

NestJS inject custom TypeOrm repository based on an Interface

I'm currently working through the database integration docs for NestJS using TypeOrm. In these docs there are examples that show how to inject a custom database repository using the app.module from NestJS. All of these examples inject classes using the actual type of the custom repository.
#Injectable()
export class AuthorService {
constructor(private authorRepository: AuthorRepository) {}
}
This code is injected via the app.modules by providing a import like such:
#Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
This works well if you are fine with programming against an implementation, but I prefer to use an interface in my classes. I've already found the solution to injecting classes via an interface with NestJS in a previous question, but when I try to inject my custom repository like that, it doesn't seem to instanciate correctly and becomes undefined.
(node:16658) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'save' of undefined
Because of this, I assume you can only inject customRepositories via the forFeature() call in the app.module, but that won't allow me to use interfaces for injection, as far as I know. Is there any other way I can inject a custom TypeOrm repository without having the replace all my interfaces for the implementation of my custom repository? Thanks in advance!
Edit
Here is my current code, I managed to get it to inject, but this still forces me to use the implementation instead of the interface each time I call the constructor. This is mainly an issue when testing due to mocking.
#CommandHandler(FooCommand)
export class FooHandler
implements ICommandHandler<FooCommand> {
private fooRepository: IFooRepository; // Using Interface as a private property.
private barEventBus: IEventBus;
constructor(fooRepository: FooRepository,
barEventBus: EventBus) { // Forced to use implementation in constructor for injection.
this.fooRepository = fooRepository;
this.barEventBus = barEventBus;
}
#EntityRepository(FooEntity)
export class FooRepository extends Repository<FooEntity> implements IFooRepository {
getFoo() {
// Do stuff
}
}
#Module({
imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([FooRepository]],
// Other module setup
})
export class AppModule {}
It should work with using the InjectRepository decorator where you specify the Repository but then you type is as your interface instead and when testing you just provide the IFooRepository!
Example code:
constructor(#InjectRepository(FooRepository) fooRepository: IFooRepository,
barEventBus: EventBus) {
Edit: This answer is crap, that abstract-class-as-interface hack used does not work out as the defined methods seem to be optional to implement despite being marked as abstract.
Well, kind of got it working. Based on this answer https://stackoverflow.com/a/74561702/913136 I used an abstract class as interface (you can actually implement it) for not being required to pass strings around as tokens. Only drawback is the misuse of the abstract class. Not sure yet if I like it.
Using an actual interface in the same way seems not to be possible unfortunately. Urgh.
#Module({
imports: [
TypeOrmModule.forRoot({
...dataSource.options,
autoLoadEntities: true,
}),
TypeOrmModule.forFeature([Listing]),
],
controllers: [ViewListingController],
providers: [
{
provide: ListingRepository,
useClass: TypeOrmListingRepository,
},
],
})
makeshift interface:
import { Listing } from "./Listing";
export abstract class ListingRepository {
abstract findMostRecent: () => Promise<Listing[]>;
}
implementation:
import { Listing, ListingRepository } from "../../Domain";
import { Injectable } from "#nestjs/common";
import { Repository, DataSource } from "typeorm";
#Injectable()
export class TypeOrmListingRepository
extends Repository<Listing>
implements ListingRepository
{
constructor(private dataSource: DataSource) {
super(Listing, dataSource.createEntityManager());
}
findMostRecent() {
return this.find({});
}
}
import { Controller, Get } from "#nestjs/common";
import { ListingRepository } from "../Domain";
#Controller("listings")
export class ViewListingController {
constructor(private readonly listingRepo: ListingRepository) {}
#Get("/most-recent")
listMostRecent() {
return this.listingRepo.findMostRecent();
}
}

Using imported interceptor on controller not working in nestjs

I like to organise my project along feature lines with different modules for cross cutting concerns eg: configuration, authentication, etc. However when importing an Interceptor into a feature module for use with a Controller Nest doesn't seem to reuse the existing instance.
controllers.module.ts
#Module({
imports: [
// ConfigService is exported from this module.
ConfigModule
],
providers: [
// For debugging purposes I used a factory so I could place a breakpoint and see
// when the Interceptor is being created.
{
provide: MyInterceptor,
useFactory: (config: ConfigService) => new MyInterceptor(config),
inject: [
ConfigService
]
}
],
exports: [
MyInterceptor
]
})
export class ControllersModule {
}
customer.module.ts
#Module({
imports: [
ControllersModule
],
controllers: [
CustomerController
],
providers: [
CustomerService
]
})
export class CustomerModule {
}
customer.controller.ts
#Controller("/customers")
#UseInterceptors(MyInterceptor)
export class CustomerController {
constructor(private readonly customerService: CustomerService) {
}
#Get()
customers() {
return this.customerService.findAll();
}
}
When the application starts up, I can see the MyInterceptor provider factory being called, with an instance of ConfigService. However then I see the following error on the console
error: [ExceptionHandler] Nest can't resolve dependencies of the MyInterceptor (?). Please make sure that the argument ConfigService at index [0] is available in the CustomerModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current CustomerModule?
- If ConfigService is exported from a separate #Module, is that module imported within CustomerModule?
#Module({
imports: [ /* the Module containing ConfigService */ ]
})
Now maybe there's something about how Nest instantiates/uses Interceptors that I'm not understanding but I thought that given that MyInteceptor had been created, and the ControllersModule imported by CustomerModule that the bean would have been available and applied to CustomerController.
Is there something I'm missing here?
Interceptors (along with other request lifecycle classes) are kinda like pseudoproviders in that that are #Injectable() but they aren't added to a providers array for binding. You can bind then via the providers array, (APP_INTERCEPTOR) but that will cause it to be bound globally.
Because interceptors can't be added to a providers array the way you are trying, you need to instead add the ConfigModule to whatever module uses the interceptor.
and as a side-note, you shouldn't use #Res with interceptors in nestjs
why
look, when you are using an interceptor, you are handling (with using .handle()) the stream of response (observable) not a whole package of it, but using express #Res actually is somehow getting around the whole flow of response streaming.
this is also explicitly mentioned in nestjs official documents:
We already know that handle() returns an Observable. The stream
contains the value returned from the route handler, and thus we can
easily mutate it using RxJS's map() operator.
WARNING
The response mapping feature doesn't work with the
library-specific response strategy (using the #Res() object directly
is forbidden).

NestJS cannot verify argument is available in current context/Mongoose Inject Model

I followed the MongoDB tutorial on the nestjs website but am unable to replicate it in my own project, I have cloned the nextjs repo and verified that the mongo sample supplied there runs fine.
This is what I currently have (minified):
app.module.ts
#Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/nest')
],
modules: [
CampaignModule
]
})
export class AppModule implements NestModule {}
}
campaign.module.ts
#Module(
{
modules: [SharedModule],
controllers: [CampaignController],
components: [CampaignService],
imports: [MongooseModule.forFeature([{name: 'Campaign', schema: campaignSchema}])]
})
export class CampaignModule {}
campaign.service.ts
#Component()
export class CampaignService {
constructor(#InjectModel(campaignSchema) private readonly campaignModel: Model<CampaignInterface>) {}
}
this builds fine as expected but when I run it i get this error:
Nest can't resolve dependencies of the CampaignService (?). Please verify whether [0] argument is available in the current context.
So because in my app.module.ts I have the campaign module in the modules section (how i have had it so far working fine prior to mongodb) I moved it into the imports sections with the MongooseModule now it will build and run fine, but the routes inside the controller for campaign.controller.ts do not register at all, they just return 404's. This is weird because the example in the nestjs repo have the CatsModule inside the imports with the MongooseModule and it works.
Any tips here would be appreciated. this is my schema and interface for the campaign object as well:
export interface CampaignInterface extends Document, Campaign {
}
export const campaignSchema: any = new mongoose.Schema({
campaignId: String
});
EDIT 1
I moved all my modules into the import section of my app.module.ts file as before I tried it with CampaignModule in imports and all the others in modules: []. It now fails right away when served with the same error and no routes are registered.
Ok I figured out what is going on,
It seems that if you have both a modules: [] and imports: [] inside your #Module decorator the modules: [] takes over and completely ignores the imports: []. So in my campaign.module.ts file I moved the SharedModule from modules: [] into imports: [] after the MongooseModules.forFeature import. It now works and write to mongo!

how to call class without declaring it in constructor angular 2

I am trying to use NFC module of Ionic 2. This is my code:
nfc-scan.ts:
import {Component} from '#angular/core';
import {IonicPage, NavController, NavParams, Platform} from 'ionic-angular';
import { Device } from '#ionic-native/device';
import {NFC, Ndef} from '#ionic-native/nfc';
#IonicPage()
#Component({
selector: 'nfc-scan',
templateUrl: 'nfc-scan.html',
})
export class NfcScan {
#ViewChild(Nav) nav: Nav;
NFC: NFC;
constructor(public platform: Platform,
public navCtrl: NavController,
public navParams: NavParams,
) {
}
// NFC Scanning
checkNFC()
{
this.NFC.enabled()
.then(() => {
this.addListenNFC();
})
.catch(err => {
console.log(err);
});
}
}
nfs-scan.html
<ion-content padding>
<button on (click)="checkNFC()">Scan NFC</button>
</ion-content>
When I run the application, I get the error:
Property 'enabled' does not exist on type 'typeof NFC'.
I know I am not declaring NFC in the constructor of nfc-scan.ts. But when I do so, the page won't even load altogether.
I did finally manage to find a solution to this problem. It turns out that it's true you can't use a class without declaring it in the constructor of the class where you want to use it.
In my case, the issue was, that I was running the app in my machine's (Macbook) browser, whereas NFC plugin can only be instantiated on a phone that supports NFC (Like camera plugin). Having said that, Ionic now provides ability to mock plugins in a way so that you can use them in your machine's browser.
To use an Ionic Native plugin in the browser and ionic serve session,
you just need to extend the original plugin class and override the
methods you’d like to mock.
Source: https://ionicframework.com/docs/native/browser.html
Hope it helps someone like me.
You would usually see an output on the console, when the page is not loading. Make sure to use private nfc : NFC in the constructor;

Resources