Building a Phone Number Input

Of all the form inputs that have been written over the years, the phone number input box stands out as one of the most deceptively complex. On the surface, it appears simple—everyone knows what a phone number is! Underneath the surface, things get more complicated. There are many wrong ways to build a phone number input. A form could expect a specific format of phone number, rejecting everything else, but fail to let the user know which format it expects. Should there be parentheses around the area code? What about the country code? Even the biggest fan of your software will run screaming to a competitor if the form insists on (entirely hidden) formatting rules as shown in the screenshot.

images/noPhoneFormat.png

The other side of the coin is form elements that do way too much parsing. This unhelpful input box sliced off anything after the first nine characters (and worse, didn’t validate anything):

Worst of all are forms that ask for a phone number but don’t need one in the first place. Take a moment to consider: does this form really need a phone number input, or is it there just because everyone else does it? Even a fancy library like RxJS won’t save you from building functionality you never needed in the first place.

The Right Way

Now, how would we build a phone number input the right way? A good form input should:

  • Clearly show what sort of format is recommended.

  • Accept all kinds of formats, regardless of whether they’re pasted, typed, or entered through the browser’s autofill tool.

  • Reformat the phone number in an easy-to-read way if possible.

  • Clearly indicate problems when they occur and what needs to be done to fix them.

This is possible using just techniques from the first section of this book.

 fromEvent(​'blur'​, myInput)
 .pipe(
  pluck(​'target'​, ​'value'​),
  map(phoneNumber => phoneNumber.replace(​/​​[^d]​​*/g​, ​''​)),
  filter(phoneNumber => phoneNumber.length === 10),
 // ...etc

While you could build out a large form to this spec using RxJS alone, managing all of the elements would get complicated quickly. Thankfully, Angular has an answer to make building useful forms easier than ever. Angular supports two types of forms: Template forms and Reactive forms. Template forms use the same concepts from AngularJS where each form element is manually assigned a property and managed independently (though you don’t need AngularJS knowledge to use them).

Modifying applications with template forms can be problematic—I’ve taken down a service because I added a form element in the view and never connected it to the JavaScript object that represented the state of the form. This meant that the value sent to the server was invalid, and the backend rejected all changes. Whoops.

The engineers who built Angular’s reactive forms knew about the fragility of the old ways, and now all roads lead to a single source of truth for the form definition, in both the model and the view. Without further ado, it’s time to start building your own phone number input.

Adding Reactive Forms

It’s time to start coding. Generate a new application with the ng CLI tool you installed in Chapter 6, Using HTTP in Angular.

 ng new rx-pizza --routing

This application uses Bootstrap’s CSS to give things a modicum of visual appeal. Open index.html and bring in the CSS: add the following tag to the <head> of the file (don’t forget to have the book server running in addition to the ng serve call):

 <!-- Bootstrap (loaded from local server) -->
 <link rel=​"stylesheet"​ href=​"http://localhost:3000/assets/bootstrap.min.css"​>

Some placeholder HTML is generated in app.component.html. Remove everything and replace it with:

 <div class=​"container"​>
  <router-outlet></router-outlet>
 </div>

Reactive forms are not included by default with Angular, so the first thing to do is import them at the application level. While it’s possible to use both template-driven and reactive forms in the same application, they diverge dramatically in both concepts and implementation details. I do not recommend that you mix the two. Open app.module.ts and add the following lines:

 import​ { BrowserModule } ​from​ ​'@angular/platform-browser'​;
 import​ { NgModule } ​from​ ​'@angular/core'​;
 
 import​ { AppRoutingModule } ​from​ ​'./app-routing.module'​;
 import​ { AppComponent } ​from​ ​'./app.component'​;
import​ { ReactiveFormsModule } ​from​ ​'@angular/forms'​;
 
 /* ... snip ... */
 @NgModule({
  imports: [
  BrowserModule,
  AppRoutingModule,
ReactiveFormsModule
  ],
  declarations: [
  AppComponent
  ],
  bootstrap: [ AppComponent ]
 })
 export​ ​class​ AppModule { }

The ReactiveFormsModule is imported from @angular/forms, a package that also contains the code for template-driven forms. The Angular compiler is smart enough to include only the code you need, so this import won’t bring in the code for template-driven forms.

Adding to the imports property at the root level ensures that the tools in ReactiveFormsModule will be available throughout the applications.

Joe asks:
Joe asks:
Why Do I Need to Import Form Tools?

Angular does not include any tooling for working with forms by default and instead requires the developer to manually import them (either template or reactive). Angular is designed this way to keep build sizes down. In AngularJS, all of the tooling for working with forms was included, even if the application didn’t use all of it. Every time the user loaded a page, lots of superfluous code would be downloaded and parsed, slowing things down. With Angular, you have to explicitly ask for such tools to be included, resulting in a code bundle that only includes what’s used.

Now that the application’s components can access all the tools from the Reactive Forms module, it’s time to generate a new component. Create a new component with ng generate component phone-num and add a declaration to the routing module like you did in Chapter 6, Using HTTP in Angular. Start the Angular server with ng serve, and make sure you can navigate to the phone num component. Add a route to app-routing.module.ts for this new component:

 {
  path: ​'phone'​,
  component: PhoneNumComponent
 },

Now that the boilerplate is out of the way, let’s construct the component itself.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.224.226