Binding a class to an interface - dependency-injection

Using typescript, I can easily bind classes to themselves:
bootstrap(MyAppComponent, [MyClass]);
However, I would like to bind my class to an interface, like such:
boostrap(MyAppComponent, [???]);
such that I can inject it as follows:
class MyAppComponent {
constructor(my_class : IMyClass){
}
};
Is this possible in Angular2? If yes, how to I have to specify the binding?

To make it short the problem is that Interfaces disappear when typescript is compiled. So you'd have to use #Inject with a string.
Or there's another option, if you check the last article of Victor Savkin you can find this in the comments :
Some background. In TypeScript, interfaces are structural and are not retained at runtime. So you have to use ILoginService as follows:
constructor(#Inject("ILoginService") s:ILoginService).
You don't have to use a string - any object can be passed in there. We actually provide an object called OpaqueToken that can be used for this purpose.
interface ILoginService { login(credentials);}
const ILoginService = new OpaqueToken("LoginService");
can be used like this:
constructor(#Inject(ILoginService) s:ILoginService).

I dont know if it is possible with interface as interface will not be available at runtime (javascript does not know about interface).
But it can be done using abstract classes.
//abstract-parent-service.ts
export class DatabaseService{
getService: ()=>string;
}
//hibernate.service.ts
import {DatabaseService} from "./abstract-parent-service";
export class HibernateService implements DatabaseService{
constructor() { }
getService() {
return "i am hibernate";
}
}
//jdbc.service.ts
import {DatabaseService} from "./abstract-parent-service";
export class JDBCService implements DatabaseService{
constructor() { }
getService() {
return "i am Jdbc";
}
}
//cmp-a.component.ts
import {DatabaseService} from "./abstract-parent-service";
import {HibernateService} from "./hibernate.service";
#Component({
selector: 'cmp-a',
template: `<h1>Hello Hibernate</h1>`,
providers: [{provide: DatabaseService, useClass: HibernateService}]
})
export class CmpAComponent {
constructor (private databaseService: DatabaseService) {
console.log("Database implementation in CompA :"+this.databaseService.getService());
}
}
//cmp-b.component.ts
import {DatabaseService} from "./abstract-parent-service";
import {HibernateService} from "./hibernate.service";
#Component({
selector: 'cmp-b',
template: `<h1>Hello Jdbc</h1>`,
providers: [{provide: DatabaseService, useClass: JDBCService}]
})
export class CmpAComponent {
constructor (private databaseService: DatabaseService) {
console.log("Database implementation in CompA :"+this.databaseService.getService());
}
}
But the problem with this implementation is HibernateService and
JDBCService are not able to extend any other class now because they
have already got married with DatabaseService.
class A{
constructor(){
console.log("in A");
}
}
class B extends A{
constructor(){
super();
console.log("in B");
}
}
class C extends A{
constructor(){
super();
console.log("in C");
}
}
let c = new C();
//This thing is not possible in typescript
class D extends B, C{//error: Classes can only extend a single class
constructor(){
super();// which constructor B or C
console.log("in D");
}
}
If you are using this pattern for DI, make it sure that your child class services are not going to extend any other functionality in future.

Related

NestJs: inject service (or anything) into imported module

Is there a way better than below to inject a service or component inside an imported module?
export interface AmqpInterceptor{
after(message:any):Promise<void>;
}
export class AmqpInterceptors extends Array<AmqpInterceptor>{
}
//generic library module
#Module({
providers:[{
provide: AmqpInterceptors,
useValue: []
}]
}
export class AMQPModule implements OnModuleInit{
static register(options: AMQPOptions): DynamicModule {
const providers = options.providers || []
return {
module: AMQPModule,
providers: [
...providers,
OtherProvider
]
}
}
}
//end user module
#Module({
imports: [
AMQPModule.register(({
// I had to create a factory method to pass providers as an argument.
// I would think that it is not a good practice
providers: [{
provide:AmqpInterceptors,
useValue:[MyCustomInterceptor]
}]
})
],
providers: [
]
})
export class QueueModule {
}
Current working solution: I declare a default empty array in the generic module and a factory method that allows to pass custom value in module construction.(In my happiest world I declare multiple instances of an interface and then, DI collects all of these, but I thinks this is really impossible in NestJs)
you can use a package like #golevelup/nestjs-discovery to help you with.
Basically you've to do the following:
// AMQP Module
// amqp-interceptor.decorator.ts
import { SetMetadata } from '#nestjs/common';
export function AmqpInterceptor() {
return SetMetadata('AMQP_INTERCEPTOR', true)
}
// amqp-explorer.ts
import { OnModuleInit } from '#nestjs/common'
#Injectable()
export class AmqpExplorer implements OnModuleInit {
constructor(
private readonly discoveryService: DiscoveryService,
) {}
async onModuleInit(): Promise<void> {
const amqpInterceptorProviders = await this.discoveryService.providers('AMQP_INTERCEPTOR')
// you can store this list, to be queried by some
// other provider to use interceptors, etc.
}
}
// amqp.module.ts
#Module({ providers:[AmqpExplorer]})
export class AMQPModule { }
// end user module
// end-user.module.ts
#Module({ imports:[AMQPModule], providers: [SomeAmqpInterceptor] })
export class EndUserModule { }
// some-amqp.interceptor.ts
#Injectable()
#AmqpInterceptor()
export class SomeAmqpInterceptor {
run(): void { console.log('intercepting') }
}
Obs:
Interceptor decorator: you can add any params which would help you to improve your api
I suggest this (list of providers) instead of injecting decorated providers into your module, because I don't know how do you plan to use them. This way, you've a list of providers and can invoke each one.
AMQP_INTERCEPTOR string can collide with some other metadata, it's a good practice add something more specific to avoid two metadata with same one in a module
To invoke the interceptors later:
import { ExternalContextCreator } from '#nestjs/core/helpers/external-context-creator'
export class Runner {
constructor(
private readonly handler: DiscoveredMethodWithMeta,
private readonly externalContextCreator: ExternalContextCreator,
) { }
run(): void {
const handler = this.externalContextCreator.create(
this.handler.discoveredMethod.parentClass.instance,
this.handler.discoveredMethod.handler,
this.handler.discoveredMethod.methodName,
)
const handlerResult = await handler(content)
}
Didn't run local, but I think this is a good way to start

How to Pass data between two sibling components?

<container>
<navbar>
<summary></summary>
<child-summary><child-summary>
</navbar>
<content></content>
</container>
So, in I have a select that do send value to and .
OnSelect method is well fired with (change) inside component.
So, I tried with #Input, #Output and #EventEmitter directives, but I don't see how retrieve the event as #Input of the component, unless to go on parent/child pattern. All examples I've founded has a relation between component.
EDIT : Example with BehaviorSubject not working (all connected service to API works well, only observable is fired at start but not when select has value changed)
Start by creating a BehaviorSubject in the service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject("default message");
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
I create a sample to send message between component by using BehaviorSubject
#Injectable()
export class MyService {
private messageSource = new BehaviorSubject<string>('service');
currentMessage = this.messageSource.asObservable();
constructor() {
}
changeMessage(message: string) {
this.messageSource.next(message)
}
}
You can refer at https://stackblitz.com/edit/behavior-subject-2019

Custom ViewResolver and dependency injection

I'm attempting to create a custom ViewResolver class (extending angular's built-in class) to augment my systems' style metadata with a custom service (I'm loading my styles from external systems). However I'm running into a problem with the dependency injection system and ViewResolver.
I have my system setup something like the following:
Boot.ts:
bootstrap(App, [
MyStyleService, // my custom service
SomeOtherService, // another custom service used by MyStyleService
{
provide: ViewResolver,
useClass: MyViewResolver // my custom ViewResolver
}
])
MyViewResolver.ts:
#Injectable()
export class MyViewResolover extends ViewResolver {
constructor(
private _reflector: ReflectorReader,
// I want to reference 'StyleService' from the providers array in boot.ts
private _styleService: StyleService
) {
super(_reflector);
}
public resolve(comopnent: Type) {
let meta: ViewMetadata = super.resolve(component);
let styles = this._styleService.someMethod(meta);
}
}
However inside MyViewResolver, this._styleService has NOT been injected and is currently undefined. It should be noted that MyStyleService also depends on another injected service SomeOtherService, so I need to make sure that that provider is also defined and available for the injector.
I want all of these services to be "provided" by the bootstrap, so that in the future I can provide alternate versions of any of my services on a per-system basis.
For reference this is angular's core ViewResolver:
view_resolver.ts (Angular2 core):
import {Injectable, ViewMetadata, ComponentMetadata,} from '#angular/core';
import {ReflectorReader, reflector} from '../core_private';
import {Type, stringify, isBlank, isPresent} from '../src/facade/lang';
import {BaseException} from '../src/facade/exceptions';
import {Map} from '../src/facade/collection';
#Injectable()
export class ViewResolver {
constructor(private _reflector: ReflectorReader = reflector) {}
resolve(component: Type): ViewMetadata {
... stuff here ...
}
}
You could try to configure your class with useFactory:
bootstrap(App, [
MyStyleService,
SomeOtherService,
{
provide: ViewResolver,
useFactory: (_reflector: ReflectorReader, styleService: StyleService) => {
return MyViewResolver(_reflector, _styleService);
},
deps: [ ReflectorReader, StyleService ]
}
]);

Angular2 - Use Dependency Injection outside Components

In my app I use DI to pass information about the UserLogged around the different Components that need such info.
This means that I have a main.ts class like this
import {AppComponent} from './app.component';
import {UserLogged} from './userLogged'
bootstrap(AppComponent, [UserLogged]);
and the components that need to use the instance of UserLogged have a constructor like this
constructor(private _user: UserLogged)
Now I would like to use the same instance of UserLogged also in simple TypeScript classes (which are not #Component). Is this possible? In other words, can I get hold of the same instance of UserLogged injected by DI also if I am outside a #Component?
This constructor also works for services (other classes created by DI)
bootstrap(AppComponent, [OtherClass, UserLoggged]);
#Injectable()
export class UserLogged {
log(text) {
console.log(text);
}
}
#Injectable()
export class OtherClass {
constructor(private _user: UserLogged) {}
}
class SomeComponent {
constructor(private otherClass:OtherClass) {
this.otherClass._user.log('xxx');
}
}
If you create these classes using new SomeClass() then you can inject it like
class SomeComponent {
constructor(private _injector:Injector) {
let userLog = this._injector.get(UserLogged);
new SomeClass(userLog);
}
}
In the file where you bootstrap angular:
import { AppComponent } from './app.component';
import { UserLogged } from './userLogged';
declare global {
var injector: Injector;
}
bootstrap(AppComponent, [UserLogged]).then((appRef) => {
injector = appRef.injector;
});
In your other file:
import { UserLogged } from '../path/to/userLogged';
class TestClass {
private userLogged: UserLogged;
constructor() {
this.userLogged = injector.get(UserLogged);
}
}
To be able to use dependency injection in classes you need to have a decorator, the #Injectable one.
#Injectable()
export class SomeClass {
constructor(private dep:SomeDependency) {
}
}
The name Injectable isn't really self-explanatory. It's to make possible the injection within the class (and not into another class).
The provider for the SomeDependency class need to be visible from the component that initiates the call.
See this question for more details about dependency injection and hierarchical injectors:
What's the best way to inject one service into another in angular 2 (Beta)?
Im taking a wild guess here, but do you mean to say you want to Inject the class with having to use providers : [UserLogged] ?
If that is the case, this will work
providers: [ provide(UserLogged, {useClass: UserLogged} ]
add the above to your bootstrap and you are good to go when you 'do not want to use #Component'
sample.ts
export class Sample{
constructor(private ulog : UserLogged){}
}
In your case the bootstrap would be :
import {provide} from 'angular2/core';
import {HTTP_PROVIDERS} from 'angular2/http';
bootstrap(AppComponent,[HTTP_PROVIDERS,provide(UserLogged, { useClass : UserLogged})]);
Ive added the HTTP_PROVIDERS to demonstrate how to add multiple providers.
Cheers!

DI - No provider found for EventBus

Using Angular Dart, I define an event bus like this:
class MyModule extends Module {
MyModule() {
bind(EventBus, toImplementation: EventBus);
...
}
}
When I want to inject this event bus into a component by simply doing:
class MyComponent {
final EventBus _eventBus;
MyComponent(this._eventBus) {}
}
I am getting the error:
No provider found for EventBus!
I have no idea how to debug this...
The event bus is an external library, which looks like:
library event_bus;
import 'dart:async';
#MirrorsUsed(symbols: '*') // Do not keep any names.
import 'dart:mirrors';
class EventBus {
StreamController _streamController;
EventBus({bool sync: false}) {
_streamController = new StreamController.broadcast(sync: sync);
}
...
}
Any help welcome... thanks!
I'm a bit late, but in this case - when you can't put easily an #Injectable() annotation on the class - the simplest solution is to give the injector a value :
bind(EventBus, toValue: new EventBus());

Resources