What do you want to learn?
Skip to main content
Angular Reactive Forms
by Deborah Kurata
You can build forms in Angular by using a Reactive approach by defining the form model and validation in your component code. This course details how to build Reactive forms, validate user-entered data, and save that data using HTTP.
Start CourseBookmarkAdd to Channel
Table of contents
Hello, my name is Deborah Kurata. Welcome to my course, Angular 2: Reactive Forms, from Pluralsight. Did you know that there are two different ways to create data entry forms in Angular? The template-driven approach uses HTML with two-way data binding. The Reactive approach manages the form and its data in the component code. This course looks at the similarities and differences between the template-driven and Reactive forms approaches, helping you decide the best technique to use for your team and your project. It details how to use the Reactive forms approach, shifting the logic for the form from the HTML to the component code, giving you more flexibility and more control. It includes techniques such as watching for and reacting to user changes, and dynamically duplicating input elements, allowing you to build powerful and Reactive forms. The course walks through validation scenarios, such as adjusting validation at run time and custom and cross-field validation. And since forms display and save data, you'll learn how to send get, post, put, and delete requests to a web server using HTTP and observables. By the end of this course, you'll have the skills to build powerful and dynamic data entry forms using the Reactive forms approach. I hope you'll join me on this journey through Angular 2: Reactive Forms, from Pluralsight.
We can build forms in Angular 2 with a template-driven approach using HTML and data binding, or we can build Reactive forms using a model-driven approach by defining the form model and validation in our component code. Welcome to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and this course provides the concepts you need to build Reactive forms, validate user-entered data, and save that data using HTTP. We've all filled out our share of online forms. Register for an event, fill out a form. Make a dentist appointment, fill out a form. And often our company runs on online forms, forms for tracking our time, forms for booking conference rooms, forms for managing inventory, and so on. In this introductory module, we start with a short overview of forms in Angular. We consider several suggestions for getting the most from this course. We explore the demo form and sample application that we will use in this course, and we look at the topics we'll cover in the remainder of this course. Okay then, let's get started.
When we build a form with Angular, we always create a component class for the form logic, and a template for the form's user interface. If the form updates data, the component class calls a service to retrieve existing data from a database or other data store on a server somewhere, and that data is displayed as default values on the form. We then collect user entries, validating the data as the user types. If needed, we display validation messages back to the user. When the user submits the form, the component class calls a service to pass the validated data back to the database or other data store on the server, and the user's entries or updates are saved. How do we build the template and component class for our forms? Angular provides two techniques for building forms, template-driven and Reactive, previously called model-driven. Both techniques share the same basic concepts, but as its name implies, template-driven forms put the responsibility for the forms squarely in the template, which we define with basic HTML and data binding. Reactive forms shift much of that responsibility to the component class. We still create the template with HTML, but we manage the data and validation in the component code. Let's take a high level look at the strengths of each technique. There are many advantages to building forms using a template-driven approach. The template-driven technique is straightforward and easy to use, and it looks very familiar if you've built forms with Angular 1. Because template-driven forms rely heaving on two-way data binding, we don't have to write any code to copy the data to the input elements, or track the user's entries, it's all handled automatically. Speaking of automatically, Angular also automatically tracks form and input element state. We can use this state information to determine when the form is invalid and disable the Save button, for example. With much more of the logic for our form in our class, Reactive forms provide more flexibility, they handle more complex validation scenarios, such as changing validation based on a user's selection or on the form state. Reactive forms do not use data binding, so the form cannot mutate our data model. The component code controls how to handle any changes to the data; some developers prefer this immutability. Using Reactive forms makes it easier to perform an action on a value change, such as transforming the value to uppercase or performing a partial lookup. We have access to reactive transformations, such as DebounceTime to delay reacting to user input, and DistinctUntilChanged to ignore values that are the same as previously processed values. Reactive forms make it easier to dynamically add input elements to the form, so we could allow the user to enter any number of addresses, for example. And since more of the logic and validation are in the code, not the template, some developers find that the logic is easier to unit test. There is no magic going on under the hood when you work with Reactive forms, everything is direct, there is no template between your code and your data structures. The primary issue with Reactive forms, however, is that they require more code. Depending on your team, its experience, and its background, some of these factors may weigh more heavily than others. It is important to select the appropriate forms approach that works best for you and your team. To help you make that decision, we'll dive deeper into their similarities and differences, and examine the code for both techniques in the next modules. For now, let's look at some tips for getting the most from this course.
Get the Most from This Course
First, the prerequisites. To get the most from this course, it is important that you know the basics of Angular 2. This means understanding Angular modules, components, templates, including binding, services, and basic routing. If you don't have the requisite knowledge, consider taking one of the introductory Angular 2 courses here on Pluralsight, such as Angular 2: Getting Started or Angular 2: First Look. You do not need any prior knowledge of Angular forms; we'll cover what you need in this course. Another way to get the most from this course is to join the discussion. Thoughts, comments, or questions as you watch this course? Feel free to use the Discussion tab, you can find the link to the discussion on the Pluralsight page for this course, or follow me on Twitter. It would be great to hear about your experiences with Reactive forms. There is also a blog post specifically for this course at the URL shown here. This post identifies common issues along with their solutions. If you have any problems with the code for the course, check here first, there may already be a solution posted. When building web applications, regardless of the technologies we use, there are lots of steps and places where things can go wrong. That's when a good checklist can come in. I'll present checklists at the end of many of the modules, and we'll use them as a brief review of what was covered in that module. Feel free to jump ahead to the checklist if you have any problems when coding along with the demos, and consider referencing these checklists as you start building your own Angular forms applications. Coding along with the demos is another great way to get the most from this course. Though not required, it is often helpful to try out the presented code as you navigate through the course. To get you started, I've set up a public GitHub repository specifically for this course, it is called Angular2-ReactiveForms, and you can find it at this URL. The starter files for the forms demo are here, you can use these files as a starting point if you want to code along with the demos. If you'd prefer to follow along with the completed demo form code, you can find that here. Toward the end of this course, we'll look at a form in the context of a more full-featured sample application; that sample application is here. If you are new to GitHub, simply click this button to download all of the code as a zip file. So what's in this code? Let's take a look.
Demo Form and Sample Application
We start with a simple demo sign up form built using the template-driven approach. We examine the code, then rebuild this form using the Reactive forms approach to understand the key differences between the two techniques. Then we add more features, such as custom validation, cross-field validation, and dynamic addition of multiple addresses. Later in this course, we'll examine a second Reactive form in the context of a more full-featured Angular application. The sample application demonstrates routing to the form, including routing guards, and details the data access service to communicate with a back-end server via HTTP to retrieve and save data. Let's see this sample application in action. Welcome to Acme Product Management. As its name implies, this application manages our company's current list of products. We'll use this application later in this course, when we look at a Reactive form in the context of a more full-featured application. This application may look familiar if you have seen the Angular 2: Getting Started course here on Pluralsight. I've incorporated an edit form, and add, edit, and delete operations into the sample application that was built in the Getting Started course. Here at the top is our menu for access to the key features of the application. In a real application, there may be more options here, but we want to focus on building a Reactive form, so I've limited the application to basic functionality. Clicking on the Product List option displays the Product List page. From here we can click the Edit button to edit the product. Clicking on the product name navigates to the Product Detail page; we also have an Edit button here. Clicking the Edit button displays the Reactive form for editing the selected product. Notice that each of the fields are populated with existing values, the user can edit product information, or click Delete to delete the product. If the user makes a mistake and breaks one of the validation rules, the application displays a validation error message, and the Save button is disabled. Let's enter a new product name. If the users tries to navigate away from the page, we have a routing guard to notify them of their unsaved changes. If the user wants more search tags for this product, they can click Add Tag. This dynamically duplicates the input elements, the label and input box in this example, so the user can add any number of search tags. Clicking Save saves the product and returns to the Product List page where we see our updated value. Clicking the Add Product option displays the same form, but initialized for entry of a new product. Notice that the Save is disabled until the form values are valid. Once all of the data is valid, clicking the Save saves the new product and navigates back to the Product List page. Here we see our new product. So that's the basics of a sample application that we'll look at later in this course. Now let's finish up this introductory module with a look at the outline for the remainder of this course.
In this course, we start with a more detailed look at the similarities and differences between template- driven and Reactive forms, including an examination of the building blocks that make up the form model. Next, we build a Reactive form, first using the form model building blocks directly, and then using FormBuilder to simplify the code. We add validation rules and error messages to validate the user's entries, including custom validation and cross-field validation. With Reactive forms, we can adjust how a form works reactively based on user entries and selections. For example, based on a user's selection, we can change the form's validation rules. We'll see how to watch for user changes and react to those changes. We'll also examine reactive transformations, such as DebounceTime. Sometimes a single entry is not enough, the user may need to enter multiple email addresses, or multiple address blocks, or multiple search tags. In that case, we can dynamically duplicate input elements. We'll see how to do that with Reactive forms and form arrays. Then we switch gears a bit, and examine a Reactive form in the context of a more full-featured sample application. We'll look at techniques for routing to the form, setting up routing guards, and building reusable custom validation. In many scenarios the key purpose of a form is to collect or edit data that is then stored in a database or other data stores somewhere. To do that we need to deal with CRUD, or create, read, update, and delete operations for retrieving and saving data to a back-end server with HTTP. By the end of this course we'll have a simple, but fully operational Angular application, that includes a Reactive form with full CRUD support. You can use this application as a reference for your own development. Let's get started.
Template-driven vs. Reactive Forms
To determine whether Reactive forms is the right choice for your project and your team, it is important to understand your form options. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and in this module we lay the foundation for understanding Reactive forms by looking at the similarities and differences between it and template-driven forms. Coffee or tea? Cash or credit? Paper or plastic? Right or left? We face many choices every day, and when it comes to building forms in Angular, our choice is whether to use a template-driven or Reactive forms approach. In this module, we start with the Angular form building blocks used by both approaches, FormGroup and FormControl. We see what they are, and what they do. Next, we walk through the template syntax for both template-driven and Reactive forms. Then we examine a simple template-driven form. Our focus is not on all the features of Template-driven forms, but rather on understanding enough to compare it clearly with Reactive forms. Lastly, we look at several complex form processing scenarios as a segue to Reactive forms. Let's get started.
Form Building Blocks
One of the key uses of forms is for the user to enter data that we save somewhere. Say that we display a form to the user, like this form. We don't want to save data that is incomplete or invalid, so we validate the user's entries as needed. If the entered information is not valid, we indicate that to the user and display an appropriate validation error message. There are many ways to style invalid entries and display the validation message, this is the simple technique we will use. We also want to disable the Save button until the input elements contain valid data. To achieve all of this, we track the state of each input element and the state of the form itself. Angular has a set of predefined form and input element states. These states define whether the user has changed a value in an input element. If the value is unchanged, the state of the input element is pristine. Changed values have a state of dirty. If all input elements on a form are pristine, then the form itself is pristine. If any input element on the form is dirty, then the form state is dirty. A state and an array track validity. The input element state is valid if it passes all defined validation rules. The form itself is valid if all input elements on the form are valid. Validation errors are added to an errors collection. The key of each array element is the name of the validation rule associated with the error. These states define whether the user has visited an input element. A value of touched means the user has set focus into the input element, and then left the input element. Untouched is set if the user has not set focus, then left the input element. A form is touched if any input element has been touched. These visited states are useful to prevent displaying the required validation error message until the user has had a chance to enter something. How do we track all of this state information? Luckily for us, Angular has building blocks that define a form model, which tracks all of this state, plus the value of each input element. These building blocks are used by both template-driven and Reactive forms to track state and values. FormControl tracks the value and state of an individual input element, such as an input box. FormGroup tracks the value and state of a group of form controls. A form itself is managed as a form group, because what is a form but a group of input elements whose state and value we wish to track together. But any group of input elements on a form can comprise a FormGroup, we could group the input elements for a mailing address block, for example. And FormGroups themselves can be nested within other FormGroups. For clarity, I'll often refer to the forms FormGroup as the root FormGroup. These building blocks are actually classes provided when we work with Angular forms. Instances of these classes define the form model. I've mentioned the term form model several times now. What is a form model exactly? A form model is the data structure that represents the HTML form, and it looks like this. The structure of the form model reflects the form and input elements in the template. In most cases, each input element within the HTML form has a corresponding form control in the form model. The form model retains form state, such as dirty or valid. It retains the user's entries and its value property. As the user types into any input element on the form, the value property is changed accordingly. The form model also tracks all of the FormControls and nested FormGroups within the form, along with their state and value. Don't confuse the form model with the data model that we use with data binding. We'll see both the data model and form model when we get to the demo. We use these properties from the form model anytime we need to know the state or value of a FormControl or FormGroup. This form model is the same for both template-driven and Reactive forms, but how it is created is different. When using a template-driven approach we write HTML in our template for the form element, each input element, data binding, validation rules using attributes, and validation error messages. Angular automatically generates the associated form model, we can then use the form model as needed. In our component class, we define properties for the data binding. These properties represent our data model. We also implement methods for form operations, such as getting the data for display on the form, and saving that data on a submit. Two-way data binding is a key part of template-driven forms, as it keeps all of the data on the form in synchronization with properties in the component class. Reactive forms shift the responsibility for creating the form model to the component class. We define the form model by creating the instances of the FormGroup and FormControl building blocks in our component class. We define the validation rules in the class. We can even handle display of validation error messages in the class. We manage the data for the form in the class, no data binding in the HTML. And the class provides methods for form processing, such as handling the submit. We still define the visual parts of the form in the template, so we create the form element and input elements in the template. We then bind these input elements to the form model defined in the component class. So instead of binding the input elements to the data model properties directly, we bind to the form model we build in our component class.
Template-driven and Reactive forms use entirely different sets of directives for binding the FormControl and FormGroup building blocks to the form and input elements in the template. When using template-driven forms, we first import the forms module to bring in the appropriate set of directives. This includes ngForm to access the form model Angular generates for us, ngModel for two-way binding, and to access the input element state defined in the generated form model, and ngModelGroup for grouping input elements within the form. When we add a form element to our template, Angular automatically assigns the ngForm directive to that form. Angular creates the form model starting with the root FormGroup instance, and automatically binds it to the form to track the form value and state. We never have to apply the ngForm directive ourselves. If we want to access the form model state information in our template, we export the ngForm directive into a template reference variable like this. Here we use a hash to define a template reference variable called signupForm, and set it equal to ngForm, this variable then references the form's root FormGroup instance. Anytime we want to access the form model, we use this template reference variable. In this example, we check the valid property of the FormGroup instance to disable the Save button if the form is not valid. We'll see this in context in the upcoming demo. If you've done any two-way data binding in Angular, you are already familiar with the ngModel directive. We use this directive on each input element to keep the component class property in sync with the user-entered value. In this example, any change to the firstName input element is automatically reflected in the firstName property of the customer defined in the component class. When we add an ngModel to an input element within a form, Angular automatically creates a FormControl instance, and adds it to the form model using the input element's name as the key, hence the reason we need the name attribute here. Angular uses that name attribute for the FormControl instance key. The FormControl instances tracks the input element's value and state. We can access the FormControl state by exporting the ngModel directive into a template reference variable. Here we use a hash to define a template reference variable named firstNameVar, and set it equal to ngModel. This variable then references the form control instance for this input element. We can use the template reference variable to access the FormControl state. For example, we can use the valid property of the FormControl to determine when to display validation messages. We'll see that in the upcoming demo. To use Reactive forms, we first input the ReactiveFormsModule to bring in its appropriate set of directives. As you can see, this is an entirely different set of directives then for the template-driven approach. Notice how close these directive names map to the FormGroup and FormControl building blocks. With the Reactive forms approach, Angular does not create a form model for us, rather we create it ourselves in our component class. We then use these directives in the template to bind the form and input elements to our defined form model. So for both techniques, building an Angular form requires building a template with the appropriate set of directives. Let's take a closer look.
Template Syntax for Forms
If you've built any web-based forms you recognize this, no Angular here, it's just HTML. We have a form element, we have input elements, such as this input box, along with a label for each data entry field we want to display. Each input element has attributes, such as these validation attributes. In this example, the first name is required and has a minimum length of 3 characters. Yeah, I know, this minlength validation is a bit funky. How would P. Diddy enter his name here, for example? But I wanted to show multiple validation rules for this element. We also have a button for the user to submit their entries. When building Angular forms we use this same HTML, and just add the appropriate directives for the template-driven or Reactive approach. Let's look at this form using, first, a template-driven approach, and then Reactive. A template-driven form looks like this. We still have the form element and we're using event binding to bind to a save method when the user submits the form using the Save button, let's skip the div element for a moment. We have the same label from this simple HTML example, and the same input element with a little more stuff attached to it. Template-driven forms leverage the HTML validation attributes for its validation, so those are still here. One of the key features of template-driven forms is how it leverages data binding using ngModel. Here we bind a property from our component class to the value of this input element. Two-way data binding keeps the input element value and the property value in sync. Template-driven forms require the name attribute to properly associate the FormControl with the FormGroup in the generated form model. And here we define a template reference variable to access the FormControl instance. Going back to that div element, we use the defined template reference variable and check the FormControl state properties to determine whether or not to style the input element as an error. Here we are referencing the has-error style class from the Bootstrap style framework. The first part of this and operation is checking the FormControl touched state. If the user has touched the element, meaning the user set focus to and left the field, than touched is true. The second part of this and operation is checking the FormControl valid state. Angular automatically performs the validation as defined by the validation attributes. If the input element's value is not valid, the valid property is not true. If touched is true and the input element value is not valid, then the div element class changes to has-error, and the field and its label turn red. So, why are we checking touched and not valid? Because when the form is displayed empty, we don't want the required validation to show the input elements as an error before the user has a chance to enter something. The span tag displays our validation message. Note that we are only showing one message here. We'll see how to display both our required field message and our min length message when we get to the demo. We again use the form control state properties to determine whether to display the message. Here we check the touched property and the errors collection. If the input element was touched and the errors collection contains any values; we display the error message. How about the Reactive form template? Recall that the form model is built in the component class, so no need for template reference variables. Reactive forms do not use two-way binding, so no ngModel or name attribute. And with Reactive forms, the validation is defined in the class, so no validation attributes here either. We still have the form element, but it now binds the formGroup directive to the form model we create in our component class. We still have a label and input element for each data entry field, but now each input element is bound to the name of a formControl instance in the form model. To display the validation message and set the error style, the template uses the formError property defined in the component class. We'll look at the syntax in detail and build our own Reactive form in the next module, but first let's spend a moment looking at the code for a template-driven form, just enough to have the background information we need to compare it with Reactive forms.
Template-driven Form: Template
In this demo, we'll take a quick look at a template-driven form. If you'd like to code along with the course, now is a good time to download the demo project from my GitHub repo here. The first part of this course works with a demo application starting with Demo-Start. Later in the course, we'll add a form to a more full-featured application, APM, but we'll ignore that project for now. To code along, you'll need a recent version of npm to install all of the libraries for the Demo-Start application. I've downloaded the Demo-Start project and moved it to a Demo folder, I then opened the Demo folder in VS Code, but you can use any desired editor. VS Code now has an integrated terminal, we can open that from the View menu, or with the defined hotkey. From here you can type npm install to install the packages for this demo project. I've already done this step, so the node_modules are already in place. I can then type npm start to launch the application. Here is a simple form for a customer to sign up for a mailing list. These three fields are required, but have not yet been entered. Even though the form is current invalid, notice that no validation messages are yet displayed, but because the form state is currently invalid, the Save button is disabled. You may have noticed this extra data down here below the form, I'm displaying some of the values from the form model that Angular automatically generated for us. The Dirty state of the form is currently false, because we have not yet interacted with the form. The Touched state of the form is also false because we have not touched any of the input elements. The Valid state is false because the first three input elements have the required attribute, and we have no value in those fields. The root FormGroup's Value property displays the name of each input element, along with its current value, which is empty at this point. If we set focus to the firstName input element, then leave that element, the element has been touched, and we see the validation error message. The Touched state displayed below the form is now true. If we enter one character, we see the message change to a different validation message, and now that we've typed something, the Dirty state is true. Notice also that the Value is changed to show the entered text. If we finish data entry, the validation message disappears, but the Valid property is still false; all input elements in the root FormGroup must be valid for the form state to be valid. To complete the form, I'll put in the last name and email address. The email validation message appears as soon as we type one character, because one character is not a valid email address. We'll see later how we can make this better, but as soon as the email address is of a correct format, the validation message disappears. When we've entered valid values into each of the required fields, the Save button is enabled. If we check the Send me your catalog checkbox, we see additional fields for entry of a postal address. This forms demonstrates the input box, checkbox, radio button, and select box HTML elements. Notice also that additional items are added to the Value object. Let's uncheck this for now to shorten the form. I'm logging the entire root FormGroup instance to the console when the user clicks on Save, so let's use the browser's developer tools to take a look, then click Save. Here is the root FormGroup, we can drill down. Let's scroll to the public members. The FormGroup's dirty property is true, just as we've seen, as is touched. Controls here is the list of each FormControl associated with this form. The name of each control is the name we defined with the input element name attribute, as is the name used in the value object. So template-driven forms gives us all of this information with very little effort on our part. Now let's look at the code. Here is the template for the Sign Up form. Notice all of these style classes, we are using the Bootstrap styling framework here to make our form look good, but you don't have to. Here is the form element, quite similar to the code we saw earlier in the slides. We use the novalidate attribute to tell the browser not to perform its validation, but rather to let Angular validate the entries. Here we set the templates reference variable to our form model, and when the user submits the form, we pass that form model reference to our component's save method, that way our component code can access the form model state or values. Here is the firstName input element; wow, that's quite a bit of HTML. We use the ngClass directive to set the has-error style class and turn the elements red when the input element was touched or dirty and is not valid. We set the validation attributes on the input element itself, here we have required and minlength. We use ngModel for the two-way data binding, we set the name attribute so the FormControl instance is associated correctly in the form model. We define the template reference variable to use for validation styling and messages. And we define the validation messages for the two different possible validation errors, notice the use of the errors collection here. When Angular detects a validation error, it adds an entry to the errors collection using the name of the validation rule as the key. We can use that name to display the appropriate validation message. Here, we use errors.required to display the message appropriate for the required field validation rule. And we use errors.minlength to display the message appropriate for the minlength validation rule. Notice how this similar code is repeated for each input element on our form. One thing to note about the email validation, as of the time of this recording, Angular forms doesn't yet support the validators for the HTML5 input types, such as email, phone, and date, so code like this will not work. We instead added a simple pattern for a very basic email format validation. Way down at the bottom is the Submit button, notice that we are using the form's template reference variable to access the form model's valid property. We disable the button if the form is not valid, and here, after the form, we are using the form's template reference variable to display the properties we were looking at in the browser. We display the Dirty, Touched, and Valid properties, we also display the Value of the form model, piped into a JSON structure for more readable format. Now let's look at the component.
Template-driven Form: Component
Wow, not much here. In the component class we define the code to manage our form. There are two key bits of information here in our class. The customer property manages the instance of the customer data that we are binding to in our template. We can use Go to Definition to view the definition of the customer data model here. We're using a class and not an interface in this case, because we want to create a new instance of this class for the new customer information. The other important bit of code here is the save method. Our template binds to this method, which is executed when the user submits the form. We pass in the form model from the template so we can check the form state and view its values. For example, we could skip saving if the user clicked Save without making any changes by checking the form's dirty property. It is here that we would call a service to save our data to a back-end server. We'll see how to build save functionality later in this course. The bottom line here is that the template-driven forms approach minimizes the component class code we need to write, but it is heavy on the template code; each control takes a full page of HTML. When using template-driven forms, Angular automatically creates the form model, we can then use that to track form and control state, and the ngModel two-way binding takes care of keeping our class properties in sync. But often in a real application, forms are more complex than this. Let's look at some of these scenarios.
Our story begins a week ago when our team met with Marketing to understand the requirements for a signup feature. We then developed a first prototype of the form and are presenting it to them today. We show them the form and how well it validates the customer's entries per their specifications, and when we click the Send me your catalog button, it allows the customer to put in a mailing address to receive our catalog. This prompts our first change request. Can we allow multiple addresses, they ask? It seems that our customers often want to receive catalogs at both their home and work, and they also want to ensure their subordinates get catalogs too. Who wouldn't want more catalogs? This generates some additional ideas. Can we watch what the user types into the Street Address and provide suggestions? This would make it easier for the user to send the catalog to multiple people at the same company. Then from the back of the room we get a question, can we see that first name validation again? We show them. I don't like that the customer sees an error when they type in one character, can't we wait until they stop typing to determine if they've entered enough characters? They continue. Many of our customers also like to receive notifications from us via email or text message, can we ask for their preference and add a phone number? Be sure to make the phone number required only if they pick notifications via text message. We take our list of change requests, and head back to our office. Implementing these change requests sounds like we'll end up with a complex set of HTML that will be difficult to build and maintain over time. There has to be another way. As we've just seen, template-driven forms is mostly just HTML, and is easy to use, but as soon as we start handling more complex scenarios, things get harder and messier. Scenarios such as dynamically adding input elements to the form, such as multiple address blocks, adding code to watch what the user types and reacting accordingly, waiting validation until the user stops typing, providing different validation rules for different situations, and what if your team prefers working with immutable data? That means you need to forgo two-way data binding, what then? These scenarios are where the Reactive forms approach really shines, as we'll see through the remainder of this course.
In summary, we can build forms in Angular with the template-driven approach, or with a Reactive approach. In the template-driven approach, the form model defining the root FormGroup, FormControls, and any nested FormGroups is automatically generated. The validation rules are defined in the HTML, and the input element values and data model properties are kept in sync automatically using two-way data binding. With Reactive forms, we manually create the FormGroups and FormControls for the form model in our component class. We add the validation in the class, and we forgo two-way data binding, instead managing the data flow ourselves. In this module, we looked at the Angular form building blocks for building the form model, FormGroup and FormControl. We saw how template-driven and Reactive forms use different directives in their template syntax to work with these building blocks. To clearly compare template-driven to Reactive forms, we examined the code for a template driven form, and we peeked at the data managed by the generated form model. Lastly, we went through a few complex scenarios that may be better suited for a Reactive forms approach. Next up, let's try out Reactive forms.
Building a Reactive Form
Building a form using the Reactive approach means more component code and less HTML. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and in this module we walk through building a simple reactive form with a focus on the basics. We'll expand on these basics throughout the remainder of this course. Sometimes a simple form is all we need, then the template-driven forms approach makes sense, it's quick and easy, and gives you a lot of functionality with very little code. Other times we need more. If you need more functionality, more flexibility, or more control of your forms and their associated data, selecting the Reactive forms approach may be the better choice. In this module, we build a Reactive form, we start with the component class. We update the Angular module to pull in the Reactive form directives, then we modify the template using those directives. We look at how to use setValue and patchValue to update form values. Lastly, we see how to simplify our component class with FormBuilder. Let's get started.
The Component Class
Recall from the prior module that when we use the template-driven approach, Angular automatically generates the form model by creating the FormGroup and FormControl instances for us, and we use ngModel for two-way binding to keep the user's entries and the data model properties in sync. With the Reactive forms approach, we define the form model by creating the FormGroup and FormControl instances ourselves in our component class. We then bind the template to the form model; this means that our form is not directly modifying our data model. So how do we create the form model? Every form model requires at least one FormGroup, which I call the root FormGroup. This is the FormGroup that represents the entire form. The data structure for our demo form's root FormGroup is shown here. It contains properties to retain state, to set default values, and retain user entries, and a child controls collection that represents the content of the form. A form model often has one FormControl in this controls collection for each input element on the form. Though this is not required, we could have input elements on our form that we don't need to track. For example, we could have a checkbox or a select element that manages part of the UI, but does not need to be tracked by our form model. The controls collection can included nested FormGroups to group sets of controls or other nested FormGroups. We'll look at nested FormGroups later in this course. The controls collection can also include FormArrays to work with groups of FormControls or FormGroups as an array. We'll try out FormArrays later in this course. We start creation of our form model with the root FormGroup. One way to create that root FormGroup is shown here. This code imports the FormGroup from angular/forms, so we can use it as the data type for our form model. We declare the root FormGroup here, this property holds the reference to our form model. Our form collects data from our customers, so we'll call it customerForm. Here we define our data model, we are managing customer data so we call it Customer. In the ngOnInit lifecycle hook method, we assign the customerForm property to a new instance of the FormGroup; this creates the root FormGroup for the form model. We could put this code in the constructor instead, but we selected to use ngOnInit to ensure the component and template are initialized before building the form model. In the FormGroup constructor we pass in an object; this object contains the FormControls and any nested FormGroups that comprise the contents of this root FormGroup. Like this, we first add FormControl to the import statement from angular/forms. In the FormGroup constructor, we add a set of key and value pairs. The key is the FormControl name, and the value is a new instance of a FormControl. We repeat this, one for each input element on the form. We can optionally pass a default value to the FormControl constructor as shown here. In this case, we pass true as the default value for the sendCatalog input element. We can also pass validation rules here in the FormControl constructor. We'll talk about validation in the next module. Let's jump into a demo and see how to change our current template-driven Form into a Reactive form.
The Component Class: Demo
In this demo, we build the form model for our demo form. We are back in our demo project just as we left it from the prior module. We are about to switch our demo form from the template-driven approach to the Reactive approach, so if you want to retain the code for the template-driven approach for future reference, now is the time to make a copy of your project folder. Are we ready to tackle the Reactive forms approach? Let's begin with the component class. First, let's delete the code we added specifically to look at the template-driven forms model. We don't need to pass the form into the save method, so let's delete that. Then we no longer need ngForm here, so we can delete that as well. Whenever we build a form using the Reactive approach, we start by importing our building blocks because we know we're going to need them. We import both FormGroup and FormControl from angular/forms. With the Reactive forms approach, we explicitly create instances of these building blocks to define our form model. Let's specify a property from the root FormGroup and call it customerForm, its type is FormGroup. This root FormGroup defines our form model. Our template will bind to this property to associate the HTML form element with this root FormGroup instance. We'll use the same data model as with the template-driven approach. Let's make one more change here to fix up our console.log. Now we'll log our customerForm property using this. Now we need to create and initialize this FormGroup property. We could do it right here on the declaration, or we could add it to the component class constructor, or we could put it in a lifecycle hook. Let's add it to the OnInit lifecycle hook, so that the instance is created when the form is initialized. First we import OnInit from angular/core, then we add the implements keyword to tell TypeScript that we want to implement the OnInit interface. Lastly, we implement the ngOnInit method. In this method we use the conventional syntax to create an instance of the FormGroup class using the new keyword, this.customerForm = new FormGroup. This creates an instance of the FormGroup and assigns it to our CustomerForm property. But we see a syntax error here. If we hover over this error we see that the FormGroup constructor requires that we pass in a set of controls that are associated with this FormGroup, each control is defined with a key and value pair where the key is the control name and the value is an AbstractControl. AbstractControl is the base class for FormControl and FormGroup. This means that our FormGroup can be initialized with FormControls and nested FormGroups. For now, we'll just initialize the FormGroup with a set of FormControls. We're passing in an object so we use curly braces, then we add a FormControl for each input element in the templates form. Let's start with the first name. We'll give it a control name of firstName and create a new FormControl instance using the new keyword. We repeat this syntax for the lastName, and create a new FormControl instance for it. Same for the email address, and the sendCatalog flag. If we want to set a default value in any of the associated input elements, we pass that to the FormControl constructor. Our Marketing Department, of course, wants the Send catalog option checked on by default, so we set that default by simply passing true into the constructor for the sendCatalog FormControl. We've now defined our form's root FormGroup with a FormControl for each of our four input elements. This structure is the form model, and tracks the form value and state. Don't confuse this form model with our data model. The form model defines the set of FormGroups and FormControls that match up with the HTML form and input elements. The data model defines the data passed to and from a back-end server; we are using the same Customer class data model as the template-driven approach. This is a good start, but before we can update the template to bind to our FormGroup and FormControls, we need to pull in the appropriate directives.
The Angular Module
We can think of an Angular module as a box. The box contains a related set of components. The templates associated with any component we declare in an Angular module can only access directives declared or imported into that same Angular module. If your Customer-Components template wants to use the Reactive forms directives, the Reactive-FormsModule must be added to the imports array in the same Angular module. If you are not familiar with Angular modules, consider watching the Angular 2: Getting Started course, which covers Angular modules in detail. Let's update our Angular module now. We are looking at the application's root Angular module called AppModule; it is here that we declared our CustomerComponent. The template in the CustomerComponent wants to use the Reactive forms directives, so it is in the same Angular module that we add the Reactive FormsModule to the imports array. Recall that FormsModule was for template-driven forms. Let's just change that to ReactiveFormsModule here and here. We are now set up to use the Reactive Forms directives. The templates associated with any component declared in this Angular module can now access these Reactive forms directives. Let's update our template next.
Now that we've defined the form model with its root FormGroup and its associated FormControls in the component class, we can update the template to bind to that form model. We bind the form to the FormGroup, and each input element to its associated FormControl using the Reactive forms directives. As we saw in the last module, these are the directives provided for Reactive Forms, we'll look at formGroup and formControlName here. We'll look at more of these directives later in this course. We use the formGroup directive to bind the form element in the template to the root FormGroup of our form model. We use square brackets to denote property binding, and assign it to the form model property from our component class. The form then knows not to build its own form model, and to instead use the one defined by the customerForm property. We use the formControlName directive to bind each input element to its associated FormControl. We bind to the name of the FormControl instance as defined in the form model. Here, we bind to the firstName formControl. Notice that there are no square brackets here, we are binding to a simple string name, not to a property. We add the formControlName directive to every input element within the form that we want to track. For styling, display of the validation error messages, and other purposes, the template may want to access the form model properties. For example, we may want to access the firstName FormControl properties to determine if the first name input element was touched or is valid. There are several techniques for accessing the form model properties. One option is to navigate through the form model hierarchy. For example, to access the valid property for the firstName FormControl, we would use customerForm, our form model, .controls to access the collection of controls on the form, .firstName to access the first name FormControl, and .valid to access the desired property of that control. Alternatively, we can take advantage of the FormGroup's get method to reference the FormControl. We would use customerForm, our form model, .get to call the get method, passing in firstName to find the reference to the first name control, and .valid to access the desired property of that control. This syntax is often shorter, especially when working with nested FormGroups. Watch the quotes here because often this syntax is within a quoted string, if the outside quoted string uses double quotes, then this argument should be in single quotes. A third option is to define a separate property in the component class for the input element. We then use this property when creating the FormGroup. In the template, we can then reference the FormControl with this property. This technique is most useful for controls referenced frequently in the template or in the code. Let's try out the FormGroup and FormControl name directives in our template. Let's start with a form element. We associate the form element with the root FormGroup, we do that using the formGroup directive to bind the form element to the FormGroup instance property we defined in the component class. We'll use square brackets to define property binding, and bind to our customerForm property. We can delete the template reference variable here and here, because our template can access the customerForm property directly. Recall that the Save button was also using the signupForm template reference variable. Here, we can replace the template reference variable with our customerForm property, our button is still disabled if our FormGroup is not valid, but now we reference the FormGroup instance we created instead of one that Angular had generated for us. And if we still want to see the values of our FormGroup, we can change these logging lines to use customerForm as well. Next, let's hook up our input elements starting with the first name. We'll remove the ngModel, we no longer need the name attribute, and we'll delete the template reference variable. Instead, we'll use the formControlName directive to bind this input element to the name of the associated FormControl, in this case firstName. We don't need square brackets here because we are binding to a simple string, not a property. We then replace the template reference variable here in the div that sets the validation error style. We can use any one of the three techniques we saw in the slides to reference the desired form model object. I'll use Search/Replace to quickly replace the template reference variables with the customerForm get method. This is looking a bit messy here with lots of logic that may be better in the component class. We'll refactor this code a little later in this course. For now I'll go off and repeat these changes for the last name, email address, and send catalog checkbox. If you are coding along, be sure to delete the ngModel directive, name attribute, and template reference variable from each input element, and add the form ControlName directive, then use Search/Replace to replace each reference to the template reference variable with a customerForm.get method. Note that the directive name and the FormControl string names are case sensitive. Are we ready to move on? We still have all of these other input elements here for the customer's street address, let's comment them out for now so we can get this part of the form working. Now we're ready to try it out in the browser. And here is our first Reactive form. If the form does not display for you, use the developer tools and view the messages in the console. On this form, each of the input elements display correctly, and our default for the Send catalog checkbox is set to true. The address input elements don't display here because we commented them out. Looking at the values below the form, the value of each state here is false. If we give focus to the First Name field, and then leave the field, we see the appropriate validation error message, and we see that Touched now is true. If we type in a character we see the minimum length error message, and Dirty is now true, and if we put in valid values for each field, Valid is true. The Value property displays the key and value pairs for each FormControl registered with the FormGroup. Notice that the keys are the names we defined for each FormControl instance, and the values are our entered inputs, so the form model works the same, it's just that now we're creating it ourselves. But, what if we want to change the value of an input element from our component class? Say we want to set defaults after the form model is initialized. We aren't using two-way binding anymore, so changing the data model won't change the form values. How do we update the input element values on our form from our component class code?
Using setValue and patchValue
To update input elements on the form from our component class, we use setValue or patchValue. Use setValue to set the value of every FormControl in the form model. In this example, we have three FormControls and we set the value of each one. If we only want to set a subset of the values, we use patchValue instead, here we only set two of the values. Let's give this a try. To try this out, we'll add a button to our demo form template to provide some test data on the form. We'll use event binding to bind to a populateTestData method. Next, we need to write that method in the component class. I'll paste the code and we can talk through it. In this method, we use setValue to update each of the values in the form model. Let's check it out in the browser. Looking at the form, we see our new Test Data button. Click it, and the form populates with the defined values, but what if we only wanted to populate the name fields? If we go back to the method and remove the email address, for example, then look back at the form in the browser, if we open the developer tools, then click Test Data, we see an error message in the console, Must supply a value for form control with name: email. That's because the setValue requires that we set the value of every FormControl in the form model. What do we do? If you said patchValue, you are correct. Let's change setValue here to patchValue, recheck the form in the browser, click Test Data, and it works. Use setValue when setting all of the FormControls on the form, use patchValue to assign values for a subset of the FormControls. We'll see a more real-world example of these features later in this course. Looking back at the code that created the form model, this code is a bit long, and will be even longer if we add all of the address fields. Wouldn't it be nice if there was an easier way to write this code block? And there is, let's check out the FormBuilder next.
Simplifying with FormBuilder
FormBuilder is a class we can use when building Reactive forms to create the form model from a configuration. We can think of it as a factory that creates FormGroups and FormControls for us. The FormBuilder shortens the boilerplate code required to create an instance of the root FormGroup and its associated FormControls and nested FormGroups. This can make the code that creates the form model easier to read and maintain. The FormBuilder is provided as a service, so we access the FormBuilder instance using dependency injection. Since the FormBuilder is provided as a service, we follow the same steps we do for every other Angular server we use. We import FormBuilder with code that looks like this. We inject the FormBuilder instance using the class constructor, like this. Lastly, we use that instance to create our FormGroup, notice that we no longer need to create new instances of FormGroup or FormControl here. We'll look at this syntax more in a moment, first let's try out FormBuilder. Here in the component class we import FormBuilder from angular/forms. Our class currently has no explicit constructor, so we need to add one, then we inject the FormBuilder instance using the constructor parameter. Now we can use that FormBuilder instance to create our FormGroup. Instead of creating a root FormGroup instance using the new keyword, we'll use the FormBuilder instance and call its group method. The group method allows us to define the set of controls and nested FormGroups that are associated with the root FormGroup. Instead of explicitly creating a new FormControl instance, we simply set the default value. We could set the default value to anything, we'll set the defaults to an empty string for all but the sendCatalog FormControl. The group method returns an initialized root FormGroup instance with all of its associated FormControls and nested FormGroups. Now we can delete our original code, and remove FormControl from the import statement, because we no longer use it as a data type. Does this look easier to read without all of the cruft? Let's bring the form up in the browser and see that it still works. Let's take a closer look at the FormBuilder's FormControl syntax. The FormBuilder group method takes in a control configuration object that defines the FormControls associated with the FormGroup. In this example, we define two FormControls, firstName and sendCatalog. Each control configuration object is comprised of a key and value pair, the key is the FormControl name, such as firstName here. The value can be an expression that defines the default value for the FormControl. For example, the last entry has a key of sendCatalog and a default value of true. Alternatively, the value can be an object with two properties, value and disabled. The value property is the default value, as before, and the disabled property is a Boolean, defining whether the input element should be disabled. We'll see that in the upcoming demo. Yet another option allows specifying an array, the first element of the array is the default value expression or the object with the value and disabled state. The next two elements of the array, not shown here, define the validation rules. We'll look at validation in the next module. Now let's try out an alternate syntax. To try out an alternate syntax, let's change the lastName value to be an object with a default value and a disabled state. Let's check that out in the browser. We see that the last name default appears, and the input element is disabled, it works. Notice also that the lastName no longer appears in the Value property for the root FormGroup. This could be a useful feature in a situation that needs this functionality, but we don't need it here, so let's change it back to an empty string default. Looking back at our template, notice that our validation is all still here. We could leave it here, but if we want all of the benefits of Reactive forms, we'll want to move it into our component code as well. We'll do that in the next module. For now, let's finish up this module with some checklists we can use as we build our own Reactive forms.
Checklists & Summary
Checklists are a great way to recheck our understanding and our work. When building forms using the Reactive approach, the component class is in charge. In that class we create a property for the root FormGroup. We use this property to bind the form element in the template, then we create the FormGroup instance and pass in each FormControl instance, like this, but we can simplify this code using the FormBuilder. To use the FormBuilder, we import it using an import statement. We inject the FormBuilder instance into the class constructor, and we use that instance like this. The group method returns an initialized root FormGroup instance with all of its associated form controls. Before we can use the Reactive forms directives in our template, we need to pull them into our Angular module. We start by importing the ReactiveFormsModule using the import statement, we then add the ReactiveFormsModule to the imports array property of the ngModule decorator, like this. This makes the Reactive forms directives available to all templates associated with components defined in the declarations array. Note that our demo application has only one feature component, so it is defined in AppModule. In a real application, your form may be declared in a feature module. In that case, the ReactiveFormsModule should be added to the imports array of that feature module. We'll see this when we look at the APM sample application later in this course. We then bind the components template to the form model. In the form element we use the FormGroup directive and property binding to bind to the root FormGroup property we defined in the component class, in this example, that property was called customerForm. In each input element we used the FormControlName directive to bind to the name of the associated FormControl. The name we specify here is the name we set when creating the FormControl, and remember that these strings are case sensitive. After performing each of these steps, we have a Reactive form managed by the form model we define in the component class. In this module, we converted our template-driven form into a basic Reactive form. It began in the component class, where we explicitly created instances of the form building blocks, FormGroup and FormControl. Next we modified the application's Angular module to add the Reactive forms module to the imports array, and pull in the Reactive forms directives. Then we updated the template elements to bind to the FormGroup and FormControls we defined in the component class. We tried out the setValue and patchValue to update the input element values from the component code. Lastly, we saw how to use the FormBuilder to simplify the boilerplate code and create the root FormGroup and its associated FormControls with less cruft. But there's a bit more work to do, let's look at validation next.
If only our users never made a mistake, never missed a required field, and always knew the constraints for each input element, we would not need validation, but I don't think we can make that assumption. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and in this module we look at form validation, the Reactive way. The template-driven technique for validating form data is to specify appropriate validation attributes in the HTML. These validation rules determine if the user's entry is right or wrong, but what if it depends? What if we want to adjust the validation logic based on the user or on the state of the application? For example, we could perform different validation for administrators giving them more flexibility than normal users. Or we could validate differently based on the user's selection. If the user selects notification by text, phone is required, if by email, the email address is required. With Reactive forms we have much more control and can handle validation rules that specify right, wrong, or it depends. In this module, we examine how to set built-in validation rules in the component class, such as required and max length. We look at how to adjust those rules at runtime, based on desired criteria. We build a custom validator for the ultimate in validation flexibility. We expand our custom validator by passing in parameters, that way we can make our custom validator more generalized and reusable, but sometimes it's not enough to validate a single input element. We'll cover cross-field validation, and see how to validate a set of input elements as a group. Let's get started.
Setting Built-in Validation Rules
We've seen how to use the FormBuilder to create the root FormGroup, by specifying a FormControl with a default value for each of our input elements. Let's examine how to specify the validation rules here as well. Recall from the last module that we have three choices of syntax when defining a FormControl. We define a key value pair, where the key is the FormControl name and the value is the default input element value, such as an empty string or true in this example, or we can provide a key value pair where the value is an object with a value and disabled state. Or we define the FormControl's key value pair, where the value is an array. The first element of the array is the default input element value, the array syntax is the one we use to set validation rules for the FormControl. We set the validation rules in the second element of the array. For built-in validators we use the Validators class, and specify the name of the desired validation rule. In this example, we set the required validator. To specify multiple validation rules for a FormControl, use an array. In this example, we set the required validator and the minLength validator, passing in a 3 to specify a minimum length of 3 characters. So the first element of the array is the default value, and the second element to the array is an array of validation rules. The third element to the array, which is not shown here, is for any asynchronous validators. An asynchronous validator, as its name suggests, is for asynchronous validation operations. The most common use of asynchronous validation is calling a server-side validation method. To minimize the asynchronous calls, asynchronous validators are not executed until all synchronous validators pass validation. Notice that we can mix and match these styles. The firstName control uses an array because it has a validator. The sendCatalog FormControl specifies a simple default value because it needs no validator, so we can use the appropriate FormControl syntax for the requirements of each FormControl. Now let's set up some built-in validators. Looking at the HTML we can see that the First Name input element has two validation attributes, required and a minlength of 3. Let's delete these validation rules from the HTML. Now let's add them to the component class. Angular forms provides a validators class that has the basic validators built in. We import Validators from angular/forms to access those basic validators. We want to add validators to our firstName FormControl here, so we change the single value to an array. The first element of the array is our default value, the second element is for our validators. We can specify a single validator or an array of validators. In this example, we need both required and minlength, so we'll define an array of validators. We set Validators.required to mark the input element as required, and Validators.minLength, passing in a 3, to require a minimum length of 3 characters. Let's check it out in the browser. Set focus into the First Name, click out, and we see our required validation message. Now let's repeat this process for each of our other input elements. In the HTML, the Last Name has required and maxlength, let's delete these here and add them to our FormControl here. We change this single value to an array, then add the validators, Validators.required, Validators.maxLength 50. In the HTML the email has required and pattern, let's copy our pattern before we delete it, and add them to to our FormControl here, passing in the desired pattern. Our sendCatalog binds to a checkbox, so no validation is required. Let's check out the browser and see if all of our validation still works. First Name, yes, Last Name, yep, Email, you betcha. Our validation is now in our class. That was pretty easy, but how do we change that validation based on some criteria, such as a user selection?
Adjusting Validation Rules at Runtime
One of the benefits of using the Reactive forms approach is the ability to easily adjust validation rules at runtime. For example, we want administrative or internal users to have fewer required fields, we want different validation when creating data versus an update, or we need the validation to change based on another selection on the form. In this example, if the user selects to receive notification by text, then the phone field is required, otherwise the phone is optional. We change the validation for any FormControl at runtime by calling the setValidators method on that FormControl instance, and pass in the new validator. We can pass in a single validator or an array of validators. Here we change the validation to required and a maxLength of 30. We can remove all validators from a control calling clearValidators, this comes in handy if we want to add validation rules under some circumstances, and remove those rules under other conditions. However, updating the validators doesn't cause the validation status of the control to be reevaluated, so when we change the validators, if we want the control validation to be reevaluated based on the new validation rules, we need to call updateValueAndValidity on that FormControl as well. Let's jump back to the demo and give this a try. Since we last saw the demo application, I've added several input elements in the HTML. The Marketing Department wanted us to add a phone number, so I did that here, and set the formControlName directive to phone. We'll add phone to our FormBuilder in a moment. And the Marketing Department wanted radio buttons to set up notifications. The user can select to get notified via email or text. Notice that their values are email and text here. I've set the formControlName directive for each of the radio buttons to the same formControlName called notification; that allows us to track the radio buttons as one value. Now I need to add the FormControls for these input elements in the component class. We add phone for the phone input element, and we add notification for the set of radio buttons. Let's set the default value for the notification to email, that means initially the phone number input element is optional, and we don't need to define validation rules here. Since the user must pick one of the radio buttons, we don't need validation for notification either, but what if the user selects notification by text? We need to change the phone number validation to required. How are we going to do that? How about trying out setValidators? The first question we need to ask ourselves is where should we put the code that sets the validators? Well, what do we want it to do exactly? When the user clicks the email radio button, the phone number becomes optional, when the user clicks the text radio button, the phone number becomes required. So let's add a method that will call when either radio button is clicked. We'll call it setNotification, it takes in a string defining which radio button was clicked, and it returns a void. In the HTML, we use event binding on the click event for each radio button to call this setNotification method, passing in the appropriate text. Going back to the component class, in this setNotification method we use setValidators to change the validation of the phone input element appropriately. I'll paste the code, and we can talk through it. We first need a reference to the phone FormControl, remember from the last module how we do that? One way is to use the root FormGroup's get method to find the phone's FormControl within the form model. If the notification is via text, then we add the required validator for the phone FormControl by calling the setValidators method. We can pass in a single validator or an array of validators. In this example we only need one, required, otherwise we clear the validators for the phone FormControl. There is one more line of code we need here, after setting or clearing the validators, we need to reevaluate the phone FormControls validation state, so we call updateValueAndValidity. Let's check it out in the browser. We'll fill out the required fields so everything is valid. Now we see the Save button as enabled, and Valid is true. If we click the Text radio button, we see the phone validation error message, and the Save button is disabled. If we check email, the phone is no longer required, the validation message goes away, and the Save button is enabled. We now have a validation rule that adjusts itself at runtime. We can use setValidators, clearValidators, and the all-important updateValueAndValidity anytime we need to change the validation on the fly. Next, let's look at defining custom validation rules.
Angular only provides basic validators, such as requiredMaxLength and pattern. Though we can do a lot with pattern, there are times we need custom validation rules. We define a custom validation rule with a custom validator. In its simplest form, a validator is a function. The validator function always takes one parameter, the FormControl or FormGroup being validated. To allow passing in either a FormControl or a FormGroup, we type the parameter using the abstract class, AbstractControl. This part of the syntax defines the type of value returned from this function. It looks a bit gnarly, so let's break it down. The validator function returns an object defining the broken rule, or null if it is valid. The returned object is comprised of a key and value pair, where the key is a string and the value is a Boolean. In the custom validator function we perform whatever logic we desire. If our logic determines that the validation rule passes, we return a null, if the validation rule fails, we return an object. The key is a string and defines the name of the broken validation rule. The value is set to true to indicate that the current entry has an error. The broken validation rule is then added to the passed in FormControl or FormGroup error collection. Let's build a custom validator. Someone in Marketing had this great idea to add a rate your experience element to the form. At some future point this will have a fancy user interface, such as selecting stars, but for now they want a simple text box where the user can enter 1 through 5. I've already added the requested rating input element here, and set the formControlName directive to rating. Let's add the FormControl for the rating to the form model, we'll use an empty string for the default value. We could add a pattern validator here to check a range of values, but to try out custom validation let's create our own numeric range validator. We can add our validator function above our component class. If this validator will only be used by this component, it makes sense to add it here, but if the validator could be used by other parts of the application, consider instead putting it in its own file. It can then more easily be reused by any component that needs it with a simple import statement. We'll see how to make a validator reusable later in this course. Let's call our validator function ratingRange. I'll paste the code, and we can walk through it. As discussed on the slides, a validator function always takes one parameter, the FormControl or FormGroup being validated. To allow either a FormControl or FormGroup, we specify AbstractControl here, and we need to import it from angular/forms. Next we define the return type of this function. A validator function always returns a key and value pair defining the broken validation rule, or a null if it is valid. We define our custom validation logic in the body of the validator function. For this specific example, we check if the control has a value that is not defined, is not a number, is less than 1, or greater than 5. If so we return the key and value pair specifying the name of the validation rule, we'll call it range, and true to indicate that the validation rule was broken. The validation rule name is then added to the errors collection for the passed in FormControl. If the control is valid, we return null. To set our new validator, we simple add it to the FormControl just like the built-in validators. We change the rating FormControl from a single value to an array, and pass the name of the validator function, ratingRange in our example, as the second parameter. Notice that we set the validation rule name here as range. In the HTML, if we want to display an error message for this particular validator we use the validation rule name, I've already done that here. Now let's see how it works in the browser. Here's our new Rating field, let's put in a 7, and we see our validation message. Change it to 4, and all is well. Adding custom validation is as easy as writing a function, the only odd part is ensuring the appropriate return value. Null if the FormControl is valid, a key and value pair if it is invalid, where the key is the name of the validation rule, and the value is true to add it to the list of validation errors. But notice that we hardcoded in the range here, what if we wanted to make this more generalized to work with a provided range of numbers?
Custom Validation with Parameters
The validator function we wrote works well for any simple validation, but what if we need parameters? For example, we want to limit entry to a provided range of values, we can't just add more parameters here. To define a custom validator with parameters, we need a more complex function that returns a validator function. Wait, what? Let's talk through this code. Because a validator function can only take one parameter, the AbstractControl to validate, we can't simply pass more parameters to this function, instead we build a factory function that returns the validator function. Here we have our factory function, we can specify any number and type of parameters here. This factor function returns a validator function, so we define ValidatorFn as the return type, and return our validator function. This is the same validator function that we saw on the prior slide, but instead of declaring and naming this function, we simple return it using the arrow function syntax. Let's give this a try. We want to change our ratingRange validator to take in a minimum acceptable value and a maximum acceptable value, so this validator can be used for ranges other than one that is hardcoded in. Currently we hardcoded in a range between 1 and 5. As we saw in the slide, we can't simply pass more parameters to this validator function. Rather, we need to wrap this function in a factory function, like this. The function takes in the minimum acceptable value, and the maximum acceptable value, and returns a validator function. Oh, and we need to add ValidatorFn to the import statement, then we return our validator function by changing it to an arrow function, we add the return statement, we remove function and the function name, and we add an arrow here to define the returned validator function. Next, we modify the logic of the validator function to use the passed in parameters. We'll change the 1 here to min, and the 5 to max. Now we can change the FormControl validation to pass in the minimum and maximum values. Does it still work? Try a 7, error, 5, it's good, 0, another error, 2, it's good, it works. Modifying a validator function to accept parameters requires wrapping the validator function in a factory function. That factory function takes in any desired parameters, and returns the validator function. Then we modify the validator function to use those provided parameters, but what if we need to validate across multiple FormControls?
Cross-Field Validation: Nested FormGroup
Sometimes, simple FormControl validation is not enough, we need to compare across two or more form controls. A common example of this is to compare a start date and end date to ensure the start date is before the end date, or to confirm entry of a password or email address. The trick to cross-field validation is to define a nested FormGroup for the FormControls that are validated together. We define them as a FormGroup both in the component and in the HTML. In the component class, we define a nested group within the forms root FormGroup. In this example, we name this nested group availability. To create a nested group we use the same syntax as when we create the forms root FormGroup. We call the group method of the FormBuilder, any FormControl to be validated as part of the group is then defined within this nested FormGroup. In our example, we have two FormControls, but there could be any number of FormControls, or more nested FormGroups defined in this group. We set each FormControls default value and validation as we have throughout this module. In the HTML we define a container element, such as this div element, to enclose the input elements associated with the FormGroup, and then use the formGroupName directive to associate the container element with our nested FormGroup. The input elements within the container use the formControlName directive assigned to the name of the FormControl within the nested FormGroup. Once we have the grouping in place, we can perform the cross-field validation, but let's take it one step at a time, and add the nested FormGroup first. The Marketing Department has gotten too many returned emails, and believe it is because the user has mistyped their email address in the form, so they've requested that we add an email confirmation text box. This may not be the greatest idea, but it provides a simple example of cross-field validation. I've already added the confirm email input element, it looks similar to the email input element. Here the formControlName directive is set to confirmEmail. Now let's add the FormControl for this Confirm Email input element to the form model in our component class. We add confirmEmail, we don't need a default value, and we specify the required validator. We could also specify the pattern validator here, but if we are comparing it against the email FormControl, which already has the pattern validator, we don't really need it here. Recall from the slides that the first step to making the cross-field validation is to create a nested FormGroup for the form controls to validate as a group. Let's name that group emailGroup, and create the nested group by calling the group method of the FormBuilder. We can then copy our two email form controls into that nested form group and reformat. The resulting form model has a root FormGroup here and the nested FormGroup here. Now let's group the HTML as well. First we define an element that surrounds the input elements to validate as a group. We'll specify a div element and reformat. Note that the form-group here is just a Bootstrap style class, and does not affect our form model. We then use the formGroupName directive and set it to the name of our FormGroup, which is emailGroup. Now that the FormControls are within a nested FormGroup, we need to change each reference to the FormControl. We could use customerForm.controls.emailGroup.controls.email to drill down from the form to the FormGroup to the control, but here is where the FormGroup's get method can simplify our syntax. We make this change everywhere we are referring to this control; we make a similar change for the confirmEmail. Let's view the result in the browser. Here's our new Confirm Email input element, our required field validation works for the Email and for the Confirm Email. Notice I've misspelled the second email address, and we're not getting an error message on the cross-field validation. That's because we haven't written it yet. Notice that our Value property here at the bottom now shows our nested group. Great, let's move on to building the cross-field validator.
Cross-field Validation: Custom Validator
Now that we've defined our set of FormControls as a FormGroup, we could build a validator function similar to the one we built earlier in this module. If the validation does not require parameters, we can build a simple validator function, otherwise we can build a factory function that returns a validator function. In either case, we are validating the nested FormGroup, so that FormGroup is passed in to this method. To validate the contents of the multiple FormControls, we first access the FormControls from the passed in FormGroup using the get method. In this example, we get the FormControl named start, and the FormControl named end. We then compare their values as needed, and return null if the validation rules passes, or a validation error object if the validation fails. Then we add the validator to the FormGroup. Note that we can't simply add the validator function here, the FormGroup signature requires that we provide an object with a validator key and the function as the value. Let's see cross-field validation in action. We'll create the validator function here, I'll just paste it in, and we can talk through it. The function is called emailMatcher. We don't need any extra parameters, so we just use the simple validator function. The parameter here will be a passed in FormGroup. We call the get method of the FormGroup to find both the email and confirmEmail FormControls. We then use these references to check the value of the email FormControl against the value of the confirmEmail FormControl. If the values match, we return null. If the values don't match, we return a key and value pair, where the key is the name of the validation rule and the value is true to add this rule to the errors collection for the FormGroup. Let me say that again. This adds the broken validation rule name to the errors collection for the FormGroup, not the individual FormControls. And because we don't want the validation to return an error until the user has touched the fields, let's add one more condition to our validator function. If either the email FormControl or the confirmEmail FormControl has not yet been touched, return null, and skip the validation. Next, we add this validator to the nested FormGroup as the second argument. Recall from the slides that we can't simply add the validator function here, instead we pass an object with a validator property. To let the user know that there's a problem, let's add a style class to the group in the HTML. Here we set the style using the Bootstrap has-error class, if the emailGroup has any errors defined in its errors collection. Let's check it out in the browser. We type in an email address, and the Confirm Email does not yet turn red. As soon as we type anything into the confirmation element, both fields turn red because they don't match. When we type in a matching email address, the has-error style is removed, and the elements are no longer red. But just turning elements red is not very user friendly, let's add a validation message for the user. Let's put the validation message on the Confirm Email element. Our first thought might be to add something like this. The problem is, however, that the FormControl doesn't get a match entry in its errors collection, rather the match error appears in the errors collection for the FormGroup. We instead need to change this to check the emailGroup's errors collection. If we tried it at this point, it still wouldn't display the message because of this ngIf block here. This ngIf checks the confirmEmail FormControl and its errors collection. We need to add another or expression, and add another parenthesis here. Now these span elements are displayed if either the confirmEmail or the emailGroup errors collection has a value. This leads us to another problem. Because we are now displaying these spans when the nested FormGroup has an error, not necessarily when the Confirm Email has an error, we need to add the safe navigation operator here and here. This prevents the cannot read property required of null type of error. As you can see, the checks here for displaying the error messages are getting even more messy. We'll refactor this code, pushing much of this logic into the component class later in this course. For now, let's check this out in the browser. We'll put in an email address and start entering the confirmation email, we see the message that they don't match. As soon as it does match our error message disappears, it works, yeah. To implement cross-field validation, we create a nested FormGroup, and add the FormControls to be validated to that nested group. We then set the formGroupName directive on an HTML element that encapsulates the associated input elements. We wrote the validator function, and specified that validator when creating the nested FormGroup. Let's finish this module with some checklists we can use as we add validation to our applications.
Checklists and Summary
When setting any of the built-in validation rules for FormControl, start by adding an import statement for validators, then pass the validator or array of validators as the second element in the value for the FormControl. In this example, we pass a single validator for the firstName, and an array of validators for the lastName. To adjust the validation rules at runtime based on some criteria, first determine when to make the change. If it is based on user action, tie it to that user action. Next, we call the FormControl's setValidators method, and pass in the validator, or an array of validators, or we can clear all validators with clearValidators, like this. And always remember to call the FormControl's updateValueAndValidity. Calling setValidators or clearValidators does not trigger any update or value changed events, so the validation is not reevaluated. Call updateValueAndValidity to trigger the validation. Any time we need specialized rules for validating a FormControl, we build a custom validator. A custom validator is simply a function that takes in the control to validate and returns a null if the control is valid, or a validation error object, like this. The validation error object is a key and value pair, where the key is the name of the validation rule, and the value is true. This key is used as the name of the broken validation rule when it appears in the errors collection for the FormControl. We then use our custom validator just like any other validator, like so. Here the firstName FormControl is validated with our custom validator. It is important to note that we can also use any custom validators with the template-driven forms approach. We just need to expose our custom validator function as a directive, then the directive can be applied to the HTML elements, just like the built-in attributes such as required and max length. If we need to pass parameters to our custom validator function, we wrap it in a factory function, like this. We specify the desired parameters here, and return a validator function using the arrow function syntax. We then use the custom validator just like any other validator that has parameters, like this. When we need to validate across two or more FormControls, we group the controls to validate in a nested FormGroup. We start by defining a nested FormGroup, and adding the FormControls to validate to that group, as so. Even though we are showing two FormControls here, any number of FormControls or other nested FormGroups could belong to this nested FormControl. We then update the HTML. We define a container element around the controls to validate. We use the formGroupName directive and assign it to the nested formGroupName. Once we have the FormControls to validate within a nested FormGroup, we build the custom validator. We can build a validator function without parameters as shown here, or with parameters using a factory function, as shown previously. Either way, we then use the validator like this. When adding validators to a FormGroup, we provide it as an object with a key and value pair. The key is validator and the value is the validator function. In this module, we focused on validation. We began by working with the built-in validators, and saw how to define one or more validators for each FormControl. Next, we looked at how to adjust the validation rules at runtime based on desired criteria. We then examined how to perform custom validation by building a validator function. We created a function to validate a numeric value to ensure it fell within a hardcoded range of values. We expanded on our custom validator by adding parameters, this allows any FormControl that uses our custom validator to define the desired range of values. Lastly, we saw how to validate several fields as a group. Next up, let's see how to listen for changes on the form and react accordingly.
Reacting to Changes
With Reactive forms we can adjust how a form works reactively, based on user entries and selections. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and in this module we explore how to watch for user changes, and react to those changes dynamically controlling the form. What if we could watch every change that the user makes, every character, every keystroke? Yeah that might sound a bit creepy, but in Reactive forms it's a good thing, really. By watching the user's changes we can modify the display, the validation, or the messages, providing a much more dynamic and personal experience. In this module, we learn how to watch for user changes, we look at ways we can react to those changes, and we examine reactive transformations to improve how we watch those changes. Let's get started.
The cool thing about Reactive forms is that we can easily watch for any changes happening to a FormControl or a FormGroup, and react to those changes in real time. Both FormControls and FormGroups have a valueChanges property, that allows us to watch for changes. The valueChanges property emits an event every time the value of a control changes, either in the user interface or programmatically. The valueChanges property is an observable of any. We can think of an observable as a collection of events that arrive asynchronously over time. The generic parameter is any, so the event in the collection can be of any type. We subscribe to the observable to watch the events. Similarly, there's a statusChanges property that emits events on changes to the validation state, but often the statusChanges is less useful because we want finer control. For example, if an input element is invalid, we don't want to display validation messages if the user has not yet touched or modified an input element, so we need more information than just a change in validation state. That's why we'll use valueChanges instead. To watch the events for a single FormControl, we subscribe to its valueChanges observable. Each time the control changes, an event is emitted, the FormControls current value is provided, and the associated code is executed. In this example, we simply log the FormControls value. We can also watch for events from any control within a nested FormGroup. By subscribing to the FormGroups valueChanges observable, each time any of the FormControls or other nested FormGroups within this FormGroup are changed, an event is emitted, the FormGroups value is provided, and the associated code is executed. Note that in this case, the value is the set of key and value pairs for all of the controls and nested FormGroups within this FormGroup. So here, we use the JSON.stringify to display the value. We can also watch for any change over the entire form by subscribing to the valueChanges property for the form's root FormGroup. This allows us to watch for any changes and any control on the form. Let's try this out. We only want to watch FormControls or FormGroups if we plan to do something when we see something. In our case, we want to do something whenever the user changes the Notification radio buttons. Currently, in the HTML, we bind to the click event of both notification radio buttons, and call a method in our component class. When the user clicks the text button, we add a required validation rule to the phone FormControl. When the user clicks the email button, we remove the validation rule from the phone FormControl. Let's implement this same feature by watching for changes instead of relying on event binding in the HTML. We'll begin by watching the send notification FormControl. We want to start watching as soon as the component is initialized, so we'll put the code in the ngOnInit method. Recall how we get the FormControl from the FormGroup? We'll use this.customerForm.get, and the name of the FormControl, which in this case is notification. We access the valueChanges property and call the subscribe on that observable to start watching for changes. We'll get an event every time either radio button changes. When a change occurs we get the value of the notification FormControl, for now we'll just log the value to the console. Note that this code must be after the definition of the root FormGroup, otherwise this reference is null. And this line is a little bit long, so let's break it up. Let's try it out in the browser. First, let's open the developer tools to view the console. Click Text and we see text logged to the console. Click Email, and email is logged to the console. The code is notified every time we change these radio buttons. We can subscribe to valueChanges on a FormControl as we've done here, or on any FormGroup, including the root FormGroup. Now that we know how to watch for changes, how can we react to those changes?
Reacting: Adjusting Validation Rules
As the user makes changes to the form, we can react to those changes to provide a more dynamic and customized experience. We can adjust the validation rules. We can handle validation messages in the component class instead of hardcoded in the template. We can adjust the user interface elements, adding or removing content as needed. We can provide automatic suggestions as the user types. The possibilities are limited only by our imaginations, and of course customer requirements. Let's see what we can do. Currently, our HTML binds to click events to notify us when the user clicks a specific radio button. We then have a setNotifications method that adjusts the validation based on the selected option. Wouldn't it be nice if we could get rid of this event binding and just watch for changes using code in the component class? Let's remove the click event handler from the HTML for both radio buttons here. In the component, we already set up a watcher on the notification FormControl that is watching our radio buttons. Instead of logging to the console, in the callback function for this watcher we can call the setNotification method, and pass in the value. That's all we need to do, let's see if our code still works. Click on Text, and the phone number validation kicks in. Click on Email, and the phone number validation turns off. Yep, it works. By using a watcher to change the validation, we no longer rely on the HTML to notify us of changes to the input element. Let's see what else we can do.
Reacting: Displaying Validation Messages
Observables provide operators that allow us to transform how we see the emitted events. There are many observable operators that do everything from filtering, to mapping, to throttling, to solving world peace. Well, maybe not that last one. One such operator is debounceTime. DebounceTime ignores all events until a specified time has passed without another event. For example, debounceTime(1000) waits for 1 second with no events before emitting another event. This is very useful for validation, especially if you don't want to show the validation messages until the user has stopped typing. Let's look at this with a marble diagram. A marble diagram shows events as marbles on a timeline, and is useful for visualizing observables. Without debounceTime when the user starts typing we get valueChanges events for every change they make. Since we are setting validation on the valueChanges event, the user sees a validation message as soon as they type the first character, we don't give them a chance to enter a correct value. We continue to display the validation error messages until the user has typed enough to match the pattern. If we use the debounceTime Reactive transformation, we don't receive events until the user has stopped typing the defined amount of time. That gives the user a chance to enter a correct value before we start displaying validation error messages. In this example, the user types ab@c and pauses. When the specified amount of time passes since the last character was typed, we receive the valueChanges event and perform the validation. At this point the value is valid and no validation message is displayed. If the user then types more characters and pauses, when the specified amount of time again passes, we receive the valueChanges event, and again perform the validation. DebounceTime is one of the most commonly used reactive transformation operators when working with validation, but there are many others. ThrottleTime emits a value, then ignores subsequent values for a specified amount of time. This is useful when you receive way too many events, as when tracking mouse movements. DistinctUntilChanged suppresses duplicate consecutive items. This is useful when tracking key events to prevent getting events when only the Ctrl or Shift keys are changed. And there are many more, you can see the list of them here. Now let's see a Reactive transformation in action with a demo. We are back, and looking at the component class. The first step is to import the debounceTime Reactive extension's operator. Next, we call the debounceTime operator on the observable here, and specify the desired wait time. We'll wait 1 second, which is 1000 ms. That's it. Now our email validation should not display until the user has had a chance to enter a value. Let's see it in action in the browser. Recall previously that when we entered a character, we'd see a validation message asking us to enter a properly formatted email address. Now we have a chance to type in a valid email address before the validation kicks in. I'll reset the form, and type without stopping. Notice that no validation messages appear. Let's try it again, but this time I'll pause, and the validation message appears. Such a little change, and such a big difference for our user's satisfaction. Let's finish up this module with some checklists we can use when we do our own watching and reacting.
Checklists and Summary
To watch for changes to a FormControl or FormGroup, use the valueChanges observable property, and subscribe to the observable by calling the subscribe method. This provides notifications each time the value of myFormControl changes. To react as the user makes changes to a FormControl or FormGroup, we simply write code in the subscribe function. Code in the subscribe function can change validation rules. In the demo, we watched the send notifications FormControl, and added required validation on the phone number when the user selected to be notified via text. Code in the subscribe function can handle validation messages. We saw how to move the validation messages and control the error message styling from the component class instead of hardcoded in the HTML. Or we can write code in the subscribe function to adjust user interface elements, provide automatic suggestions, and so much more. To use Reactive extensions such as debounceTime, we first use the import statement to add the operator, then we use the operator by chaining it on the observable. We can chain any number of operators here. In the demo, we saw how to use debounceTime to give the user a chance to enter a valid email address before displaying validation messages. In this module, we examined how to watch for changes, and react to perform tasks such as modifying validation rules and managing validation messages. We also saw how to leverage Reactive transformations, such as debounceTime, to transform the events we are watching. At this point, we have successfully moved much of the logic we had in our template for the email input element into our component class. On the left is the original HTML for the one input element using the template-driven technique. It's long and has lots of styling and validation logic. On the right is the HTML as we have it now. It is the same input element using the Reactive forms technique. Notice that we have no complex logic here deciding when to display the has-error style class, no two-way binding, no validation attributes, no hardcoded validation error messages, or complex logic deciding when to display those messages. We simply set the formControlName directive and use the property containing the validation messages to display. Which HTML would you rather build, maintain, and test? Up next, let's look at how to dynamically add input elements to our form.
Dynamically Duplicate Input Elements
A user often wants to order multiple items on an order form, or enter multiple email, or postal addresses on a profile form, or specify multiple expenses on an expense form. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and in this module we learn how to dynamically duplicate input elements on a form. Sometimes a single entry is not enough, the user may need to enter multiple values, such as multiple email addresses, or in this example, multiple address blocks. Hmm, how do we duplicate these input elements using a Reactive forms approach? In this module, we outline the steps involved with dynamically duplicating input elements. Then we walk through how to perform each step. Along the way we learn about FormArrays and how to use them. The result is a form that duplicates form elements for multiple user entries. Let's get started.
Let's outline the steps here, and then work through each step. To dynamically duplicate input elements, we first define the input element, or set of input elements that we want to duplicate. If we allow multiple email addresses, then duplicating one input element may be enough, but if we provide for multiple address blocks, there will be a set of input elements to duplicate, one for each part of the address. To keep it simple, we'll want to duplicate just one thing, if there is one input element, we'll duplicate its FormControl. If there are multiple input elements, we'll put their FormControls in a nested FormGroup, so we can use that group as the one thing to duplicate. Then we refactor the FormBuilder code to create the FormControl, or a nested FormGroup instance within a method of the component class. Next, we create a FormArray to retain those multiple copies. We have not yet talked about FormArrays, we'll cover them in detail in this module. In the HTML, we loop through the FormArray and display the appropriate input elements for each FormControl or FormGroup in the FormArray. All of these steps are preparation for this final step, to actually duplicate the input elements. That's our process, let's jump right in to the first step.
Define the Input Element(s) to Duplicate
The first step on our path to dynamically duplicating form elements is to define which element or elements we wish to duplicate. On a form such as this, the Marketing Department may want to collect multiple email addresses, if so we could duplicate this single email input element, but in our case, the Marketing Department requests multiple postal addresses so the user could requests catalogs at their home, work, and vacation home, because who wouldn't want that? To accomplish this requirement we'll duplicate this entire address block. Note that our user interface uses the US address block format, feel free to change it as appropriate for your use. Let's set up our demo accordingly. Since the last time we saw the demo application, I've uncommented the address block input elements we commented out earlier in this course. I've removed the template-driven form syntax, such as the ngModel directives, and I replaced the name attributes with the formControlName directives. I also changed the ngIf here to use the customerForm's get method to find the value of the send catalog checkbox. Be sure to specify value here. If the value is true, the address block is displayed. In the component class, we add FormControls for each address block input element to our form model using the FormBuilder. We'll start with the address type radio button, we'll call it addressType, and set its default value to home. Then we'll add the street address 1. Feel free to add validation if desired, we won't here just to keep things simple for this demo. Next is street address 2, then city, state, and zip code. Be sure the names here exactly match the names used in the formControlName directives in the HTML, they are case sensitive. Now let's view it in the browser. By default Send me your catalog is checked so the address block appears. If you don't see the address block, check the developer tools for any syntax errors. Be sure that you've removed every ngModel directive, changed the name to formControlName, and set the ngIf syntax appropriately. If we uncheck the checkbox, the address block disappears, so it's working. Since we need to duplicate this entire address block, let's define a FormGroup to encapsulate these address block FormControls, we'll do that next.
Define a FormGroup
Our requirement for the demo form is to allow the user to enter multiple postal addresses. To meet this requirement we defined the address block as the set of input elements we want to duplicate, and we've added the FormControls for these input elements to our form model using FormBuilder. To make things easier, we'll put the set of FormControls we wish to duplicate into a nested FormGroup. That is our next step. As we've seen before in this course, a FormGroup is simply a set of form building blocks that we can work with as a group. A FormGroup can contain any number of FormControls and nested FormGroups. There are several benefits of defining a FormGroup. We can match the value of the form model to a data model that contains hierarchal data. We can easily check the touched, dirty, and validation state of a group of elements. We can watch for changes to a group of elements and react as needed. We can perform cross-field validation, comparing the values of the elements in the group. And, we can dynamically duplicate the elements in the group. Now let's define a nested FormGroup for the address block, so we can duplicate its elements. We'll start in the component class. Let's modify the FormBuilder to encapsulate the address block FormControls into a FormGroup. We'll call the FormGroup addresses and set its value by calling this.fb.group, we then copy in the address block FormControls, and we're done. The address block is now in its own nested FormGroup. That takes care of the form model, next we update the HTML to put a div element around the associated input elements. Let's collapse the address block input elements to make this a bit easier. We open this new div below the div with the ngIf, and close it after the zip code, then reformat. We add the formGroupName directive to the div and set it to the name of the nested FormGroup. We should be able to run as before, and all is well. If we scroll down, we now see the nested addresses FormGroup in the Value property here. We are ready to move on to the next step, and refactor so we can make multiple instances of our FormGroup.
We now have a FormGroup that encapsulates the input elements we want to duplicate. Next, we need to refactor the FormBuilder code to create the instance of the FormGroup within a method of the component class. To refactor, we define a function and return the address block's FormGroup from this function. Every time we call this function we'll get another instance of the address block's FormGroup, then in the FormBuilder code we simply call this function, this makes our initial instance of the address block. Let's give this a try. In the component class, we add a method. We can name it anything, such as buildAddress, its return type is a FormGroup. In this method, we return the FormGroup for our address block, we can copy it from here, and paste it here. We call this method anytime we want to create an instance of the FormGroup, then we replace the FormBuilder code with a call to the buildAddresses method. By calling buildAddresses here, we create the first instance of our address block FormGroup, and add it to our form model. When we view the application in the browser, it should work as before, and it does. We are now ready to put our FormGroup into a FormArray.
Create a FormArray
In the last step, we refactored our code to create an instance of our address block FormGroup in a method. We can call that method to create multiple instances of that FormGroup, but we need somewhere to hold these multiple instances. That's the purpose of a FormArray. A FormArray is simply a group of FormControls or FormGroups that are conceptualized as an array. Unlike a FormGroup, they are accessed by index instead of by name. A FormArray can contain any number of FormControls or FormGroups, but they are often used for like items, such as multiple instances of the same FormControl or FormGroup. Because each item in a FormArray does not need a unique name, they are great for sets that are dynamic, or of unknown length. It's a perfect choice for storing the instances of our FormGroup. Just like the other Reactive forms building blocks, there are two basic ways to create a FormArray. We can use the new keyword and create a new instance of a FormArray, passing in an array of FormControls and FormGroups. Or we can use the FormBuilder's array method and pass in an array of FormControls and FormGroups. Both ways work. Since we are already using the FormBuilder to define our FormControls and FormGroups, we'll use FormBuilders for our FormArray as well. Let's try it out. We start by adding FormArray to the import statement, so we can use FormArray as a type. Let's look again at the definition of our form model. Notice here that we are creating one instance of our address FormGroup. If we are going to allow the user to enter multiple addresses, we need to define a FormArray here instead, to hold them all. We use the FormBuilder array method, just as we saw in the slide. And for our particular demo form, we want the array to initially contain one instance of our address block FormGroup, so we call the buildAddresses method here to create the first instance of our FormGroup, and assign it as the first element of the array. So now our first address block FormGroup is an element 0 of the FormArray. To make it easier to access this FormArray, let's create a property for it in the component code. To ensure none of the code accidentally modifies this FormArray, let's define it as a getter instead of a normal property. I'll paste the code, and then we can walk through it. This is the standard syntax for a property getter, since it is returning the addresses we call it addresses as well. We define the property as a function, this getter returns a FormArray. We use the customerForm.get method to get the reference to the FormArray and return it. Notice that we use a cast operator here to cast it to the desired type, otherwise the type is an AbstractControl. Before we can try this out, we need to modify the HTML, we need to let it know that our FormGroup is now in a FormArray. We do this by adding another div element around our FormGroup, then we'll reformat for clarity. In this div element we use the formArrayName directive and assign it to our FormArray's name, which is addresses. Looking back at our form model, we no longer have a FormGroup named addresses, rather our FormGroup is in position 0 of the FormArray. So in the HTML, we change the FormGroup name to 0, odd looking I know, this will look better in a few moments, but we want to get the code her runnable so we can try it out. When we check it out in the browser, it still runs. And notice the Value shown below the form, the addresses are now an array as indicated by the square brackets. The array has one array element, our FormGroup. Next up, let's loop through that FormArray to create copies of the input elements in the HTML.
Loop Through the FormArray
We now have our FormArray set up and are definitely getting closer to duplicating our input elements. Our next step is to loop through the FormArray in our HTML, that way if we have multiple instances of our FormGroup in the FormArray, we'll display each of them in the HTML. As you may have guessed, looping in the HTML requires use of the ngFor directive. Here, we look through each address block FormGroup within our addresses FormArray. The extra code here is to gain access to the indexer, we use that indexer as the name of the FormGroup within the FormArray. Let's try this out. We want to repeat our entire address block for each FormGroup instance in the FormArray, so we add the ngFor to the FormArray div element, using the syntax shown on the slide. This will repeat all of the form elements here. Now we can change our hardcoded 0 to the variable i, and since this is bound to a variable we need to surround the directive with square brackets. Each FormGroup in the HTML now has a unique name that is the index of the FormGroup in the FormArray. There's one more thing to consider here. Now that we will have multiple copies of these input elements, we need to ensure that each label is associated with the correct input element. To do that, we can use the i variable in the id attribute of the input element. I'll change it for the first street address here. Then we need to change the labels for attribute, however we can't bind to the for attribute directly because it has no associated dom property. Instead we use attribute binding, like this, and we change the id to match the id here. By using the index number in the id and in the label's for attribute, when we have multiple address blocks a screen reader will associate the correct label with the correct copy of the input box. When we view the results in the browser, the form and the Value look the same. That's because even though we are looping we still only have one FormGroup instance in the FormArray. Next, let's add what we need to actually duplicate our input elements.
Duplicate the Input Elements
We're almost there. We now have all of the support pieces in place, and we are ready to create more instances of our FormGroup, and duplicate its associated input elements. This step requires that we add a method that creates another instance of the FormGroup or FormControl, and pushes it onto the FormArray. And we need some way for the user to request a copy of the input elements, so we add a button in the HTML. We use event binding to bind the new element to our new method. When the user clicks the button, the method is called, adding another FormGroup instance to the FormArray. Let's give this a try. We'll start by creating the method we need. We call it addAddress, which returns void. Recall that we defined a getter that returned the reference to our FormArray called addresses. In this method we simply push a new instance of the FormGroup onto our addresses FormArray. We call this method anytime we want to create another instance of the FormGroup and add it to our FormArray. Next, we add a button to the HTML. We'll put it inside the ngIf so it does not appear unless the user checked Send me your catalog, but outside of the FormArray so it is not duplicated. This button binds the click event to our new addAddress method. I've added a binding to the disabled property as well, so that the user cannot add another address if any prior address is invalid, but we don't currently have any validation in the address FormControls, feel free to add some as you wish. Let's try this out in the browser. As always we see one address block, we can fill it out, and click Add Another Address, and we dynamically duplicate our address block. We can fill that one in and click the button again. We can continue to add everyone on our shopping list. Notice that if we click in the label of Street Address 1, focus is set to the correct copy of the input box. Our bound input element IDs are working as expected. Clicking on the label does not work for the other controls. Notice it put the focus up here. This is because we have not yet changed them to use the index as part of the ID. And look at our Value property, our FormArray has an entry for each address FormGroup, we can access those elements and their properties as needed. To see how that's done, let's add another binding at the bottom of the form. This displays the street address of the first address FormGroup. We use our addresses property getter and call its get method, passing in the index of the desired FormGroup along with the name of the FormControl within that FormGroup. We add the safe navigation operator in case that element is not found. Going back to the browser, if we type in to the Street 1 input element, we see its value here at the bottom. We have successfully duplicated our address FormGroup as many times as the user requests, yay. Let's finish up this module with a checklist.
Checklists and Summary
For this module, what better checklist than our steps. We first define the input element or set of input elements that we want to duplicate. In our example, it was an entire address block. We then define a FormGroup containing the FormControls that we want to duplicate, in our case our address FormControls. Then we refactor the FormBuilder code to create the instance of the FormControl or nested FormGroup within a method. Next, we create a FormArray to retain those multiple instances. In the HTML we loop through the FormArray, and display copies of the input elements for each FormControl or FormGroup in the FormArray. Lastly, we provide a way for the user to specify when to make a copy of the input elements. In our example, we added a button the user could click to add another address block. When the user clicks the button, a method in the component class creates another instance of the FormControl or nested FormGroup, and adds it to the FormArray. The ngFor and the HTML makes a copy of the address block for each FormArray entry, effectively dynamically duplicating the input elements. In this module, we provided a way for the user to dynamically duplicate input elements. We first outlined the set of steps required to achieve this task, and then walked through each step. Along the way we learned about FormArrays and how to leverage them to manage multiple instances of a FormGroup or FormControl. Our demo form now demonstrates building a form the Reactive way, and includes features such as displaying validation messages from the component class, cross-field validation, watching the notifications element and reacting by changing the validation rules, performing custom validation for the rating range, and dynamically duplicating input elements. Next up, let's put away our demo form and look at a Reactive form within a more full-featured application.
Reactive Form in Context
Now's the time to shift gears a bit, and see how a Reactive form fits into a larger scale application. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and in this module we look at how an edit form works within the context of a more full-featured Angular application. Rarely is a form the entirety of an application, rather it is a piece of a larger puzzle. In addition to understanding the basics of building a Reactive form, to fit the form piece into the context of a larger application, we need to consider how the user will navigate to the form, how to set up validation for reuse, and how to populate data into, and save data from the form. In this module, we concentrate on additional concepts and requirements when building a Reactive form within the context of a full-featured application. We start with an overview of the sample application we'll use in these next two modules. Then we focus on the code for routing to the form, reading the route parameter passed to the form so we know which item the user selected to edit, setting up a deactivate routing guard to remind the user to save before navigating away, and refactoring custom validation into a separate class for reusability. We'll look at retrieving and saving data from the form in the next module. Let's get started.
In the next two modules, we'll examine the code for a Reactive form in the context of a more full-featured application, called APM for Acme Product Management. It manages a list of products that Acme sells. The completed code for this sample application is available in my GitHub repository, here. If you want to follow along with your own copy of this code, clone the repo, or download it as a zip file and unzip it, then in the APM folder, use npm install to install the required packages. I've opened the APM folder in VS Code. Let's start by running the sample application using npm start. This is Acme Product Management. I walk through building the basic features of this sample application in the Angular 2: Getting Started course here on Pluralsight. If you've taken that course, this application will look familiar. I've added a Reactive form to this sample application so we can see an edit form in context. We are looking at the Welcome page. Click Product List to review the list of products. Notice the product rating stars. Using stars instead of a number makes it easier for the user to visualize the rating. We can use the Filter by box to filter the list of products, or clear the filter to view all products. Notice that each row has an Edit button. Click the product name to view the product details. Click the Edit button to display the product edit form. Notice that the form populates with the existing product data, yay. From here the user can edit any of this data, validation messages appear as needed. If we remove the product name, we see a validation message. If we put in a star rating out of the valid range, we see a validation message. Because of our validation errors the Save button is currently disabled. When we fix the errors, the errors disappear, and the Save button is enabled. Notice this Add Tag button. It allows us to dynamically duplicate the tag input element, and add search tags for this product. This is similar to the multiple addresses we implemented in the demo form. Click Save to save our product, and we see the changed item in the product list. To add a new product, click the Add Product menu option. The form is then displayed initialized for us to enter a new product. We can then fill out the form, and click Save. Our new product appears in the list. Our current form does not have fields for availability date and price, so those are empty here. Notice that if we refresh, these changes are gone. We are using an in-memory Web API server, so our changes are only retained in memory. We'll talk more about that in the next module. Though not a large application, Acme Product Management does provide enough features to see how all of the moving parts work together. Let's look at the architecture of this application.
Sample Application: Architecture
When building an Angular application, we break the features and user interface pieces into components. For the APM sample application, we have a main application component called App component, and a component for each primary page of the application. The Welcome page has a Welcome component. The solid line here represents route navigation, the user can navigate from the menu, defined in the App component, to the Welcome page, defined in the Welcome component. The Product List page has a Product List component. To provide the filtering feature, the application has a product filter pipe. The Detail page has its own component, and we can route to the Detail page from the Product List page. Both the Product List and Product Detail components use a nested component to display the star rating implemented in the Star component. Because this course is on Reactive forms, we'll focus on the Edit page defined in the Product Edit component. We can navigate to the Edit page from the Add Product menu option, defined in the App component, or from the Edit button on the Product List or Product Detail pages. This sample application includes a product data service, which gets the data for our components. In the next module, we'll see how to implement, create, read, update, and delete in this data service, and use it in the Product Edit component. Once we have our components and other pieces of our application defined, we organize them into Angular modules. For the APM sample application, we divided the application pieces into three Angular modules, AppModule for the basic application and startup pieces, ProductModule for the product feature pieces, and SharedModule for pieces sharable across our feature modules. AppModule is the root application module and contains the root and startup components of the application. Our App and Welcome components go here. We import the BrowserModule, HttpModule, and RouterModule, and pull in the ProductModule. ProductModule is a feature module that contains all of the pieces for the product features. Our product feature pages, pipes, and services go here. Our product feature uses Reactive forms and routing, so we import the associated modules here, and pull in the SharedModule. SharedModule, as its name suggests, is a module that can be shared between all of the feature modules. We only have one feature module, but could easily add more to implement customers or invoices, for example. Our StarComponent goes here so it can be reused by any feature, and we export CommonModule and FormsModule, so they can also be used by any feature. Note that even though our ProductEditComponent uses Reactive forms, and therefore requires the ReactiveFormsModule, we are still using ngModel in the ProductListComponent for the filter by box, so the ProductListComponent needs the FormsModule. We have the FormsModule in the SharedModule so that any other component can use ngModel as well. If we plan to use Reactive forms throughout our application, we could move the ReactiveFormsModule to the SharedModule, and export it, so it can be used by any feature. Note that we do not need to declare any of our simple classes within an Angular module, so our validation classes are not depicted here. In this course module, we'll focus on the ProductEditComponent and ProductEditGuard. In the next course module, we'll focus on the ProductService and how the ProductEditComponent uses that service. Let's take a look at the code.
Sample Application: Code
Here is the code for the Acme Product Management application. The majority of this code was built step by step in the Angular 2: Getting Started course. Our focus here is on the product-edit form. If you want more information about any of the other files here, check out the Getting Started course. I've added three files in the product folder, product-edit.component.html for the template, product-edit.component.ts for the component class, and the product-data class. We'll look at the product-data class in the next course module. Let's start with the product-edit.component. When working with Reactive forms, one of the key tasks for the component class is to define the form model, we do that here in the ngOnInit. When the component is initialized we build the root FormGroup. The form element and the HTML binds to this root FormGroup, using the FormGroup directive. The productName has no default value, is required, has a minLength of 3, and a maxLength of 50, but wait, doesn't the productName need to default to the current name of the product? The problem is that we don't yet have that data here in the ngOnInit method. We'll need to go off to the server to get it, so we can't get the default values until the data is retrieved from the server and returned in an HTTP response. So at this point, there is no default value. We'll see how to set the default values from the HTTP response in the next module. The productCode has no default value at this point either, and is required. The starRating also has no default and uses a custom validator. This is the same range validator we created earlier in this course. Because it is a validator that we may want to reuse, I've moved it to its own file in the shared folder. We'll look at that a little later in this module. Lastly is the product description, which also has no default value set at this point, and it has no validation rules. And here is the FormArray for the search tag. Recall that the search tags are dynamically duplicated on the form when the user clicks the Add Tag button. It is initialized to an empty array. The code to work with this FormArray is similar to the code we wrote in the demo form to manage the address block FormArray. Next, let's look at the ProductFeatureModule. In Angular, every component needs to belong to an Angular module, and it makes sense that our new ProductEditComponent would belong to the ProductFeatureModule. We import the ProductEditComponent with an import statement here, then add it to the declarations array here. I've added a few other things here as well. As we know from earlier in this course, to use Reactive forms we need to import the ReactiveFormsModule. We add it to the imports array here. I've also added the InMemoryWebApiModule, we'll talk more about that in the next course module. And here is the route configuration for the product features, including the route to our ProductEditComponent. Let's take a closer look at how to navigate to our ProductEditComponent, and display our edit form.
Routing to a Form
Anytime we add a feature to our application, such as a product edit form, we need to define some way for the user to navigate to that feature. In Angular we define our application navigation with routing. Setting up routing requires three steps, configuring the routes, activating the routes based on a user action, and lastly identifying where to place the activated components template. The first step is to configure the routes with a list of route definitions, each definition specifies a route object. The path property defines the URL path segment for the route. When this route is activated, this URL path segment is appended to the URL of our application. In most cases, we also specify a component, which is the component associated with the route. It is this component's template that is displayed when the route is activated. This first route simply maps a specific URL path segment to a specific component. The id in the second and third routes represents a route parameter. The Product Edit page displays a form for editing one product, so it needs to know which product to display. The ProductEditComponent reads the ID from the path segment, and displays the defined product in the form. There may be times that we want to limit access to a route. We want routes only accessible to specific users, such as an administrator, for example, or we want the user to confirm a navigation operation, such as asking whether to save before navigating away from an edit page. For that we use routing guards. We build the guards using the same techniques as building an Angular service, then we attach the desired guards to the routes in the route configuration. In this example, the product detail route has a canActivate guard, which checks specific criteria before allowing navigation to a particular route. The productEdit route has a canDeactivate guard, which checks specific criteria before allowing navigation away from a particular route. Let's see this in the context of the APM application. The route configuration is defined in an Angular module. Our product routes are in our ProductFeatureModule. Here is the route to the ProductEditComponent. We specify productEdit as the path URL fragment. Because we need to know which product to display for editing, we use the colon to identify a placeholder variable for the ID of the product to edit. We'll pass that ID on the URL to the ProductEditComponent when this route is activated. We want to guard against the user leaving the form when there are unsaved changes, so we define a canDeactivate guard. We'll look at the code for this guard later in this module. Next, we need to define the UI elements to activate this route. We want to display the product form for edit from the Product List page, and from the Product Detail page. We also display the product edit form when the Add Product menu option is selected to add a new product. We tie routes to actions using the routerLink directive. We use property binding to bind the routerLink directive to an array. The first element in the array is the route to activate. The second element is the data to pass to that route. Here we pass 0 to the productEdit route, what does this 0 mean? The implementation rules for the APM application define an ID of 0 to mean a request for a new product. It has no special meaning, other than that which we give it. Because of this implementation rule, anytime the productEdit URL path segment includes a 0 ID, the code will infer that the user wants to add a new product. Let's take a look. The menu for this application is defined with an inline template in the AppComponent, so it is here that we see the Add Product menu option. It uses the routerLink directive to activate the route. This syntax is a shortcut for what we saw in the slide. Since we are always passing a 0 here, we just add it as part of the URL segment, instead of passing it separately in a second element of the array. What about the Edit button? In the product-list.component the Edit button is down here. It uses the routerLink directive to activate the route. Here we pass the ID of the product so that the productEdit component knows which product to edit. The code for the Product Detail button is similar. Now we've configured the routes and tied the routerLink directive to appropriate UI elements so the user can activate the productEdit route. When a route is activated, the associated components view is displayed, but displayed where? How do we specify where we want the routed component to display its view? We use the router-outlet directive. The routed component's view then appears in this location. Let's take a look. Here in our AppComponent we use the router-outlet directive to specify where the routed template should appear, so each page is displayed here under the menu, including our Product Edit page. Let's view it in the browser. When we click Add Product from the menu, the URL changes to productEdit with 0 as the product ID. The edit form is then displayed and set up for entry of a new product. When we click to edit a product, the URL changes to include productEdit and the ID of the selected product. The edit form now displays the data for that selected product. For the product edit form to display the data for the correct product, it needs to read that product ID parameter from the route URL. Let's look at how to do that next.
Reading a Route Parameter
Here again is the route configuration for the productEdit route. To display the appropriate product in the product edit form, the ProductEditComponent needs to read the passed in parameter from the route URL. To get the parameter from the URL, we use the ActivatedRoute service provided by the router. We want an instance of this service, so we define it as a dependency in our constructor. We use the instance of the ActivatedRoute to get the desired parameter. There are two ways to get the parameter from the ActivatedRoute, using a snapshot or using an observable. Use the snapshot if you only need to get the initial value of the parameter, the code is then a one liner as shown here. If you expect the parameter to change without leaving the page, use an observable instead. The observable provides notification when the URL parameter changes. For example, the user could click Add Product while on the Product Edit form, so the URL could change to 0 without leaving the page. Because of this we want to use the observable approach in the productEdit component. Let's take a look. In the product-edit.component, we need to read the product ID from the route, so we know which product to edit, or if the user is requesting to create a new product. For this task the code needs the ActivatedRoute from the router. We import it here, and inject its service using the constructor here. We set up the route parameter observable in the OnInit lifecycle hook, here is where the code subscribes to the route parameters. Every time the parameter changes this code get notified, the ID is pulled from the provided parameter array, and the code calls getProduct to get the product data for this ID. We'll look at getting the data in the next module. To ensure the subscription is appropriately cleaned up, this code uses the ngOnDestroy to unsubscribe, but to unsubscribe from an observable we need to have the subscription, so we define a private property sub that retains the reference to this subscription so we can unsubscribe from it. Let's see this in the browser. Let's edit the hammer. Notice the URL change, then the Product Edit page appears. If I click on Add Product, the Product Edit page input elements are initialized and ready for entry of a new product. This works because we are watching for changes to the URL parameters. Now watch what happens if I change something and try to leave the page, I get a message asking if I want to navigate away and lose my changes. This could be a very helpful feature for a user that just created a new product and then distractedly navigated away. Let's see how we set up this route guard.
Setting a canDeactivate Guard
Building a guard follows the common pattern used throughout Angular, create a class, add a decorator, import what we need. Here we define a ProductEditGuard class, since we are implementing this guard as a service we use the Injectable decorator, and we import what we need. This class implements canDeactivate. Notice this syntax, it includes a generic parameter that specifies the associated component. This code implements the canDeactivate method, passing in the specified component. For simple cases, this method can return a Boolean value, true to deactivate the route, and false to cancel the route deactivation. For more complex cases, this could return an observable or a promise. Let's take a look. The guards used in this sample application are very specific to products, so they are in the products folder, in the product-guard.service.ts file. We have two guards in here. The ProductDetailGuard ensures that the user cannot navigate to the ProductDetailRoute unless the URL contains a valid product ID. The ProductEditGuard checks the form's dirty state. If the form is dirty, it asks the user to confirm the navigation. The canDeactivate method passes in the component. Code in the method can then access the component properties. In this example, the code uses the productForm property, which is the reference to the form model. It then checks the dirty property to determine if the user has made any changes to the form. If so, the code notifies the user, and requests a confirmation before navigating away from the Product Edit page, or navigating to a different product. Before we can use a guard, we need to register the service provider in an Angular module. We import the guard using an import statement here, then register the provider for the guard here. The last step is to tie the guard to the appropriate route. The guards are specified as part of the route configuration. Here we specify canDeactivate and provide an array of guards, we only have one so we specify that here. Let's see this in the browser. We'll navigate to the Edit page, make a change, and select to Add a Product. We see the message provided by the canDeactivate route guard. Note that the canDeactivate only works when navigating within the Angular application, it does not check the guard if the user navigates to an entirely different site, or closes the browser. Looking again at this form, it uses a range validator similar to the one we built in the demo form earlier in this course. Let's see how that validator was changed in the context of the APM application.
Refactoring to a Custom Validation Class
Earlier in this course, we defined a range validator in our demo form component using code similar to this. This range validator required parameters for the minimum and maximum values for the range. This meant that we had to wrap the validator function in a factory function, as shown here. In the demo form application, we put this function within the form's component code file, making the validator more difficult to discover and reuse by other forms in the application. We could instead define this validator in its own file. Let's take a look. Since the range validator can be shared with any form that needs it, including a template-driven form, I added it to the shared folder. Under the shared folder is number.validator.ts. This file is set up to hold any number validator, assuming over time we may have more than just this range validator. Within this file we import what we need, then we define a class that we export. This makes it easy to reuse any number validators, defined here, in any of our components. We mark the method as static so that any code can use it without creating an instance of this class. The remainder of this code is as we saw it in the demo form component we built earlier in this course. To use this validator in a form component such as the product-edit.component, we import the validator using an import statement, then use it like any other validator when building the form model here. Let's try it out in the browser, we'll again edit the hammer. Here on the form we enter 0 and see a validation message, we enter 5 and the validation message disappears, enter 6 and the validation message reappears. Bottom line, it's easy to take a local validator function or factory function that returns a validator function and move it into a separate class. The validator is then easier to discover because it is in the shared folder, and easier to reuse because we can simply import it. Let's move on to the fun bits, retrieving and saving data, but first a quick checklist.
Checklists and Summary
When building a form in the context of a more full-featured application, we need to add a route configuration to route to the form, add user interface elements to activate the route, such as an Edit button, read the route parameter passed to the form to edit the appropriate item, optionally set up a canDeactivate guard to remind the user to save before navigating away, and optionally refactor any locally-defined validator functions to a custom validator class for reuse within the application. In this module, we saw how a Reactive form is often just a piece of the puzzle of a larger application. We examined how to route to the form, and how to read the route parameters in the component class to identify the appropriate item to edit. We saw how to set up a canDeactivate guard to provide a notification message if the user navigates away from the form with unsaved changes. And we discovered how to refactor a local validator function into a custom validator class for reusability. Next up, let's see how to get data into our form and save the user's changes.
Create, Read, Update, and Delete (CRUD) Using HTTP
The key purpose of a form, in many scenarios, is to collect or edit data. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and in this module, we deal with CRUD, or to say that another way, Create, Read, Update, and Delete operations for retrieving and saving data to a back-end server with HTTP. Many Angular applications obtain data using HTTP. The application issues an HTTP get request to a web service on a web server. At some future point in time, that web service processes the request, retrieves the data, often from a database, and returns that data to the application in an HTTP response. The application then processes that data. Note that I said, at some future point in time. HTTP requests are asynchronous. We send a request, then continue about our processing, handling other user interactions as needed. At some future point in time, the request is processed, and we receive the response. The user then enters or updates information, and selects to save. The application issues a post or a put request to a web service, passing along the entered data. At some future point in time, that web service processes the request, stores the data, and returns an HTTP response. The application then processes that response, which may contain the updated data. Notice that I again said, at some future point in time. Working with HTTP requests and responses requires working asynchronously. In this module, we examine the Data Access Service for our data operations, including creating new data, retrieving the data for displaying the form, processing user updates to existing data, and deleting existing data. Let's get started.
Data Access Service
Here again is the architecture of our sample APM application. We defined a component for each view in our application, and a Product Data Service to encapsulate all of the HTTP communication for our products. Why? Why create a separate service? Why not just put the data access logic in the component that needs it? One reason to build a Data Access Service is separation of concerns. Let each component focus only on the logic required to display its template and handle its interactions. And let the Data Access Service focus only on the communication with the HTTP server. By separating concerns, each piece of the application can focus on what it does best. Putting the data access logic in a Data Access Service also provide reusability. The Product List component needs to retrieve product data, the Product Detail component needs to retrieve product data, and the Product Edit component needs to retrieve product data. Instead of repeating that code in each component, the components can share the functionality provided by the Data Access Service. Another benefit of using a Data Access Service, is that is can share the data itself with all of the components. The Data Access component could retrieve the data one time, and share that data with all of the components that need it. The next question then is how? How do we send an HTTP request from a Data Access Service? Here is a Data Access Service called Product Data Service. The back-end web server can be hosting a Node server, ASP.NET Web API, PHP, Rails, or whatever, Angular doesn't care, the Product Data Service communicates with our back-end web server using Angular's built-in Http Service. The Product Data Service issues a request, such as a get request, to the Angular Http Service, which in turn issues the request to the back-end web server. At some future point in time, the back-end web server processes the request, and returns the response. Angular's Http Service forwards that response on to the Product Data Service. To build a Data Access Service, we first need to register the provider for the built-in Angular Http Service. Next we create a class for the Data Access Service, and register the service provider. Then we inject the Angular Http Service using the class constructor, so we can use it in the Data Access Service. We import observables and the observable operators that we need. Observables help us manage asynchronous data, such as data coming from a back-end server. We need them here because working with requests and responses means we're working asynchronously, so Angular's Http Service returns observables. Lastly, we write the code to issue each HTTP request that we need, get, post, put, delete, and so on. Let's dive into a demo and look at the code required for each of these steps.
Data Access Service: Demo
In this demo, we'll look at the basic code required to build a Data Access Service. Our first step is to register the built-in Angular Http Service, the question is where? Where should we register this service? We can register a service within a specific component or within an Angular module. Since we want to use the Http Service from our own Data Access Service, not a specific component, we'll register it within an Angular module. When registering a service with an Angular module, the service is registered with the root injector, so it really doesn't matter which Angular module we inject it in. My rule of thumb is to put it where I'd think to find it later. If I had a core module, I'd add it there with my other services. But since there is no core module defined in this particular application, the next best place is probably the app.module. The HttpModule is imported here with an import statement, then added to the imports array here. The HttpModule registers the Http Service, so it is then ready for us to inject and use. The next step is to create the Data Access Service class. Because the service is specifically for product data, this service resides in the products folder, its named product.service.ts. In Angular a service is defined in a class. In this example, we call it ProductService, we optionally decorate a service using the Injectable decorator. As with every Angular service, we need to register it. To keep all of the product code together, we'll register it in the product.module. We import the service with the import statement here, then add it to the providers array here. Once the Data Access Service class is in place, we inject the built-in Angular Http Service so we can use it to communicate with the back-end server. We first import that service here along with several other classes we'll need later, then we inject the service in the constructor here. Now that the product service has itself an injected service, namely this Http Service, the Injectable decorator is now required. Angular's Http Service returns observables, so our next step is to import observable and the observable operators. Here we import Observable from the Reactive extensions, rxjs. We also import the operators we will use, do for debugging, catch and throw for error handling, and map to map the HTTP response to products. We'll see how to use these operators in the upcoming demos. The last step is to write the code to issue each HTTP request. We'll examine the code for each request shortly. But before we can issue an HTTP request to a back-end server, we need to have a back-end server, or at least fake one.
Faking a Backend Server
Before we can issue HTTP requests to a back-end server, we need to set up that back-end server. We select a technology such as Node, ASP.NET, PHP, Rails, or whatever, we set up a set of URLs for calling this server, those URLS define the API, or Application Programming Interface, and we build the server-side code to process requests sent to those URLs, and return appropriate responses. Our Data Access component then uses the defined API to issue HTTP requests, and processes the returned responses. But, what if we don't have a back-end server in place? Luckily, we can fake one. There are numerous ways to fake a back-end server. Our Data Access Service could simply return hardcoded data, this is a technique often used in demo applications; however, this technique does not use the Angular Http Service, and does not demonstrate how to issue requests to a back-end server, it won't do for our purposes. Another option is to use a JSON file. By setting the URL for the HTTP call to the location of a JSON file, we can use the Http Service to retrieve the data from the JSON file as if it were a back-end server. This works great if we only want to get data, but we also want to put and post data, so this technique is not sufficient. Angular provides a MockBackend class that we could use to write our own fake back-end server, but that sounds like a chore. An easier option is to use the in-memory Web API simulator called Angular in-memory-web-api. This simulator is not part of Angular core, but is a separate service developed and provided as part of the Angular documentation quick start files. Let's go with this last option. There are a few steps to set it up, let's take a look. To set up the Angular in-memory-web-api, we need to first ensure it is included in our package.json dependencies. We have it here, so it's installed when you run npm install, and we need to have it in our systemjs.config file, it is here. This tells the module loader where to find it. Then we need to add the in-memory-web-api to the imports array of one of our Angular modules. In this example, it is defined in the product.module because we are only using it for our product data. If we had multiple feature modules using it, we may want to move it from here to the core module or the app module. We define an import statement for it and its associated product data class here. We add it to the imports array here. In the imports array we call the forRoot method and pass the name of the ProductData class. The ProductData class provides the data managed by the in-memory-web-api. Let's take a look. This is the ProductData class, it implements InMemoryDbService, which specifies one required method, createDb. In this method we provide the data managed by the service. In our example, we create an array of products. One thing to note here, if you worked through the Acme Product Management application in the Angular 2: Getting Started course, you may notice that this data uses id instead of productId. Using id is required for the in-memory-web-api service to access the data by id, and assign new ids. The in-memory-web-api service then processes each HTTP request against this set of data. It retains all changes to this data in memory. If you stop the application and restart, the data is reinitialized to this array of products. When your real back-end server is set up, you can remove the InMemoryWebApiModule and the associated data class from the Angular module. Then the code in the Product Data Service will issue the HTTP requests to the actual server instead. With that, let's look at how to use the Http Service in our Product Data Service to retrieve data.
Populating the Form with Data
When working with forms, why do we need to get data from a back-end server? If it's a login form, or registration form, or other data collection type of form, you may not need to get data, just collect the user's entries and process them, but if it's an edit form, we want the current data so we can display it for edit. What if we already retrieved the data for a list or a selection style page? We may still want to re-get the data for the edit form from the back-end server to ensure we have the most current data before presenting it to the user for update. Whether any particular form in your application requires getting data from a back-end server depends largely on the purpose of the form, and volatility of its data. For the purposes of our example, the product edit form does indeed require current data, so we'll add code to populate the form from data obtained from the back-end server. Here is the code required to issue an HTTP get request using Angular's Http Service. The key bit of this code is the getProduct method. We want to retrieve a single product to edit, so we pass the ID of the desired product into this method. Here we use the backtick to define a template literal. This takes the base URL and appends a slash, followed by the defined id. The resulting URL looks something like this. The code then calls the get method of the Angular Http Service, and passes in the URL. Recall that the Http Service was injected into this service in the constructor here. At some future point in time when the response is returned, we map the data back into a product, and any subscribers are notified. How do we call this method and subscribe to this observable? In the forms component, in our case product-edit, we inject our Product Data Service using the constructor. Here we define a local getProduct method that calls the Product Data Service get method, passing in the id of the desired product to retrieve. We then call subscribe to subscribe to the observable returned by the getProduct method. By subscribing to the observable, this code is notified when the data is returned. When the observable does return the product, this code calls a method to process that product. If the observable returns an error, the error is cast to a type of any, and assigned to an errorMessage string. Let's examine this code in context.
Populating the Form with Data: Demo
In this demo, we look at how to retrieve data using HTTP, and use that data to populate the form. We start in the Product Data Service with the getProduct method to issue a get request and process the response. The get method here does a bit more work than the one shown in the slides. Recall from earlier in this course that our sample application's business rules use a product ID of 0 to indicate that the user wishes to add a new product, so if the ID is 0 the code assumes there is no product to retrieve, and instead returns a new initialized product. The initializedProduct method looks like this. Since the getProduct method returns an observable of IProduct, not a product directly, we create a new Observable, call next to return initializedProduct, and call complete to mark the subscription as complete. Observable.of provides a shortcut syntax for this set of code that you can use instead. As with many of these types of coding decisions, some may object to having a getProduct method return an initializedProduct. If so, you can delete this extra code here. The calling code would then decide whether to call getProduct to get an existing code, or call the initializeProduct method directly to get a newly initialized product. Note that often in real applications the back-end server is set up to return an existing item, or a new initialized item. That way the business rules for initialized item are defined at the server. If that is the case, this additional block of code is not needed. If the product's ID is not 0, the code builds up the appropriate URL using the passed in id. It then calls Angular's Http Service get method, as we saw in the slides. The code uses the map operator to map the responses to a product. We could inline that function here, but instead this code passes in a reusable function. The extractData function calls the JSON method, and returns an object literal containing the JSON data, or an empty object if there is no data. We could add other code here to modify or enhance this data before returning it. For example, we could alphabetize the search tags, or calculate the number of days since the product was released. This example also demonstrates the do operator, which specifies an action to take for each emitted observable. Here we simply log the data. This operator is great for debugging. We'll use do in each of our HTTP requests to see when the request occurs. Since we are using an in-memory Web API, we won't see our HTTP traffic in the Network tab, so logging to the console is a useful substitute. Note that this code is not executed until a caller subscribes. So let's look at that next. The ProductEditComponent needs the product data to display on the form for edit. It wants to use the Product Data Service to get the data, so it imports it here, and injects it using the constructor here. Next we need to decide the appropriate time to get the product data. Recall that the id of the product to edit is provided on the route as a parameter; we must read that value from the route before we can get the appropriate product. The code to read the route parameter is here in the ngOnInit, as discussed previously in this course. After we pull the id from the route, we call the local getProduct method, passing in that id. It is in this getProduct method that we use the Product Data Service. Here we call the Product Data Service getProduct method, passing in the id. We subscribe to the returned observable providing two callback functions. After the Product Data Service receives the response and maps it to a product, the subscribers are notified. If the product is retrieved successfully, the returned product is passed in, and this callback method is executed. This method calls onProductRetrieved to process it, otherwise this method sets the error message properly to display a message to the user. The onProductRetrieved method performs several operations. First, if the form was already in use, it resets it. This ensures that all of the state flags such as dirty, touched, and so on, are cleared before displaying the retrieved product. Then we set the product property to the retrieved product. This property holds our data model. Recall that we are not using data binding on the product, so this does not update the form with data. Next we set the appropriate title on the page based on the add or edit operation. This last bit of code here uses the product properties to set the value for each of the FormControls, the product data then appears on the form. We looked at setValue and patchValue earlier in this course. We use patchValue here and not setValue, so we can set the values for a subset of the FormControls on the form. This is required because our form model includes a FormArray, and we can't set the value of the FormArray with setValue. We then use setControl to reset the FormArray to the set of product tags. This ensures that the correct number of FormControls are created for this product's search tags. Let's check it out in the browser. Select Product List, select to edit the hammer, and the existing data is displayed for edit, including each of the three search tags. Let's edit something so we get a validation error. Now let's click the Add Product menu option. First we see our route guard warning us that we will lose our changes, click OK, and see the form reset, clearing our validation error. Notice also that our tags were reset to display only one initialized search tag. Use the techniques shown here anytime you need to retrieve data for display in a form, so the user can edit that data. Speaking of editing data, let's look at how to save those edits.
An edit form is not really an edit form if we can't save the user's edits. We expect the user to make some changes to the data on the form, and then click the Save button, but what data do we save? That may seem like an odd question, because we of course want to save the user's changes, but when using Reactive forms, we have the data defined in two places. We have the user-entered values as shown in the form itself. As we saw earlier in this course, we can access that data using the value property of the form's root FormGroup, however, we also have the product property, which retains the original values we retrieved for display. Since we are not using ngModel for two-way data binding, these values are not automatically updated with the user's changes. Also notice that we have more fields in the product property than we do on the form, such as id, price, and imageUrl. If we update the back-end server with just the data from the form, we'll lose the values of those extra fields. We need to copy the user's changes over the original product properties. This may sound tedious, but is actually easy if we use the Object.assign method. This method takes in a target and any number of sources. Here our target is a new object, and our two sources are the product property and the form's root FormGroup value. Since later sources' properties overwrite earlier ones with the same key, our p variable now holds the original product properties overwriting any user's edits from the form. This is the data, then, that we'll save. We'll see this in action when we get to the demo shortly, but there's one other topic to cover first. When working with HTTP there are two different verbs we can use to save data, post and put. When do we use which? Post posts data for a resource or a set of resources without specifying an ID. It is used to create a new resource when the server assigns the ID, or update a set of resources. This URL is basically saying, I have some product data, process it. Put replaces data for a specific resource using a provided ID. It is used to create a new resource when the client assigns the ID, or update a resource with the defined ID. This URL is basically saying, I have a product with an ID of 5, replace that product if it exists, otherwise create it. One other important differentiator between post and put is that put is defined to be idempotent. This means that multiple put requests are expected to have no additional effect if called multiple times with the same parameters. Post is not idempotent. Issuing multiple post requests are not expected to have the same effect as one request. Bottom line, we will use post when creating a new product because we want the back-end server to have the responsibility of assigning appropriate IDs. We will use put when updating a product. We will explicitly set the ID of the product to update. Since we are talking now about saving edits to existing data, we'll start with the put. Here is another snippet from the Product Data Service. The key bit of this code is the updateProduct method. We want to update the data for our product, so we pass the updated product into this method. We then set the headers and options. In the headers object the Content-Type specifies that the body is in JSON format. We use this headers object to configure the options object. The RequestOptions allows us to specify request settings, such as the header. Here we use the backtick to define a template literal. This takes the baseUrl and appends a slash, followed by the defined id. The code then calls the put method of the Angular Http Service, and passes in the URL, the updated product, and the defined options object. At some future point in time, when the response is returned, we map the passed in product and any subscribers are notified. Note that if our back-end server was set up to return the updated product, we could use that here instead. Returning the updated product is useful if the server sets additional properties, such as the last update date. How do we call this updateProduct method? The product-edit.component calls the Product Data Service to save any edits. Here we define a local editProduct method that calls the Product Data Service updateProduct method, passing in the updated product. We then call subscribe to subscribe to the observable returned by the updateProduct method. By subscribing to the observable, this code is notified when the put operation completes. If the product is saved successfully, we call onSaveComplete for any final processing. In this example, we don't care about the returned product so we don't reference it here. If the observable returns an error, the error is cast to a type of any and assigned to an error message string. What type of code goes in to the onSaveComplete? That depends on the requirements of the application. Do you want to allow the user to save, but still stay on the same page? If so, then you may not want to do anything else there. Or do you want to navigate back to the list page after the save? Then you could add navigation code there. Let's examine the edit code in context.
Saving Edits: Demo
In this demo, we look at how to save user edits using HTTP. Again, we start with a Product Data Service. To minimize duplicate code between the put and the post, I added a saveProduct method. This method takes in the edited product, sets the header in options, as shown in the slide, and then performs the put or post based on the product ID. When editing an existing product, this calls the updateProduct method, passing the product and defined options. The updateProduct method then builds the appropriate URL. It calls the Angular Http Service put method to issue a put request and update the provided product. Notice we are again using the do operator to log our action to the console. The put request is not executed until a caller subscribes, so let's look at that next. When the user clicks the Save button and submits the form, the ngSubmit event handler calls the saveProduct method in the product-edit component. This saveProduct method first checks the form state, there's no point in saving the form data if it was not changed, as defined by the dirty property, or if it was not valid. If it was not dirty, clicking Save simply completes the save operation. If it was invalid, it stays on the Product Edit page. This line should look familiar from the slides. Here is where we create a new product object from the product data model property, overwriting any values from the form. This gives us all of the user's changes, plus any fields that were not included on the form, such as the ID. Then we call the saveProduct method on the Product Data Service, passing in the new product object we just created here. After the Product Data Service receives the response, the subscribers are notified. If the product is saved successfully, this callback is executed. This method calls onSaveComplete to perform any additional processing, otherwise this method sets the error message property to display a message to the user. The onSaveComplete method can perform any operations required after the product is saved. In this example, it navigates to the Product List page. Notice that we call Reset on the form before navigating away, this is because the form is still marked as dirty. If we don't reset the form, our route guard will tell us the form has unsaved changes. Now let's see the edit operation in action in the browser. Here is our edit form, we can update any values. When the form is valid we click Save to save those changes, and the navigation takes us back to the Product List page. We can see our edit here. So that's how we save user changes, but what if the user creates a new product? How do we handle that?
Creating New Items
At first glance, creating a new item sounds easy, just put up an empty form, and save the user's entries with the same save method as for and edit, but there is more to it than that. Often we want to start with a properly initialized object, and when we save a new item we want to call post instead of put. We'll look at post in a moment, but why do we need a properly initialized object and not just an empty form? This looks like an empty form. There are several reasons we may want to start with an initialized object instead of just an empty form. We can initialize arrays and other data structures so they are easier to work with. In this example, the tags array is initialized to an empty array, so we can easily push items to it without worrying about the array being undefined. We can set proper defaults, the business rules for this app define that a new product has an id of 0, so we can set that here. We could also set defaults for the releaseDate, or any other fields that make sense. So the next question is where, where should we initialize this product? As we discussed earlier, one good choice is the back-end server. We could put the logic for defining a properly initialized object in the back-end server's code. When it receives the get request for an id of 0, it could return an initialized product. This is a good option to keep all of that logic in the back-end server, however, our in-memory Web API service does not know how to do that, and there may be cases that we don't want our back-end server to handle initializing our objects. In these scenarios we initialize the object in the Data Access Service with code like this. The code to post request is similar to the code to put a request, except that we don't pass an ID. We set up the same headers and options. Instead of passing in an URL that contains the product ID, we just use the baseUrl. We still pass the product and options, we then map the response to a product, and notify the subscribers passing along the newly saved product. This product includes any changes made by the back-end server, such as assigning the unique ID. We call this method similar to how we call the method to edit existing data. Let's look at this code in the context of our sample application.
Creating New Items: Demo
In this demo, we look at how to create new items by issuing an HTTP post request. When the user clicks the Save button and submits the form, the ngSubmit event handler calls the saveProduct method in the product-edit.component. That method doesn't care if the product is new and requires a post, or is an update and requires a put, it just ensures that it has the updated data using the Object.assign technique we discussed earlier. Here product is the newly initialized product object, and productForm.value provides the user entered values. It then calls the Product Data Service saveProduct method, passing in the product to save. We looked at the saveProduct method earlier, this code sets up the headers and options, and calls the appropriate routine based on the product id. If we are creating a new product, specified with a product.id of 0, it calls the createProduct method to issue a post request. The code in the createProduct method looks very similar to the code in the slide, however, it first sets the product.id to undefined. This is for the in-memory Web API service we are using to fake our back-end server. For it to assign a unique id to each created item, we must specify an id of undefined, then this method calls Angular's Http Service post method, and posts the product and defined options to the specified URL. When the request is processed and the response is returned, this code maps the response object back to a product object, the subscribers are then notified, and pass this product. Notice that we are again using the do operator to log our action to the console. In our example, the subscriber ignores the returned object because it does not need it. It won't stay on this page, instead it navigates back to the Product List page, just as with the edit operation. Let's try it out in the browser. We click on the Add Product menu option to create a new product. The product edit form is displayed with an initialized product. We can enter any data, and click Save. We are returned to the Product List page with our new product here. Click to Edit the product, and we see the assigned ID and the URL. Next, let's look at a delete.
Deleting an Existing Item
In our sample application, a user can delete an existing item using the Delete button at the bottom of the form. We could have instead added a Delete button to the Product List page next to the Edit buttons here. So the ability to delete is not really tied to using a form, it is included in this course for completeness. The biggest question to consider when providing delete functionality is whether to really delete? In most of the applications I've written, the delete option does not really delete data, rather it sets a status field in the data to deleted, that way the data is never gone, it is still available for history, reporting, and that emergency request to undelete an accidental deletion. When marking an item as deleted using a status field in the data, we issue a put or patch request to update the status field similar to how we perform an edit operation, we do not issue an HTTP delete request. In this clip, however, we'll assume that we really, really, really need to delete the data, and we'll see how to issue an HTTP delete request. Here is a snippet from the Product Data Service. The deleteProduct method takes in the id of the product to delete, it does not need the entire product. Notice that it returns an Observable of Response, not an Observable of Product. This method then defines the headers and options similar to the HTTP put and post, and uses the passed in id to build a URL that specifies the item to delete. We then call the Angular Http Service delete method, passing along the URL and defined options. Since we don't expect to get back a product, we don't need a map operator here. The product-edit.component calls the Product Data Service to perform the delete operation. Here we define a local deleteProduct method that calls the injected Product Data Service deleteProduct method, passing in the id of the product to delete. We then subscribe to the observable returned by the deleteProduct method. By subscribing to the observable, this code is notified when the delete operation completes. When the response is returned, we proceed as with the save. Let's look at this in the context of our sample application.
Deleting an Existing Item: Demo
In this demo, we look at how to delete existing items. When the user clicks the Delete button on the form, the click event handler calls the deleteProduct method in the product-edit.component. The deleteProduct method here is a bit more complex than that shown in the slide. First we check the product.id. If the user selected to add a product and clicked the Delete button before that new product was saved, we don't need to issue a delete request to the back-end server, we can just not save it. This code simply resets the form, and navigates back to the Product List page. If the product id is not 0, the code displays a dialog asking the user to confirm the delete operation. For a real app, you may want to consider displaying something nicer, but this works for our purposes. If the user confirms the delete operation, we call the Product Data Service deleteProduct method, passing in the id of the product to delete. In the Product Data Service, the deleteProduct method first sets the headers and options similar to the code for the save operations, and builds a URL that includes the id of the product to delete. It then calls Angular's Http Service delete method, passing along the URL and options. Here again is the do operator, so we can track when the delete operation occurs, and some exception handling. Now let's try it out in the browser. Let's start with a scenario whereby the user deletes a newly added item without saving first. Before we begin, let's open the developer tools, and clear the console. Recall that we won't see the HTTP requests in the Network tab since this application uses an in-memory Web API. To see when each request occurs, the code includes the do operator, and logs to the console for each HTTP request. We click Add Product, and notice there was no get request to get the data for display, instead the Product Data Service returned an initialized product. We can enter some data, and then click Delete. If we scroll up in the console, we see no delete request, all we see is the getProducts request used to display this page. Now let's try deleting an existing product. Let's clear the console, and click Edit to edit the hammer. Scrolling up, we can see that we issued a get request to get the current product data for display. Then click the Delete button. Here we see the delete confirmation message, click OK, and the product is deleted. We can see the delete request here. With that, we have a Data Access Service that supports CRUD, create using post, read using get, update using put, and delete using delete. Now let's finish up this module with some checklists we can use when our forms communicate with a back-end server.
Checklists and Summary
Before we can use Angular's HTTP Client Service, we need to register it with an Angular injector. This registration is done for us in the HttpModule included in the Angular HTTP package. We pull HttpModule into our application by adding HttpModule to the imports array of one of our application's Angular Modules, then build the Data Access Service to wrap the HTTP requests. Specify the needed imports. Define a dependency for the Angular Http Client Service using a constructor parameter. Create a method for each HTTP request. In each method call the desired HTTP method, such as get, and pass in the URL to the desired server, along with any other required arguments. Use the map operator to transform the raw HTTP response to a JSON object, and add error handling as desired. In any component that needs data from a data service, including any forms, such as our product-edit.component, inject the Data Access Service in the constructor. Then for each Data Access Service method call, such as getProduct, subscribe to the returned observable. Provide a function to execute when the observable emits an item, and add an error function to handle any returned errors. Use similar code for each Data Access Service operation your form needs. This module was all about data access. We examined a sample Data Access Service that uses Angular's Http Service to communicate with a back-end server. We faked that back-end server with an in-memory Web API so the application could demonstrate the appropriate code, yet not require setup of an actual back-end server. We saw how to initialize an object and call the post method to create new items. We examined how to retrieve existing items with the get method. We saved the user's updates with the put method. And, even though we may want to save a status code that marks an item as deleted instead, we walked through the code required to directly delete an item with the delete method. Only one module left.
You now have the basics you need to build Reactive forms. Welcome back to Angular 2: Reactive Forms, from Pluralsight. My name is Deborah Kurata, and the final words in this course include a recap of our journey, and a few pointers to additional information. Let's jump right in to the short module.
Recapping Your Journey
Template-driven or Reactive forms? To help you make that decision, we looked at the similarities and differences between these two choices for building data entry forms with Angular. As we covered in this course, these are some of the key advantages of a template-driven approach, and these are some of the reasons to go with a Reactive forms approach. Depending on your team, its experience, and its background, some of these factors may weigh more heavily than others. Select the appropriate forms approach that works best for you, your team, and your project. As you saw throughout this course, Reactive forms shifts the responsibility from managing our form and its data from the template to the component class. Instead of the template automatically generating the form model to track form and input element state, we build the form model ourselves in our component class using FormBuilder, the template then binds to our form model. Instead of using two-way data binding to automatically keep the class properties and user entries in sync, we can define an immutable data model, and explicitly manage the form data. Since the class properties are in sync with the form data in the template-drive approach, when it comes time to save, we just save the class properties. With Reactive forms, we combine the class properties and the user's changes retained by the form model to specify the data to save. Because we build the form model and manage the data ourselves, we have more, more functionality, more flexibility, and more control of our forms and their associated data, and as we've seen in this course, more code. With Reactive forms we define right and wrong by setting validation rules when building the form model, but we can easily change those rules at run time. In the demo form, we set the phone number as required, only if the user requested notification by text message. So we can handle validation rules that specify right, wrong, or it depends. I see you. By watching the user's changes we can react to modify the display, the validation, or the messages, providing a much more dynamic and personal experience. In the demo form, we walked through how to watch for changes to the email input element. When the value changed, we checked the element state, and displayed validation error messages when appropriate. Moving the validation error messages and their logic to the component class greatly simplified our template HTML. We then tried out the debounceTime reactive transformation, to allow the user to enter an entire value before displaying a validation error message. Sometimes a single entry is not enough, the user may want to enter multiple address blocks or multiple search tags. We examined how to use a FormArray to dynamically duplicate input elements on the form using the Reactive forms approach. We then looked at how to fit the form piece into the context of a more full-featured application. We saw how to navigate to a form, and set up a route guard to remind the user to save before navigating away, and we set up validation for reuse. Lastly, we examined the code for a Data Access Service to create, read, update, and delete form data on a back-end server using HTTP. Along the way, this course provided a set of checklists containing steps and tips. Feel free to revisit and reference these checklists as you start building your own Reactive forms applications.
If you want more information about Angular components, services, dependency injection, or observables, check out one of these beginner level courses. To learn more about template-drive forms, check out this course. And the Angular documentation covers both template-driven and Reactive forms, it's a great reference.
Congratulations, you now know how to build Angular Reactive forms with full CRUD support, yay. Only one question remains, right or left? Thanks for listening, and I'd love to hear about your experience with Angular forms.
Deborah Kurata is a software developer, consultant, Pluralsight author, Google Developer Expert (GDE) and Microsoft Most Valuable Professional (MVP). Follow her on twitter: @deborahkurata
Released11 Jan 2017