Skip to content
Merged
3 changes: 2 additions & 1 deletion docs/architecture/adr/0025-ts-deprecate-enums.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
adr: "0025"
status: Accepted
date: 2025-06-24
date: 2025-05-30
accepted: 2025-06-24
tags: [clients, typescript]
---

Expand Down
53 changes: 53 additions & 0 deletions docs/contributing/code-style/angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,56 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"

export class DifferentPackageService {}
```

## Enum-likes ([ADR-0025](../../architecture/adr/0025-ts-deprecate-enums.md))

For general guidance on enum-likes, consult [Avoid TypeScript Enums](./enums.md).

### String-backed Enum-likes

String-typed enum likes can be used as inputs of a component directly. Simply expose the enum-like
property from your component:

```ts
// given:
const EnumLike = { Some = "some", Value: "value" };
type EnumLike = EnumLike[keyof typeof EnumLike];

// add the input:
@Component({ ... })
class YourComponent {
@Input() input: EnumLike = EnumLike.Some;

// ...
}
```

Composers can use the enum's string values directly:

```html
<my-component input="value" />
```

### Numeric Enum-likes

Using numeric enum-likes in components should be avoided. If it is necessary, follow the same
pattern as a string-backed enum.

Composers that need hard-coded enum-likes in their template should expose the data from their
component:

```ts
import { EnumLike } from "...";

// add the input to your component:
@Component({ ... })
class TheirComponent {
protected readonly EnumLike = EnumLike;
}
```

And then bind the input in the template:

```ts
<my-component [input]='EnumLike.Value' />
```
50 changes: 34 additions & 16 deletions docs/contributing/code-style/enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use [constant objects][constant-object-pattern] instead of introducing a new enu
- Avoid asserting the type of an enum-like. Use explicit types instead.
- Create utilities to convert and identify enums modelled as primitives.

### Example
### Numeric enum-likes

Given the following enum:

Expand Down Expand Up @@ -58,6 +58,29 @@ type CipherContent =
| { type: typeof CipherType.SecureNote, note: EncString, ... }
```

:::warning

Unlike an enum, TypeScript lifts the type of the members of `const CipherType` to `number`. Code
like the following requires you explicitly type your variables:

```ts
// โœ… Do: strongly type enum-likes
let value: CipherType = CipherType.Login;
const array: CipherType[] = [CipherType.Login];
const subject = new Subject<CipherType>();

// โŒ Do not: use type inference
let value = CipherType.Login; // infers `1`
const array = [CipherType.Login]; // infers `number[]`

// โŒ Do not: use type assertions
let value = CipherType.Login as CipherType; // this operation is unsafe
```

:::

### String enum-likes

The above pattern also works with string-typed enum members:

```ts
Expand All @@ -73,31 +96,26 @@ export const CredentialType = Object.freeze({
export type CredentialType = CredentialType[keyof typeof CredentialType];
```

:::warning
:::note[Enum-likes are structural types!]

Unlike an enum, TypeScript lifts the type of the members of `const CipherType` to `number`. Code
like the following requires you explicitly type your variables:
Unlike string-typed enums, enum-likes do not reify a type for each member. This means that you can
use their string value or their enum member interchangeably.

```ts
// โœ… Do: strongly type enum-likes
let value: CipherType = CipherType.Login;
const array: CipherType[] = [CipherType.Login];
const subject = new Subject<CipherType>();
let value: CredentialType = CredentialType.Username;

// โŒ Do not: use type inference
let value = CipherType.Login; // infers `1`
const array = [CipherType.Login]; // infers `number[]`

// โŒ Do not: use type assertions
let value = CipherType.Login as CipherType; // this operation is unsafe
// this is typesafe!
value = "email";
```

However, the string-typed values are not always identified as enum members. Thus, when the const
object is in scope, prefer it to the literal value.

:::

## Utilities

The following utilities can be used to maintain type safety after compilation. This code assumes
`const CipherType` is frozen.
The following utilities can be used to maintain type safety at runtime.

```ts
import { CipherType } from "./cipher-type";
Expand Down
6 changes: 6 additions & 0 deletions src/components/AdrTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export default function AdrTable({ frontMatter }): JSX.Element {
<th>Published:</th>
<td>{formatDate(frontMatter.date)}</td>
</tr>
{frontMatter.accepted && (
<tr>
<th>Accepted:</th>
<td>{formatDate(frontMatter.accepted)}</td>
</tr>
)}
</tbody>
</table>
);
Expand Down