Можно ли ввести интерфейс с 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; };
}
Когда service является классом, компилятор 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; };
}
7 ответов
Нет, интерфейсы не поддерживаются для DI. С интерфейсами TypeScript больше не доступны во время выполнения, только статически и, следовательно, не могут использоваться в качестве токенов DI.
В качестве альтернативы вы можете использовать строки в качестве ключей или InjectionToken
provide('CoursesServiceInterface', {useClass: CoursesServiceMock}) // old
providers: [{provide: 'CoursesServiceInterface', useClass: CoursesServiceMock}]
и впрыскивать это как
constructor(@Inject('CoursesServiceInterface') private coursesService:CoursesServiceInterface) {}
Смотрите также https://angular.io/api/core/InjectionToken
Причина, по которой вы не можете использовать интерфейсы, заключается в том, что интерфейс является артефактом времени разработки 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, например, в вашем app.module.ts:
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
Затем вы можете внедрить объект конфигурации в любой конструктор, который в этом нуждается, с помощью декоратора @Inject:
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
Альтернативное решение для угловой 9
@Injectable()
export class TodoListPublicService implements TodoListService {
getTodos(): Todo[] {
const todos: Todo[] = [
{
title: 'get groceries',
description: 'eggs, milk, etc.',
done: false
}
];
return todos;
}
}
создать абстрактный класс
export interface Todo {
title: string;
description: string;
done: boolean;
}
@Injectable()
export abstract class TodoListService {
abstract getTodos(): Todo[];
}
Использование в компоненте
providers: [
{ provide: TodoListService, useClass: TodoListPublicService }
]
export class TodoListComponent implements OnInit {
todos: Todo[];
constructor(private todoListService: TodoListService) { }
ngOnInit() {
}
Используйте 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;
}
Я надеюсь, что это может помочь.
Мой опыт (начиная с разработки java-бэкенда) до разработчика приложений следующий.
Если мы говорим об "интерфейсе", я ожидаю, что основной принцип использования интерфейса обеспечивается языками, которые "предлагают" интерфейс. Что означает: "код против интерфейса, а не против реализации".
Кажется, это не гарантировано машинописным / угловым2. (чем они не должны использовать интерфейс слова еще, возможно).
Каков был мой случай (предупреждение: я изучаю angular2, так что мой обходной путь может показаться уродливым для опытных пользователей):
Компонент A1 имеет дочерний компонент B.
Компонент B должен знать родителя и вызывать метод для родителя.
Таким образом, компонент B получает родительский объект через DependencyInjection в своем конструкторе.
constructor( private a: A1Component) {}
Все отлично.
Чем все усложняется.
Другой компонент A2 может быть родительским для comp. B.
В идеале я должен внедрить в B интерфейс (не реализацию), который реализуется как A1, так и A2 (это естественно в мире Java).
Чем B будет работать с этим интерфейсом. Если необходимо, то тип A, например, приведенный к 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, я согласен) - но у меня просто не хватило времени и я был разочарован тем, что связано с интерфейсом.
обновленный
Я пересматриваю предыдущий ответ (это плохой дизайн, некрасиво... было в моих началах с угловым)
Теперь в документации Angular есть четкое описание этой точной проблемы: поиск родителя компонента.
Нельзя использовать интерфейс - интерфейс не может быть введен.
"Поиск компонентов, реализующих интерфейс, был бы лучше. Это невозможно, потому что интерфейсы TypeScript исчезают из перенесенного JavaScript, который не поддерживает интерфейсы. Нет артефактов для поиска".
Не может использовать базовый класс возможных родителей ни... (это коренная причина моего предыдущего отчаянного, плохого ответа)
Что работает? Техника: найти родителя по его классу-интерфейсу.
В основном:
Ребенок B видит общего родителя Parent
(может быть A1Component или A2Component)
export class BComponent {
name = 'B';
constructor( @Optional() public parent: Parent ) { }
}
И каждый возможный родительский компонент обеспечивает Parent
(на уровне компонента!!!) используя class-interface:
providers: [{ provide: Parent, useExisting: forwardRef(() => A1Component) }],
Я оставлю здесь несколько центов, так как сам столкнулся с подобной проблемой (хотя v10). Вместо предоставления абстрактного класса службе в качестве зависимости я использовал интерфейс. Мне удалось решить эту проблему, внедрив службу зависимостей с помощью
@Inject()
class DependencyService implements IDependency {}
class SomeDataService {
constructor(@Inject('IDependency') private readonly service: IDependency) {}
}
и в компоненте, который полагался на этот сервис
@Component({
providers: [
SomeDataService,
{
provide: 'IDependency',
useClass: DependencyService,
}
]
})
export class ListComponent {
constructor(private readonly someDataService: SomeDataService) {}
}
Мой «трюк» состоит в том, чтобы объявить интерфейс в компонентах , но внедрить класс. Это гарантирует, что typescript позволяет компоненту использовать только интерфейс.
/**
* The official Interface to my Store
*/
interface Store {
readonly hello: string;
readonly theAnswer: number;
doSomething(): void;
}
Вот служба
import { Component, Injectable } from "@angular/core";
/**
* The implementation with potentially public members nobody should see
* but may be needed to implement the service...
*/
@Injectable({ providedIn: "root" })
export class StoreService implements Store {
readonly hello = "world";
readonly theAnswer = 42;
doSomething() {}
otherStuff() {}
}
Компонент, использующий интерфейс Сервиса
@Component({
selector: "my-component",
template: `<span>{{ store.hello }}</span>`,
})
export class MyComponent {
/**
* This is what we use
*/
readonly store: Store;
/**
* Here we inject the entire store, but we will only use the interface
* @param store
*/
constructor(store: StoreService) {
this.store = store;
}
}
Дополнительным преимуществом является то, что в одном магазине можно реализовать несколько интерфейсов (например, используяPick
) и компоненты внутри используют то, что ему нужно.
@Component({
selector: "my-other-component",
template: `<span>{{ store.theAnswer }}</span>`,
})
export class MyOtherComponent {
/**
* Here we use only a subset of the store
*/
readonly store: Pick<Store, "theAnswer">;
constructor(store: StoreService) {
this.store = store;
}
}