Reputation: 6097
I have a base Account interface:
interface Account {
id: number;
email: string;
password: string;
type: AccountType;
}
where AccountType:
enum AccountType {
Foo = 'foo',
Bar = 'bar'
}
and two account subtypes (FooAccount and BarAccount) that extend the Account interface:
interface FooAccount extends Account {
foo: Foo;
}
interface BarAccount extends Account {
bar: Bar;
}
Account is an aggregate that holds basic account info and, depending on the type, owns a Foo or a Bar object.
Actions on these objects, can only be performed by their owners (the account).
I have defined an AccountRepository:
export interface AccountRepository {
findById(accountId: number): Account;
}
where the findById(accountId: number)
returns an Account, but this account could be any FooAccount or BarAccount.
I want to use this findById
function before performing any action on a Foo
or Bar
. For example, let's say I want to update an account's Foo
:
findById(accountId: number)
to retrieve the accountaccount.type === AccountType.Foo
account.foo.id
and use that fooId
to perform the desired updateThe problem here is, that this last point is failing: as findById(accountId: number): Account
returns an Account and there is no foo: Foo
property defined in its interface.
I have also tried the following, but cannot be done either:
const fooAccount: FooAccount = findById(accountId);
because the function returns an Account.
I am trying to figure out how can this be achieved, what am I missing out? Is there anything I could be doing wrong?
Upvotes: 3
Views: 4321
Reputation: 6097
Solved this using Type Assertion, by just adding as FooAccount
like this:
const fooAccount: FooAccount = findById(accountId) as FooAccount;
There's no need to modify the existing design to achieve it.
Basically, the assertion from type S to T succeeds if either S is a subtype of T or T is a subtype of S.
More info: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html
Upvotes: -3
Reputation: 249606
The best solution is probably to use a discriminated union.
export class Bar { public idBar: number; }
class Foo { public idFoo: number; }
interface AccountCommon {
id: number;
email: string;
password: string;
}
enum AccountType {
Foo = 'foo',
Bar = 'bar'
}
interface FooAccount extends AccountCommon {
type: AccountType.Foo; // type can only be Foo
foo: Foo;
}
interface BarAccount extends AccountCommon {
type: AccountType.Bar; // type can only be Bar
bar: Bar;
}
// The discriminated union
type Account = BarAccount | FooAccount //type is common so type can be either Foo or Bar
export interface AccountRepository {
findById(accountId: number): Account;
}
let r: AccountRepository;
let a = r.findById(0);
if (a.type === AccountType.Bar) { // type guard
a.bar.idBar // a is now BarAccount
} else {
a.foo.idFoo // a is now FooAccount
}
Upvotes: 5