#html #angular #typescript #inheritance
Вопрос:
У меня есть проблема с наследованием машинописного текста и ссылкой на свойства в угловом HTML-шаблоне, и я долго не мог ее решить. (если это дублирование вопроса, извините за это, я не смог найти такой вопрос).
Итак, у меня есть родительский интерфейс, пусть он вызывает DummyParent с некоторыми свойствами.
export interface DummyParent {
dummy1?: string;
dummy2?: number;
}
И у меня есть двое детей от этого интерфейса.
export interface DummyChild1 extends DummyParent {
foo1?: string;
bar1: number;
}
export interface DummyChild2 extends DummyParent {
foo2?: string;
bar2: number;
}
И у меня есть угловой компонент, который получает входные данные, и его тип может быть копией всех этих интерфейсов.
@Input() dummy!: DummyParent | DummyChild1 | DummyChild2;
Но когда у меня есть ссылка в HTML-шаблоне для свойства DummyChild, я получил ошибку.
Например:
<ng-container *ngIf="dummy.foo1"></ng-container>
Error: Property 'foo1' does not exist on type 'DummyParent'.
Основываясь на этой ошибке, кажется, что свойство dummy-это просто объект DummyParent, и я не могу ссылаться на него как на объект DummyChild1 или DummyChild2. Мой вопрос в том, как я должен создать экземпляр @Input() dummy, чтобы он был типом DummyParent ИЛИ DummyChild1 ИЛИ DummyChild2.
Я знаю, что могу решить проблему с использованием объекта типа»любой» или приведения объекта в HTML-шаблон, как это
<ng-контейнер *ngIf= «$any(фиктивный).foo1»>
но я стараюсь избегать использования этих решений.
Большое спасибо за любые ответы.
Ответ №1:
Вот как работают профсоюзы TS:
export interface DummyParent {
dummy1?: string;
dummy2?: number;
}
export interface DummyChild1 extends DummyParent {
foo1?: string;
bar1: number;
}
export interface DummyChild2 extends DummyParent {
foo2?: string;
bar2: number;
}
type Union = DummyParent | DummyChild1 | DummyChild2
declare var union: Union;
// only dumm1 and dummy2 are available
union.dummy1
union.dummy2
union
позволяет использовать общие свойства всех фиктивных типов. Поскольку каждый тип расширяется DummyParent
, вам разрешается использовать только свойства из DummyParent
.
Для того, чтобы сделать это безопасным, вы должны использовать пользовательские типы защиты:
export interface DummyParent {
dummy1?: string;
dummy2?: number;
}
export interface DummyChild1 extends DummyParent {
foo1?: string;
bar1: number;
}
export interface DummyChild2 extends DummyParent {
foo2?: string;
bar2: number;
}
type Union = DummyParent | DummyChild1 | DummyChild2
declare var union: Union;
// only dumm1 and dummy2 are available
union.dummy1
union.dummy2
const isOne = (union: Union): union is DummyChild1 => 'bar1' in union
const isTwo = (union: Union): union is DummyChild2 => 'bar2' in union
if(isOne(union)){
union.bar1 // ok
}
if(isTwo(union)){
union.bar2 // ok
}
Тогда, я полагаю, вы можете сделать что-то подобное:
<ng-container *ngIf="isTwo(union)">
// do smth with union.bar2
</ng-container>
P.S. Я не на 100% уверен, что in
оператор-лучшее решение для такого типа охранника.
Вы также можете рассмотреть это:
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj amp; Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
Ответ №2:
Мой вопрос в том, как я должен создать экземпляр @Input() dummy, чтобы он был типом DummyParent ИЛИ DummyChild1 ИЛИ DummyChild2.
Именно этим вы сейчас и занимаетесь. Если вы говорите, что что-то относится к типу A, типу B или типу C, вы можете получить доступ только к свойствам, существующим для ВСЕХ ТРЕХ типов. В противном случае, какой смысл в явном вводе, если вы все еще можете получить доступ к несуществующим свойствам?
И поскольку в ваших случаях B и C наследуются после A, это будут только свойства типа A. Если вы хотите получить доступ к свойствам типа A, вы должны сузить тип до A (например, с помощью type guard).
Вы можете прочитать больше об этом в официальных документах TypeScript здесь.