Skip to content

Signal Forms: Cannot bind a field of type "string | null" to a <textarea>, but can to <input> #65839

@nickbelling

Description

@nickbelling

Which @angular/* package(s) are the source of the bug?

forms

Is this a regression?

No

Description

The Signal Forms documentation explicitly calls out that optional fields should be initialized as null.

For example:

export interface PersonModel {
  name: string;
  bio: string | null;
}

export const DEFAULT: PersonModel = {
  name: '';
  bio: null;
}


@Component({ ... })
export class PersonFormComponent {
  protected readonly personModel = signal<PersonModel>(DEFAULT);
  protected readonly personForm = form<PersonModel>(this.personModel, (path) => {
    required(path.name);
  });
}

The following template code is valid, as an input field can accept a field of type string | null:

<input [field]="personForm.name" />
<input [field]="personForm.bio" />

However, as <input> elements cannot be multiline, attempting to replace the <input> with a <textarea> will throw a compilation error:

<input [field]="personForm.name" />
<textarea [field]="personForm.bio"></textarea>

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

Attempting to bind to a `<textarea>` throws the following error:


Type 'string | null' is not assignable to type 'string'.
  Type 'null' is not assignable to type 'string'.


Binding the same field to an `<input>` element works without the same error.

Please provide the environment you discovered this bug in (run ng version)

Angular CLI       : 21.0.1
Angular           : 21.0.1
Node.js           : 22.16.0
Package Manager   : npm 10.9.2
Operating System  : linux arm64

┌────────────────────────────────────┬───────────────────┬───────────────────┐
│ Package                            │ Installed Version │ Requested Version │
├────────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build                     │ 21.0.1            │ ^21.0.1           │
│ @angular/cdk                       │ 21.0.1            │ ^21.0.1           │
│ @angular/cli                       │ 21.0.1            │ ^21.0.1           │
│ @angular/common                    │ 21.0.1            │ ^21.0.1           │
│ @angular/compiler                  │ 21.0.1            │ ^21.0.1           │
│ @angular/compiler-cli              │ 21.0.1            │ ^21.0.1           │
│ @angular/core                      │ 21.0.1            │ ^21.0.1           │
│ @angular/forms                     │ 21.0.1            │ ^21.0.1           │
│ @angular/material                  │ 21.0.1            │ ^21.0.1           │
│ @angular/material-date-fns-adapter │ 21.0.1            │ ^21.0.1           │
│ @angular/platform-browser          │ 21.0.1            │ ^21.0.1           │
│ @angular/router                    │ 21.0.1            │ ^21.0.1           │
│ @angular/youtube-player            │ 21.0.1            │ ^21.0.1           │
│ rxjs                               │ 7.8.2             │ ~7.8.0            │
│ typescript                         │ 5.9.3             │ ~5.9.2            │
│ vitest                             │ 4.0.12            │ ^4.0.12           │
└────────────────────────────────────┴───────────────────┴───────────────────┘

Anything else?

I was able to work around this by explicitly casting the field to a FieldTree<string> instead of a FieldTree<string | null> using a pipe:

<textarea [field]="personForm.bio | nullableField"></textarea>
@Pipe({
  name: 'nullableField',
  pure: true
})
export class NullableFieldCoercionPipe implements PipeTransform {
  public transform<T>(field: FieldTree<T | null>): FieldTree<T> {
    return field as FieldTree<T>;
  }
}

As far as I can tell, untouched/unpopulated field values from a <textarea> correctly remain null if untouched by the user, so hopefully this is just a typing issue.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions