angular / angular/angular

Add typed key for `FormRecord`

Open
#49,442 1 comment 4 reactions 0 assignees View on GitHub
area: forms
Dominant language
TypeScript
Stars
101k
Forks
27.5k
Avg merge
1d 19h
Merged PRs (30d)
288

Description

### Which @angular/* package(s) are relevant/related to the feature request?

forms

### Description

Currently, the `FormRecord` only support typing the control, but not the key:

```ts
new FormRecord>({
key1: new FormControl(false, { nonNullable: true }),
key2: new FormControl(false, { nonNullable: true }),
key3: new FormControl(false, { nonNullable: true }),
});
```

Here the controls can only be `FormControl` but the keys of the controls can be anything (as long as it is a string).

But in some cases we want to key to be restricted to a specific set of strings (or Enum).

```ts
enum ControlKey {
Key1 = 'key1',
Key2 = 'key2',
Key3 = 'key3',
}

new FormRecord>({
key1: new FormControl(false, { nonNullable: true }),
key2: new FormControl(false, { nonNullable: true }),
key4: new FormControl(false, { nonNullable: true }), // I want the "key4" to throw a typing error here
});
```

This would be useful for known list of options that we can active/deactivate through a form.

The topic has already been discussed here:
- https://github.com/angular/angular/pull/45607#issuecomment-1098292360
- https://github.com/angular/angular/discussions/44513#discussioncomment-1991963

But it looks like it has never been considered as a new feature.

### Proposed solution

We could update the typing of `FormRecord` with something like this:

```ts
export class FormRecord<
TControl extends AbstractControl = AbstractControl,
TKey extends string = string
> extends FormGroup>> {}

export interface FormRecord {
registerControl(name: TKey, control: TControl): TControl;
addControl(name: TKey, control: TControl, options?: { emitEvent?: boolean }): void;
removeControl(name: TKey, options?: { emitEvent?: boolean }): void;
setControl(name: TKey, control: TControl, options?: { emitEvent?: boolean }): void;
contains(controlName: TKey): boolean;
setValue(value: Partial>>, options?: { onlySelf?: boolean, emitEvent?: boolean }): void;
patchValue(value: Partial>>, options?: { onlySelf?: boolean, emitEvent?: boolean }): void;
reset(value?: Partial>>, options?: { onlySelf?: boolean, emitEvent?: boolean }): void;
getRawValue(): Partial>>;
}
```

So that we can use it like this:

```ts
enum ControlKey {
Key1 = 'key1',
Key2 = 'key2',
Key3 = 'key3',
}

const form = new FormRecord, ControlKey>({
[ControlKey.Key1]: new FormControl(false, { nonNullable: true }),
[ControlKey.Key2]: new FormControl(false, { nonNullable: true }),
[ControlKey.Key3]: new FormControl(false, { nonNullable: true }),
});
```

### Alternatives considered

At the moment I'm using a `FormGroup` to match my need:

```ts
enum ControlKey {
Key1 = 'key1',
Key2 = 'key2',
Key3 = 'key3',
}

new FormGroup>>>({
[ControlKey.Key1]: new FormControl(false, { nonNullable: true }),
[ControlKey.Key2]: new FormControl(false, { nonNullable: true }),
[ControlKey.Key3]: new FormControl(false, { nonNullable: true }),
});
```

Contributor guide

Open the contributing guide

Research direction

Start in the @angular/forms package by locating the FormRecord type and its control-management and value APIs. Review the linked pull request and discussion for prior constraints, then verify that typed keys are enforced across construction and named methods while existing string-key usage remains compatible.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, typescript
Domain
frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.