-
Notifications
You must be signed in to change notification settings - Fork 38
Angular Reactive Forms
"As of Angular 14-16, reactive forms are strictly typed by default.", however, constructing typed FormGroup in TypeScript codes is still a manual process. Before the Angular team would develop respective features, WebApiClientGen can generate client API codes that provide typed FormGroups including validation codes according to annotation attributes decorating class properties or fields.
Article "Generate Typed FormGroup of Angular Reactive Forms with ASP.NET Core Web API" provides more details about application programming with the generated codes.
Declarative info of data constraints in 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
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),
});
}
Attribute | NG Validator |
---|---|
Required | required |
MaxLength | maxLength |
MinLength | minLength |
StringLength | minLength maxLength if each is greater than zero |
Range | min max if Maximum or Minimum is defined |
EmailAddress | |
RegularExpression | pattern |
.NET Integral Numeric Type to Angular Validator
Type | NG Validator |
---|---|
sbyte | Validators.min(-127), Validators.max(127) |
byte | Validators.min(0), Validators.max(256) |
short | Validators.min(-32768), Validators.max(32767) |
ushort | Validators.min(0), Validators.max(65535) |
int | Validators.min(-2147483648), Validators.max(2147483647) |
uint | Validators.min(0), Validators.max(4294967295) |
Remarks:
- For .NET long and ulong, the situation could be tricky, as in JavaScript, Number.MAX_SAFE_INTEGER=9007199254740991. It is better to let application programmers decide what to do.
- If a member property is of one of these integral types and is decorated by "RangeAttribute", the code gen won't yield validators based on the integral type but "RangeAttribute".
[DataContract(Namespace = Constants.DataNamespace)]
public class IntegralEntity : Entity
{
[DataMember]
public sbyte SByte { get; set; }
[DataMember]
public byte Byte { get; set; }
[DataMember]
public short Short { get; set; }
[DataMember]
public ushort UShort { get; set; }
[DataMember]
public int Int { get; set; }
[DataMember]
public uint UInt { get; set; }
[Range(-1000, 1000000)]
[DataMember]
public int ItemCount { get; set; }
}
export interface IntegralEntity extends DemoWebApi_DemoData_Base_Client.Entity {
byte?: number | null;
int?: number | null;
/** Range: inclusive between -1000 and 1000000 */
itemCount?: number | null;
sByte?: number | null;
short?: number | null;
uInt?: number | null;
uShort?: number | null;
}
export interface IntegralEntityFormProperties extends DemoWebApi_DemoData_Base_Client.EntityFormProperties {
byte: FormControl<number | null | undefined>,
int: FormControl<number | null | undefined>,
/** Range: inclusive between -1000 and 1000000 */
itemCount: FormControl<number | null | undefined>,
sByte: FormControl<number | null | undefined>,
short: FormControl<number | null | undefined>,
uInt: FormControl<number | null | undefined>,
uShort: FormControl<number | null | undefined>,
}
export function CreateIntegralEntityFormGroup() {
return new FormGroup<IntegralEntityFormProperties>({
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, [Validators.pattern('https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)')]),
byte: new FormControl<number | null | undefined>(undefined, [Validators.min(0), Validators.max(256)]),
int: new FormControl<number | null | undefined>(undefined, [Validators.min(-2147483648), Validators.max(2147483647)]),
itemCount: new FormControl<number | null | undefined>(undefined, [Validators.min(-1000), Validators.max(1000000)]),
sByte: new FormControl<number | null | undefined>(undefined, [Validators.min(-127), Validators.max(127)]),
short: new FormControl<number | null | undefined>(undefined, [Validators.min(-32768), Validators.max(32767)]),
uInt: new FormControl<number | null | undefined>(undefined, [Validators.min(0), Validators.max(4294967295)]),
uShort: new FormControl<number | null | undefined>(undefined, [Validators.min(0), Validators.max(65535)]),
});
}
Property or field of nested complex objects is not supported.
Property of field of array is not supported.
Default value of property is not supported, and System.ComponentModel.DefaultValueAttribute is not supported.
If you want a FormGroup with nested FormGroups and FormArrays, it is fairly easy to implement at the application codes level, for example:
export interface HeroWithNestedFormProperties extends DemoWebApi_Controllers_Client.HeroFormProperties {
address?: FormGroup<DemoWebApi_DemoData_Client.AddressFormProperties>,
phoneNumbers?: FormArray<FormGroup<DemoWebApi_DemoData_Client.PhoneNumberFormProperties>>,
}
export function CreateHeroWithNestedFormGroup() {
const fg: FormGroup<HeroWithNestedFormProperties> = DemoWebApi_Controllers_Client.CreateHeroFormGroup();
fg.controls.address = DemoWebApi_DemoData_Client.CreateAddressFormGroup();
fg.controls.phoneNumbers = new FormArray<FormGroup<DemoWebApi_DemoData_Client.PhoneNumberFormProperties>>([]);
return fg;
}