DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • The Most Popular Angular UI Libraries To Try in 2021
  • Create a Beautiful Login Form With Angular Material
  • Dynamic Web Forms In React For Enterprise Platforms
  • How to Get Word Document Form Values Using Java

Trending

  • Measuring the Impact of AI on Software Engineering Productivity
  • Artificial Intelligence, Real Consequences: Balancing Good vs Evil AI [Infographic]
  • Automatic Code Transformation With OpenRewrite
  • How to Convert XLS to XLSX in Java
  1. DZone
  2. Coding
  3. Frameworks
  4. Angular Reactive Typed Forms — Not Just a Dream

Angular Reactive Typed Forms — Not Just a Dream

This article will focus on Angular 14 and the most significant update since Ivy, including Typed Reactive Forms and Standalone Components plus minor improvements.

By 
Anastasios Theodosiou user avatar
Anastasios Theodosiou
·
Jul. 21, 22 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
4.5K Views

Join the DZone community and get the full member experience.

Join For Free

When the new Angular version 14 was released, I was quite satisfied with two new features, and I wanted to share them with you. The first is Typed Reactive Forms, and the second is Standalone Components.

After 6 years of the first release and after months of discussion and feedback, the most needed feature and up-voted issue in the Angular repository are now solved in Angular v14!

Angular 14 was released 2nd of June with the most significant update since Ivy. It includes two long-awaited features, Typed Reactive Forms, and Standalone Components, as well as several minor improvements.

In this article, we will focus on Typed Reactive Forms. As before Angular v14, Reactive Forms did not include type definitions in many of its classes, and TypeScript would not catch bugs like in the following example during compilation.

TypeScript
 
const loginForm = new FormGroup({
  email: new FormControl(''),
  password: new FormControl(''),
});

console.log(login.value.username);


With Angular 14, the FormGroup, formControl, and related classes include type definitions enabling TypeScript to catch many common errors.

Migration to the new Typed Reactive Forms is not automatic.

The already existing code containing FormControls, FormGroups, etc.. will be prefixed as Untyped during the upgrade. It is important to mention that if developers would like to take advantage of the new Typed Reactive Forms, must manually remove the Untyped prefix and fix any errors that may arise.

More details about this migration can be found at the official Typed Reactive Forms documentation. 

A Step-By-Step Migration Example of an Untyped Reactive Form

Let's say that we have the following register form.

TypeScript
 
export class RegisterComponent {
  registerForm: FormGroup;

  constructor() {
    this.registerForm = new FormGroup({
      login: new FormControl(null, Validators.required),
      passwordGroup: new FormGroup({
        password: new FormControl('', Validators.required),
        confirm: new FormControl('', Validators.required)
      }),
      rememberMe: new FormControl(false, Validators.required)
    });
  }
}


Angular also provided an automated migration to speed up the process. This migration will run when we, as developers, run the following command.

ng update @angular/core  or on demand if we already manually updated your project by running the next command.   ng update @angular/core --migrate-only=migration-v14-typed-forms .

In our example, if we use the automated migration, we end up with the above-changed code.

TypeScript
 
export class RegisterComponent {
  registerForm: UntypedFormGroup;

  constructor() {
    this.registerForm = new UntypedFormGroup({
      login: new UntypedFormControl(null, Validators.required),
      passwordGroup: new UntypedFormGroup({
        password: new UntypedFormControl('', Validators.required),
        confirm: new UntypedFormControl('', Validators.required)
      }),
      rememberMe: new UntypedFormControl(false, Validators.required)
    });
  }
}


The next step now is to remove all the Untyped* usage and adjust properly our form.

Each UntypedFormControl must be converted to FormControl<T>, with T the type of the value of the form control. Most of the time, TypeScript can infer this information based on the initial value given to the FormControl.

For example, passwordGroup can be converted easily:

TypeScript
 
passwordGroup: new FormGroup({
  password: new FormControl('', Validators.required), // inferred as `FormControl<string | null>`
  confirm: new FormControl('', Validators.required) // inferred as `FormControl<string | null>`
}),


Note that the inferred type is string | null and not string. This is because calling .reset() on a control without specifying a reset value resets the value to null. This behavior is here since the beginning of Angular, so the inferred type reflects it. We’ll come back to this possibly null value in an example below, as it can be annoying (but there is always a way).

Now let’s take the field registerForm. Unlike FormControl, the generic type expected by FormGroup is not the type of its value but a description of its structure in terms of form controls:

TypeScript
 
registerForm: FormGroup<{
  login: FormControl<string | null>;
  passwordGroup: FormGroup<{
    password: FormControl<string | null>;
    confirm: FormControl<string | null>;
  }>;
  rememberMe: FormControl<boolean | null>;
}>;

constructor() {
  this.registerForm = new FormGroup({
    login: new FormControl<string | null>(null, Validators.required),
    passwordGroup: new FormGroup({
      password: new FormControl('', Validators.required),
      confirm: new FormControl('', Validators.required)
    }),
    rememberMe: new FormControl<boolean | null>(false, Validators.required)
  });
}


Nullability in Forms

As we can see above, the types of the controls are string | null and boolean | null, and not string and boolean as we could expect. This is happening because if we call the .reset() method on a field, resets its value to null. Except if we give a value to reset, for example .reset(''), but as TypeScript doesn’t know if and how you are going to call .reset(), the inferred type is nullable.

We can tweak behavior by passing the nonNullable options (which replaces the new option introduced in Angular v13.2 initialValueIsDefault). With this option, we can get rid of the null value if we want to! 

On one hand, this is very handy if your application uses strictNullChecks, but on the other hand, this is quite verbose, as we currently have to set this option on every field (I hope this changes in the future).

TypeScript
 
registerForm = new FormGroup({
  login: new FormControl<string>('', { validators: Validators.required, nonNullable: true }),
  passwordGroup: new FormGroup({
    password: new FormControl('', { validators: Validators.required, nonNullable: true }),
    confirm: new FormControl('', { validators: Validators.required, nonNullable: true })
  }),
  rememberMe: new FormControl<boolean>(false, { validators: Validators.required, nonNullable: true })
}); // incredibly verbose version, that yields non-nullable types


Another way to achieve the same result is to use the NonNullableFormBuilder. A new property introduced by Angular v14 called nonNullable that returns a NonNullableFormBuilder, which contains the usual as known control, group, array, etc., methods to build non-nullable controls.

Example of creating a non-nullable form group:

TypeScript
 
constructor(private fb: NonNullableFormBuilder) {}

registerForm = this.fb.group({
  login: ['', Validators.required]
});


So, Does This Migration Worth It? What Do We Gain With Typed Reactive Forms?

Before Angular v14, the existing forms API does note performing very well with TypeScript because every form control value is typed as any. So, we could easily write something like this.registerForm.value.something, and the application would compile successfully.  

This is no longer the case: the new forms API properly types value according to the types of the form controls. In my example above (with nonNullable), the type of this.registerForm.value is:

TypeScript
 
// this.registerForm.value
{
  login?: string;
  passwordGroup?: {
    password?: string;
    confirm?: string;
  };
  rememberMe?: boolean;
}


We can spot some ?  in the type of the form value. What does it mean?

It is widely known that in Angular, we can disable any part of our form we want; if so, Angular will automatically remove the value of a disabled control from the value of the form.

TypeScript
 
this.registerForm.get('passwordGroup').disable();
console.log(this.registerForm.value); // logs '{ login: null, rememberMe: false }'


The above result is a bit strange, but it explains sufficiently why the fields are marked as optional if they have been disabled. So, they are not part of the this.registerForm.value anymore. TypeScript calls this feature a Partial value.

There is also a way to get the hole object even with the disabled fields by running the .getRawValue() function on the form. 

TypeScript
 
{
  login: string;
  passwordGroup: {
    password: string;
    confirm: string;
  };
  rememberMe: boolean;
} // this.registerForm.getRawValue()


Event More Strictly Typed .get() Function

The get(key) method is also more strictly typed. This is great news, as we could previously call it with a key that did not exist, and the compiler would not see the issue.

Thanks to some hardcore TypeScript magic, the key is now checked, and the returned control is properly typed! It is also works with array syntax for the key as below.

TypeScript
 
his.registerForm.get('login') // AbstractControl<string> | null
this.registerForm.get('passwordGroup.password') // AbstractControl<string> | null

//Array Syntax
this.registerForm.get(['passwordGroup', '.password'] as const) // AbstractControl<string> | null


Also works  with nested form arrays and groups, and if we use a key that does not exist, we can finally get an error:

TypeScript
 
this.registerForm.get('hobbies.0.name') // AbstractControl<string> | null 

//Non existing key
this.registerForm.get('logon' /* typo */)!.setValue('cedric'); // does not compile 


As you can see, get() returns a potentially null value: this is because you have no guarantee that the control exists at runtime, so you have to check its existence or use! Like above.

Note that the keys you use in your templates for formControlName, formGroupName, and formArrayName aren’t checked, so you can still have undetected issues in your templates.

Something Fresh: FormRecord

FormRecord is a new form entity that has been added to the API. A FormRecord is similar to a FormGroup, but the controls must all be of the same type. This can help if you use a FormGroup as a map, to which you add and remove controls dynamically. In that case, properly typing the FormGroup is not really easy, and that’s where FormRecord can help.

It can be handy when you want to represent a list of checkboxes, for example, where your user can add or remove options. For example, our users can add and remove the language they understand (or don’t understand) when they register:

TypeScript
 
languages: new FormRecord({
  english: new FormControl(true, { nonNullable: true }),
  french: new FormControl(false, { nonNullable: true })
});

// later 
this.registerForm.get('languages').addControl('spanish', new FormControl(false, { nonNullable: true }));


If we try to add a control of a different type, TS throws a compilation error!

But as the keys can be any string, there is no type-checking on the key in removeControl(key) or setControl(key). Whereas if you use a FormGroup, with well-defined keys, you do have type checking on these methods: setControl only allows a known key, and removeControl only allows a key marked as optional (with a ? in its type definition).

If we have a FormGroup on which we want to add and remove control dynamically, we’re probably looking for the new FormRecord type.

Conclusion

I’m very excited to see this new form of API in Angular! This is, by far, one of the biggest changes in recent years for developers. Ivy was big but didn’t need us to make a lot of changes in our applications. Typed forms are another story: the migration is likely to impact dozens, hundreds, or thousands of files in our applications! 

The TypeScript support in Angular has always been outstanding but had a major blind spot with forms: this is no longer the case!

So, yes. It is totally worth it!!

Till next time,

Happy coding.

AngularJS DREAM (software) Form (document)

Published at DZone with permission of Anastasios Theodosiou. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • The Most Popular Angular UI Libraries To Try in 2021
  • Create a Beautiful Login Form With Angular Material
  • Dynamic Web Forms In React For Enterprise Platforms
  • How to Get Word Document Form Values Using Java

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!