Is it possible to inject interface with angular2?

后端 未结 5 1513
不思量自难忘°
不思量自难忘° 2020-12-02 14:06

i wonder if there is a proper way to inject interfaces in Angular2? (cf. below)

I think this is related with the missing @Injectable() decorator on the interface, bu

相关标签:
5条回答
  • 2020-12-02 14:30

    No, interfaces are not supported for DI. With TypeScript interfaces are not available at runtime anymore, only statically and therefore can't be used as DI tokens.

    Alternatively you can use strings as keys or InjectionToken

    provide('CoursesServiceInterface', {useClass: CoursesServiceMock}) // old
    

    providers: [{provide: 'CoursesServiceInterface', useClass: CoursesServiceMock}]
    

    and inject it like

    constructor(@Inject('CoursesServiceInterface') private coursesService:CoursesServiceInterface) {}
    

    See also https://angular.io/api/core/InjectionToken

    0 讨论(0)
  • 2020-12-02 14:39

    Use OpaqueToken, interfaces are not supported by DI, beacause Javascript itself haven't interfaces. One way to do this in Angular 2 is by using OpaqueToken. https://angular.io/docs/ts/latest/guide/dependency-injection.html

    import { OpaqueToken } from '@angular/core';
    
    export let APP_CONFIG = new OpaqueToken('app.config');
    
    providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
    
    constructor(@Inject(APP_CONFIG) config: AppConfig) {
      this.title = config.title;
    }
    

    I hope this can help.

    0 讨论(0)
  • 2020-12-02 14:40

    The reason you can't use interfaces is because an interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces. The TypeScript interface disappears from the generated JavaScript. There is no interface type information left for Angular to find at runtime.


    Solution 1:

    The easiest solution is just to define an abstract class which implements the interface. Often, you need a abstract class anyway.

    Interface:

    import {Role} from "../../model/role";
    
    export interface ProcessEngine {
    
         login(username: string, password: string):string;
    
         getRoles(): Role[];
    }
    

    Abstract Class:

    import {ProcessEngine} from "./process-engine.interface";
    
    export abstract class ProcessEngineService implements ProcessEngine {
    
        abstract login(username: string, password: string): string;
    
        abstract getRoles(): Role[];
    
    }
    

    Concrete Class:

    import { Injectable } from '@angular/core';
    import {ProcessEngineService} from "./process-engine.service";
    
    @Injectable()
    export class WebRatioEngineService extends ProcessEngineService {
    
        login(username: string, password: string) : string {...}
    
        getRoles(): Role[] {...}
    
    }
    

    Now you can define your provider like usual:

    @NgModule({
          ...
          providers: [
            ...,
            {provide: ProcessEngineService, useClass: WebRatioEngineService}
          ]
    })
    

    Solution 2:

    The official documentation of Angular suggest to use the InjectionToken, similar to OpaqueToken. Here is the Example:

    Your interface and class:

    export interface AppConfig {
       apiEndpoint: string;
       title: string;
    }
    
    export const HERO_DI_CONFIG: AppConfig = {
      apiEndpoint: 'api.heroes.com',
      title: 'Dependency Injection'
    };
    

    Define your Token:

    import { InjectionToken } from '@angular/core';
    
    export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
    

    Register the dependency provider using the InjectionToken object, e.g in your app.module.ts:

    providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
    

    Than you can inject the configuration object into any constructor that needs it, with the help of an @Inject decorator:

    constructor(@Inject(APP_CONFIG) config: AppConfig) {
         this.title = config.title;
    }
    
    0 讨论(0)
  • 2020-12-02 14:45

    My experience (coming from java backend development) into frontend dev is the following.

    If we speak about 'interface' I have the strong expectation that an main principle of using interface is assured by languages who 'offer' interface. Which is: 'code against interface not against implementation'.

    This seems to not be assured by typescript/angular2. (than they shouldn't use word interface yet, maybe).

    What was my case (warning: I'm learning angular2 so my workaround could seem ugly to advanced users):
    Component A1 has a child component B.
    Component B should know the parent and call a method on parent.
    So component B receives the parent via DependencyInjection in it's constructor.

       constructor( private a: A1Component) {}
    

    Everything is fine.
    Than things get complicated.
    Another component A2 can be the parent of comp. B.
    Ideally I should inject in B an interface (not implementation) which is implemented by both A1 and A2 (this would be naturally in java world).
    Than B would work with this interface. If needed, A typecast to A2 for example would make B aware if the instance he has is really A2 or not.

    I speak about plain components/classes, not services (I see that most solutions refers to services).
    I tried to use @Host(), @Injectable(), OpaqueToken, Providers but always there was an error. When in the end it seemed to work: in reality the object injected in Component B was an empty object, not the parent - maybe I wrongly used to providers and a new empty object was created instead of injecting the parent object.

    What I did in the end: I didn't use interface.
    I created a plain base class for A1 and A2 - let's call it ABase.
    Component B would keep a reference to this base class. Reference would be set in constructor as this:

    //BComponent:
     parent: ABase;    
    
     constructor(@Optional parentA1: A1Component, @Optional parentA2: A2Component) {
        if( parentA1 )
           this.parent = parentA1;
        else
           this.parent = parentA2
     }
    

    Yes, it's a strange workaround, not nice (coming from java world thinking, I agree) - but I just run out of time and was disappointed about the 'interface' thing.

    Updated

    I reconsider the previous answer (it's bad design, ugly ... was at the my beginnings with angular)

    Now Angular's documentation have clear description on this exact issue: finding the parent of a component.

    Can not use interface - as interface can not be injected.

    "Looking for components that implement an interface would be better. That's not possible because TypeScript interfaces disappear from the transpiled JavaScript, which doesn't support interfaces. There's no artifact to look for."

    Can not use base class of possible parents neither ... (this is the root cause of my with previous desperate, bad- answer)

    What works? Technique: find a parent by its class-interface.

    Mainly:

    Child B see a general parent Parent (can be A1Component or A2Component)

    export class BComponent {
      name = 'B';
      constructor( @Optional() public parent: Parent ) { }
    }
    

    And the each possible parent component provides a Parent (at component level !!!) using class-interface:

    providers: [{ provide: Parent, useExisting: forwardRef(() => A1Component) }],
    
    0 讨论(0)
  • 2020-12-02 14:50

    Alternate solution for angular 9

    @Injectable()
    export class TodoListPublicService implements TodoListService {
      getTodos(): Todo[] {
        const todos: Todo[] = [
          {
            title: 'get groceries',
            description: 'eggs, milk, etc.',
            done: false
          }
        ];
    
        return todos;
      }
    }
    

    create an abstract class

    export interface Todo {
      title: string;
      description: string;
      done: boolean;
    }
    
    @Injectable()
    export abstract class TodoListService {
      abstract getTodos(): Todo[];
    }
    

    Use in the component

    providers: [
        { provide: TodoListService, useClass: TodoListPublicService }
      ]
    export class TodoListComponent implements OnInit {
      todos: Todo[];
    
      constructor(private todoListService: TodoListService) { }
    
      ngOnInit() {
      }
    
    0 讨论(0)
提交回复
热议问题