Repository: angular
Issue: Add typed key for `FormRecord`
Labels: area: forms

### 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<FormControl<boolean>>({
  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<boolean>` 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<FormControl<boolean>>({
  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<Partial<Record<TKey, TControl>>> {}

export interface FormRecord<TControl, TKey> {
  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<Record<TKey, ɵValue<TControl>>>, options?: { onlySelf?: boolean, emitEvent?: boolean }): void;
  patchValue(value: Partial<Record<TKey, ɵValue<TControl>>>, options?: { onlySelf?: boolean, emitEvent?: boolean }): void;
  reset(value?: Partial<Record<TKey, ɵValue<TControl>>>, options?: { onlySelf?: boolean, emitEvent?: boolean }): void;
  getRawValue(): Partial<Record<TKey, ɵValue<TControl>>>;
}
```

So that we can use it like this:

```ts
enum ControlKey {
  Key1 = 'key1',
  Key2 = 'key2',
  Key3 = 'key3',
}

const form = new FormRecord<FormControl<boolean>, 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<Partial<Record<ControlKey, FormControl<boolean>>>>({
  [ControlKey.Key1]: new FormControl(false, { nonNullable: true }),
  [ControlKey.Key2]: new FormControl(false, { nonNullable: true }),
  [ControlKey.Key3]: new FormControl(false, { nonNullable: true }),
});
```