Repository: angular
Issue: Generate typed FormGroup including validations from interface/model through declarative info of validations
Labels: area: forms,area: compiler

### Which @angular/* package(s) are relevant/related to the feature request?

compiler-cli, compiler

### Description

["As of Angular 14, reactive forms are strictly typed by default."](https://angular.io/guide/typed-forms), however, constructing typed  FormGroup in TypeScript codes is still almost always a manual process. 

It can bring productivity to application programming that Angular compiler can generate a typed FormGroup from declarative info of validations.

And such design concept is illustrated in [WebApiCoreNG2FormGroupClientAuto.ts](https://github.com/zijianhuang/webapiclientgen/blob/NgTypedForm/HeroesDemo/src/clientapi/WebApiCoreNG2FormGroupClientAuto.ts) generated by [WebApiClientGen](https://github.com/zijianhuang/webapiclientgen) according to [declarative info of data constraints in C#](https://github.com/zijianhuang/webapiclientgen/blob/NgTypedForm/DemoWebApi.DemoDataCore/Entities.cs).

**Declarative info of data constraints in C#**
```c#
	/// <summary>
	/// Base class of company and person
	/// </summary>
	[DataContract(Namespace = Constants.DataNamespace)]
	public class Entity
	{
		public Entity()
		{
			Addresses = new List<Address>();
		}

		[DataMember]
		public Guid? Id { get; set; }

		/// <summary>
		/// Name of the entity.
		/// </summary>
		[DataMember(IsRequired =true)]//MVC and Web API does not care
		[System.ComponentModel.DataAnnotations.Required]//MVC and Web API care about only this
		[MinLength(2), MaxLength(255)]
		public string Name { get; set; }

		/// <summary>
		/// Multiple addresses
		/// </summary>
		[DataMember]
		public IList<Address> Addresses { get; set; }


		[DataMember]
		public virtual ObservableCollection<PhoneNumber> PhoneNumbers { get; set; }

		public override string ToString()
		{
			return Name;
		}

		[DataMember]
		public Uri Web { get; set; }

		[DataMember, EmailAddress, MaxLength(255)]
		public string EmailAddress { get; set; }
	}

	[DataContract(Namespace = Constants.DataNamespace)]
	public class Person : Entity
	{
		[DataMember]
		public string Surname { get; set; }
		[DataMember]
		public string GivenName { get; set; }

		/// <summary>
		/// Date of Birth.
		/// This is optional.
		/// </summary>
		[DataMember]
		public DateOnly? DOB { get; set; }

		[DataMember]
		[DataType(DataType.Date)]
		public DateTimeOffset? Baptised { get; set; }

		public override string ToString()
		{
			return Surname + ", " + GivenName;
		}

	}

```

**Typed FormGroups generated**
```js
	export interface Person extends DemoWebApi_DemoData_Base_Client.Entity {

		/** Data type: Date */
		baptised?: Date | null;

		/**
		 * Date of Birth.
		 * This is optional.
		 */
		dob?: Date | null;
		givenName?: string | null;
		surname?: string | null;
	}
	export interface PersonFormProperties extends DemoWebApi_DemoData_Base_Client.EntityFormProperties {

		/** Data type: Date */
		baptised: FormControl<Date | null | undefined>,

		/**
		 * Date of Birth.
		 * This is optional.
		 */
		dob: FormControl<Date | null | undefined>,
		givenName: FormControl<string | null | undefined>,
		surname: FormControl<string | null | undefined>,
	}
	export function CreatePersonFormGroup() {
		return new FormGroup<PersonFormProperties>({
			emailAddress: new FormControl<string | null | undefined>(undefined, [Validators.email, Validators.maxLength(255)]),
			id: new FormControl<string | null | undefined>(undefined),
			name: new FormControl<string | null | undefined>(undefined, [Validators.required, Validators.minLength(2), Validators.maxLength(255)]),
			web: new FormControl<string | null | undefined>(undefined),
			baptised: new FormControl<Date | null | undefined>(undefined),
			dob: new FormControl<Date | null | undefined>(undefined),
			givenName: new FormControl<string | null | undefined>(undefined),
			surname: new FormControl<string | null | undefined>(undefined),
		});

	}
```

There are some limitations in such code generation for type FormGrooup:
1. Generic types not supported, because FormGroup takes only concrete types.
2. (Nested) member fields of complex types not supported because of various reasons. 

### Proposed solution

Directive in Angular is similar to attribute in C#. Attributes in C# is used by .NET run time, and directive is used by Angular compiler.   I would purpose such solution with some pseudo directives.

```js
	export interface Entity {

		/**
		 * Multiple addresses
		 */
		addresses?: Array<DemoWebApi_DemoData_Client.Address> | null;

		/** Max length: 255 */
		@validation([Validators.email, Validators.maxLength(255)])
		emailAddress?: string | null;
		id?: string | null;

		/**
		 * Name of the entity.
		 * Required
		 * Min length: 2
		 * Max length: 255
		 */
		@validation('Super Hero', [Validators.required, Validators.minLength(2), Validators.maxLength(255)])
		name: string;
		phoneNumbers?: Array<DemoWebApi_DemoData_Client.PhoneNumber> | null;
		web?: string | null;
	}
```

**Application codes:**
```js
const myFormGroup = FormGroup.create<Entity>();
```

**NG compiler or its pre-processor or IDE generates**
```js
const myFormGroup = new FormGroup({
			emailAddress: new FormControl<string | null | undefined>(undefined, [Validators.email, Validators.maxLength(255)]),
			id: new FormControl<string | null | undefined>(undefined),
			name: new FormControl<string | null | undefined>('Super Hero', [Validators.required, Validators.minLength(2), Validators.maxLength(255)]),
			web: new FormControl<string | null | undefined>(undefined),
		});
```

Sometimes a nested complex object like "addresses" is to be included in the FormGroup, then a directive like `@formGroup(1)` or `@formArray()` could decorate a respective member field.

The reason why `@formGroup()` has a number parameter is to avoid endless recursive nesting.

### Alternatives considered

If such feature is implemented, WebApiClientGen just need to exporting respective directives according to C# attributes.

An alternative design is to have a declarative object to associated a data model/interface with a validation map, mapping some member fields with some validations. However, such approach may look almost the same as the current way, and require a lot more design change in Angular compiler. And this is not friendly to code generators like WebApiClientGen

**Remarks:**
Comparing with https://github.com/angular/angular/issues/46864, this proposal emphasizes on validations through declarative attributes (.NET) and directives (Angular), and cover more scenarios of application programming.