В моем шаблоне Angular есть следующие настройки:
<mat-tab-group>
<mat-tab *ngIf = "sees1">
<app-custom-table
(columnButtonClick) = "service1.columnClicked($event)">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees2">
<app-custom-table>
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees3">
<app-custom-table>
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees4">
<app-custom-table>
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees5">
<app-custom-table
(columnButtonClick) = "service5.columnClicked($event)">
</app-custom-table>
</mat-tab>
</mat-tab-group>
Я хочу, чтобы в каждый из app-custom-table
была внедрена отдельная реализация сервиса, чтобы иметь возможность отображать и обрабатывать разные данные.
На самом деле я пытаюсь изменить существующее решение, которое включало передачу службы в качестве аргумента @Input()
непосредственно каждому из компонентов. Вот так:
<mat-tab-group>
<mat-tab *ngIf = "sees1">
<app-custom-table [service] = "serviceImpl1"
(columnButtonClick) = "service1.columnClicked($event)">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees2">
<app-custom-table [service] = "serviceImpl2">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees3">
<app-custom-table [service] = "serviceImpl3">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees4">
<app-custom-table [service] = "serviceImpl4">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees5">
<app-custom-table [service] = "serviceImpl5"
(columnButtonClick) = "service5.columnClicked($event)">
</app-custom-table>
</mat-tab>
</mat-tab-group>
Я пытаюсь отойти от этого подхода, потому что он кажется плохим.
Я хочу что-то вроде этого:
@Component({
providers: [
{ provide: BaseService, useClass: ImplementationService1 },
{ provide: BaseService, useClass: ImplementationService2 },
{ provide: BaseService, useClass: ImplementationService3 },
{ provide: BaseService, useClass: ImplementationService4 },
{ provide: BaseService, useClass: ImplementationService5 }]
})
но Angular, очевидно, не знает, куда внедрить каждый из соответствующих сервисов.
Возможно, важно отметить, что все реализации extend
класса BaseService
представляют собой абстрактный класс с реализацией методов по умолчанию, которые в большинстве случаев не меняются, по крайней мере, для большинства реализаций.
Есть ли способ сообщить Angular, какой сервис следует внедрить, или мне следует придерживаться текущего подхода (внедрение сервиса в качестве аргумента Input()
)? Неужели нынешний подход настолько плох?
Обновлено:
CustomTable<T>
и BaseService<T>
являются общими классами, кроме того, BaseService<T>
— абстрактный класс без декоратора @Injectable
. Все ImplementationService
расширяют BaseService
конкретной моделью как T
.
🤔 А знаете ли вы, что...
Angular Universal позволяет рендерить веб-приложение на сервере для улучшения SEO и производительности.
Почему бы вам не добавить службу в providers
компонента app-custom-table
, чтобы служба создавалась для каждого экземпляра компонента? Вместо передачи сервисов через входы вы можете просто внедрить и использовать сервис непосредственно в дочернем компоненте, и это будет отдельный экземпляр сервиса для каждого компонента.
@Component({
selector: 'app-custom-table',
// this will create a new instance of the service per component instance
providers: [{ provide: BaseService, useClass: ImplementationService }],
})
export class CustomTableComponent {
constructor(private service: BaseService) {}
columnButtonClick(event: unknown) {
this.service.columnClicked(event);
}
}
См. руководство по внедрению зависимостей .
Если вы хотите вызывать службу не каждый раз, а по условию, вы можете добавить в компонент входные данные для управления этим.
В родительском сервисе вместо BaseService
используйте разные экземпляры.
@Component({
providers: [
ImplementationService1,
ImplementationService2,
ImplementationService3,
ImplementationService4,
ImplementationService5,
})
export class SomeComponent {
constructor(
private impSer1: ImplementationService1
private impSer2: ImplementationService2
private impSer3: ImplementationService3
private impSer4: ImplementationService4
private impSer5: ImplementationService5
) {}
Добавьте их в HTML.
<mat-tab-group>
<mat-tab *ngIf = "sees1">
<app-custom-table [service] = "impSer1"
(columnButtonClick) = "service1.columnClicked($event)">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees2">
<app-custom-table [service] = "impSer2">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees3">
<app-custom-table [service] = "impSer3">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees4">
<app-custom-table [service] = "impSer4">
</app-custom-table>
</mat-tab>
<mat-tab *ngIf = "sees5">
<app-custom-table [service] = "impSer5"
(columnButtonClick) = "service5.columnClicked($event)">
</app-custom-table>
</mat-tab>
</mat-tab-group>
Во-первых, должен быть более простой способ решить то, что вы хотите.
Но на данный момент это мое предложение.
Добавьте сервис в массив поставщиков компонента app-custom-table
.
@Component({
selector: 'app-custom-table',
...
providers: [{ provide: BaseService, useClass: ImplementationService },],
})
export class CustomTable {
Теперь все дочерние компоненты имеют уникальный экземпляр.
Затем возьмите дочерний элемент представления от родительского к дочерним компонентам, чтобы получить текущий экземпляр, для этого вы можете использовать геттер.
...
@ViewChild(CustomTable) customTable: CustomTable;
get baseServiceInstance() {
return this.customTable?.baseService;
}
Примечание: услуга будет доступна только ngAfterViewInit
, поэтому начните использовать эту логику оттуда, а не ngOnInit
или constructor
.
Я старался абстрагировать решение как можно более абстрактно, однако есть предел тому, насколько абстрактные вещи могут оказаться.
В какой-то момент, поскольку вы используете одну и ту же общую таблицу, но с разными сервисами, вам необходимо определить, какой сервис будет использоваться в какой момент.
Благодаря приведенному ниже решению, которое можно найти в этом рабочем stackblitz, конфигурация была перенесена в токен внедрения, а пользовательские компоненты, таким образом, используют DI для выполнения вашей работы.
Идея решения заключается в том, чтобы
Аннотация BaseService, согласно вашему требованию, не может быть внедрена.
export abstract class BaseService {
abstract getData():string;
}
Сервис 1 и Сервис 2, расширение/реализация абстрактного сервиса.
@Injectable({ providedIn: 'root' })
export class OneService extends BaseService {
override getData(): string {
return 'One Service';
}
}
@Injectable({providedIn:'root'})
export class TwoService extends BaseService {
override getData(): string {
return 'Two Service';
}
}
Токен инъекции + заводская функция
export let ServiceToken :InjectionToken<BaseService> = new InjectionToken('service-injection');
export let injectionFactory = (key:string) => {
switch(key) {
case "1":
return inject(OneService);
case "2":
return inject(TwoService);
default:
throw new Error('not-supported');
}
};
CustomTable, многоразовый, согласно вашему требованию
@Component({
standalone:true,
selector:'my-table',
template:`
<div>My Table {{key}}</div>
`,
})
export class TableComponent {
constructor(@Inject(ServiceToken) public serivce:BaseService){};
get key():string {
return this.serivce.getData();
}
}
Компоненты-оболочки для таблицы, которые внедряют вашу конкретную службу, которая будет использоваться многоразовой таблицей.
@Component({
standalone: true,
selector: 'one-table',
imports: [TableComponent],
providers: [{ provide: ServiceToken, useExisting: OneService }],
template: `
<my-table>
`,
})
export class OneTableComponent {}
@Component({
standalone: true,
selector: 'two-table',
imports: [TableComponent],
providers: [{ provide: ServiceToken, useExisting: TwoService }],,
template: `
<my-table>
`,
})
export class TwoTableComponent {}