3

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:

  • would use findById(accountId: number) to retrieve the account
  • check the AccountType of the account, in this case account.type === AccountType.Foo
  • if the AccountType check is correct, then would access account.foo.id and use that fooId to perform the desired update

The 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?

2 Answers 2

5

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
}
Sign up to request clarification or add additional context in comments.

2 Comments

this is not a polymorphism at all
-3

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.