Можно ли вводить интерфейс с помощью angular2?
Интересно, есть ли способ внедрить интерфейсы в Angular2? (см. ниже)
Я думаю, что это связано с отсутствующим декоратором @Injectable() на интерфейсе, но, похоже, это не разрешено.
С уважением.
Когда CoursesServiceInterface реализуется как интерфейс, компилятор TypeScript жалуется: "CoursesServiceInterface не может найти имя":
import {CoursesServiceInterface} from './CoursesService.interface';
import {CoursesService} from './CoursesService.service';
import {CoursesServiceMock} from './CoursesServiceMock.service';
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
GlobalService,
provide(CoursesServiceInterface, { useClass: CoursesServiceMock })
]);
но с CoursesServiceInterface в качестве интерфейса:
import {Injectable} from 'angular2/core';
import {Course} from './Course.class';
//@Injectable()
export interface CoursesServiceInterface {
getAllCourses(): Promise<Course[]>;//{ return null; };
getCourse(id: number): Promise<Course>;// { return null; };
remove(id: number): Promise<{}>;// { return null; };
}
Когда сервис является классом, компилятор TypeScript больше не жалуется:
import {Injectable} from 'angular2/core';
import {Course} from './Course.class';
@Injectable()
export class CoursesServiceInterface {
getAllCourses() : Promise<Course[]> { return null; };
getCourse(id: number) :Promise<Course> { return null; };
remove (id: number) : Promise<{}> { return null; };
}
Ответы
Ответ 1
Нет, интерфейсы не поддерживаются для DI. С интерфейсами TypeScript больше не доступны во время выполнения, только статически и поэтому не могут использоваться в качестве токенов DI.
В качестве альтернативы вы можете использовать строки как клавиши или InjectionToken
provide('CoursesServiceInterface', {useClass: CoursesServiceMock}) // old
Забастовкa >
providers: [{provide: 'CoursesServiceInterface', useClass: CoursesServiceMock}]
и введите его как
constructor(@Inject('CoursesServiceInterface') private coursesService:CoursesServiceInterface) {}
См. также https://angular.io/api/core/InjectionToken
Ответ 2
Причина, по которой вы не можете использовать интерфейсы, заключается в том, что интерфейс является артефактом времени разработки TypeScript. JavaScript не имеет интерфейсов. Интерфейс TypeScript исчезает из сгенерированного JavaScript. Информация о интерфейсе отсутствует для Angular для поиска во время выполнения.
Решение 1:
Самое простое решение - просто определить абстрактный класс, реализующий интерфейс. Часто вам нужен абстрактный класс.
Интерфейс:
import {Role} from "../../model/role";
export interface ProcessEngine {
login(username: string, password: string):string;
getRoles(): Role[];
}
Абстрактный класс:
import {ProcessEngine} from "./process-engine.interface";
export abstract class ProcessEngineService implements ProcessEngine {
abstract login(username: string, password: string): string;
abstract getRoles(): Role[];
}
Класс бетона:
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[] {...}
}
Теперь вы можете определить своего провайдера, как обычно:
@NgModule({
...
providers: [
...,
{provide: ProcessEngineService, useClass: WebRatioEngineService}
]
})
Решение 2:
В официальной документации Angular предлагается использовать InjectionToken, аналогично OpaqueToken. Вот пример:
Ваш интерфейс и класс:
export interface AppConfig {
apiEndpoint: string;
title: string;
}
export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
};
Определите свой токен:
import { InjectionToken } from '@angular/core';
export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
Зарегистрируйте поставщика зависимостей, используя объект InjectionToken, например, в вашем приложении .module.ts:
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
Чем вы можете вставить объект конфигурации в любой конструктор, который ему нужен, с помощью декоратора @Inject:
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
Ответ 3
Используйте OpaqueToken, интерфейсы не поддерживаются DI, потому что у Javascript нет интерфейсов. Один из способов сделать это в Angular 2 - с помощью 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;
}
Надеюсь, это поможет.
Ответ 4
Мой опыт (исходящий из разработки java-backend) в frontend dev выглядит следующим образом.
Если мы говорим об "интерфейсе", у меня есть сильное ожидание того, что основной принцип использования интерфейса обеспечивается языками, которые "предлагают" интерфейс.
Который: "код против интерфейса не против реализации".
Это не гарантируется typescript/angular2. (чем они не должны использовать интерфейс слова, возможно).
Что было в моем случае (предупреждение: я изучаю angular2, поэтому мой обходной способ может показаться уродливым для продвинутых пользователей):
Компонент A1 имеет дочерний компонент B.
Компонент B должен знать родителя и вызвать метод для родителя.
Таким образом, компонент B получает родителя через DependencyInjection в нем конструктор.
constructor( private a: A1Component) {}
Все в порядке.
Чем все усложняется.
Другой компонент A2 может быть родителем comp. B.
В идеале я должен ввести в B интерфейс (а не реализацию), который реализуется как A1, так и A2 (это было бы естественно в java-мире).
Чем B будет работать с этим интерфейсом. Если необходимо, то приведение типа к A2, например, сделает B осведомленным, если экземпляр, который у него есть, действительно A2 или нет.
Я говорю о простых компонентах/классах, а не об услугах (я вижу, что большинство решений относится к службам).
Я попытался использовать @Host(), @Injectable(), OpaqueToken, Providers, но всегда была ошибка. Когда, в конце концов, это сработало: на самом деле объект, введенный в Компонент B, был пустым объектом, а не родителем - возможно, я ошибочно использовался для провайдеров, а новый пустой объект был создан вместо того, чтобы вводить родительский объект.
Что я сделал в конце.. Я не использовал интерфейс.
Я создал простой базовый класс для A1 и A2 - позвонил ему ABase.
Компонент B сохранит ссылку на этот базовый класс. Ссылка будет установлена в конструкторе следующим образом:
//BComponent:
parent: ABase;
constructor(@Optional parentA1: A1Component, @Optional parentA2: A2Component) {
if( parentA1 )
this.parent = parentA1;
else
this.parent = parentA2
}
Да, это странное обходное решение, а не приятное (исходящее из Java-мышления, я согласен) - но у меня просто закончилось время и разочаровался в "интерфейсе".