简体   繁体   中英

No redirection after successful angular form validation

Today, I encountered a problem with angular routing after successful validation of Angular reactive form. All I want to achieve is validate passwords fields in my form and in case success redirect toward another component ('/customers') or in case that passwords are not the same we should do nothing and give a chance to the user to retype proper passwords again.

RegistrationComponent.ts


@Component({
  selector: 'app-registration',
  templateUrl: './registration.component.html',
  styleUrls: ['./registration.component.css']
})
export class RegistrationComponent implements OnInit {

  registerForm: FormGroup;
  submitted = false;
  email$: Observable<string>;

  constructor(private formBuilder: FormBuilder, private customerService: CustomerService, private router: Router) {
  }

  ngOnInit() {

    this.registerForm = this.formBuilder.group({
      username: ['', Validators.required],
      email: ['', Validators.required],
      name: ['', Validators.required],
      surname: ['', Validators.required],
      phoneNumber: ['', Validators.required],
      nip: ['', Validators.required],
      password: ['', Validators.required],
      confirmPassword: ['', Validators.required],
    }, {
      validator: MustMatch('password', 'confirmPassword')
    });
  }

  get form() {
    return this.registerForm.controls;
  }

  onSubmit() {
    this.submitted = true;

    const registeredCustomer: RegistrationForm = {

      username: this.registerForm.controls.username.value,
      email: this.registerForm.controls.email.value,
      name: this.registerForm.controls.name.value,
      surname: this.registerForm.controls.surname.value,
      phoneNumber: this.registerForm.controls.phoneNumber.value,
      password: this.registerForm.controls.password.value,
      confirmPassword: this.registerForm.controls.confirmPassword.value,

    };


    this.email$ = this.customerService
    .register(registeredCustomer)
    .pipe(map(customer => customer.email));

    if (this.registerForm.invalid) {
      return;
    } else {
      setTimeout(() => this.router.navigate((['/customers'])), 5000);
    }

    alert('User successfully registered' + JSON.stringify(this.registerForm.value));

  }

}


RegistrationComponent.html

<div class="jumbotron">
  <div class="container">
    <div class="row">
      <div class="col-md-6 offset-md-3">
        <h3>Fill in the form below to complete the registration process </h3>
        <form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
          <div class="form-group">
            <label>Username</label>
            <input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && form.username.errors }" />
            <div *ngIf="submitted && form.username.errors" class="invalid-feedback">
              <div *ngIf="form.username.errors.required">Username is required</div>
            </div>
          </div>
          <div class="form-group">
            <label>Name</label>
            <input type="text" formControlName="name" class="form-control" [ngClass]="{ 'is-invalid': submitted && form.name.errors }" />
            <div *ngIf="submitted && form.name.errors" class="invalid-feedback">
              <div *ngIf="form.name.errors.required">Customer name is required</div>
            </div>
          </div>
          <div class="form-group">
            <label>Surname</label>
            <input type="text" formControlName="surname" class="form-control" [ngClass]="{ 'is-invalid': submitted && form.surname.errors }" />
            <div *ngIf="submitted && form.surname.errors" class="invalid-feedback">
              <div *ngIf="form.surname.errors.required">Customer surname is required</div>
            </div>
          </div>
          <div class="form-group">
            <label>Phone Number</label>
            <input type="text" formControlName="phoneNumber" class="form-control" [ngClass]="{ 'is-invalid': submitted && form.phoneNumber.errors }" />
            <div *ngIf="submitted && form.phoneNumber.errors" class="invalid-feedback">
              <div *ngIf="form.phoneNumber.errors.required">Phone number is required</div>
            </div>
          </div>
          <div class="form-group">
            <label>Email</label>
            <input type="text" formControlName="email" class="form-control" [ngClass]="{ 'is-invalid': submitted && form.email.errors }" />
            <div *ngIf="submitted && form.email.errors" class="invalid-feedback">
              <div *ngIf="form.email.errors.required">Email is required</div>
              <div *ngIf="form.email.errors.email">Email must be a valid email address</div>
            </div>
          </div>
          <div class="form-group">
            <label>Password</label>
            <input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && form.password.errors }" />
            <div *ngIf="submitted && form.password.errors" class="invalid-feedback">
              <div *ngIf="form.password.errors.required">Password is required</div>
              <div *ngIf="form.password.errors.minlength">Password must be at least 6 characters</div>
            </div>
          </div>
          <div class="form-group">
            <label>Confirm Password</label>
            <input type="password" formControlName="confirmPassword" class="form-control" [ngClass]="{ 'is-invalid': submitted && form.confirmPassword.errors }" />
            <div *ngIf="submitted && form.confirmPassword.errors" class="invalid-feedback">
              <div *ngIf="form.confirmPassword.errors.required">Confirm Password is required</div>
              <div *ngIf="form.confirmPassword.errors.mustMatch">Passwords must match</div>
            </div>
          </div>
          <div class="form-group">
            <button class="btn btn-primary">Register</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>


I was based on this tutorial: Registration tutorial

but after small modification, my current solution is not working properly.

I was trying to replace this

    setTimeout(() => this.router.navigate((['/customers'])), 5000);

infront of:

    if (this.registerForm.invalid) {
      return;
    }

and checking if boolean value submitted = false but it didn't yield the desired result.

Thank you in advance for all your help.

Edit: Stackblitz link for application.

component

stackblitz editor

We've been working at work on a library to handle forms easily and with also more type safety (for both TS and HTML!). This library is called ngx-sub-form and you can find it here: https://github.com/cloudnc/ngx-sub-form

Using ngx-sub-form , I've made a demo of your app that you can try here:
https://stackblitz.com/edit/angular-genpiv

Now, some explanations.

First, we want to define a proper interface for your form (type safety for the win!):

export interface RegistrationForm {
  username: string;
  email: string;
  name: string;
  surname: string;
  phoneNumber: string;
  nip: string;
  password: string;
  confirmPassword: string;
}

Then we create all the components we need for the app:
- app/app.component.ts
- app/registration/registration.component.ts
- app/registration/registration-form/registration-form.component.ts
- app/customers.component.ts

Now we need to define the routing for our app. Our app.module.ts should look like the following:

const ROUTES: Routes = [
  {
    path: '',
    redirectTo: '/registration',
    pathMatch: 'full'
  },
  {
    path: 'registration',
    component: RegistrationComponent
  },
  {
    path: 'customers',
    component: CustomersComponent
  },
];

@NgModule({
  imports: [BrowserModule, ReactiveFormsModule, RouterModule.forRoot(ROUTES)],
  declarations: [AppComponent, RegistrationFormComponent, RegistrationComponent, CustomersComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

And app.component.html should simply be <router-outlet></router-outlet> .

RegistrationComponent will act as a smart component. It will inject the router and just wait for the form to be sent. That component doesn't want to be aware of the form itself, just the object that has been sent.

@Component({
  selector: 'app-registration',
  templateUrl: './registration.component.html',
  styleUrls: ['./registration.component.css']
})
export class RegistrationComponent {
  constructor(private router: Router) {}

  public register(registrationForm: RegistrationForm): void {
    // here you probably want to inject a `RegistrationService` instead of the `Router`
    // and call a method that will make an XHR call to your backend and on success
    // would do that `navigate` from the service too
    // for the sake of simplicity here I'm just doing the navigate directly
    this.router.navigate(['/customers']);
  }
}

And its HTML:

<app-registration-form (register)="register($event)"></app-registration-form>

RegistrationFormComponent will be the component responsible to handle the form. That component is the only one that needs to use ngx-sub-form library.

@Component({
  selector: 'app-registration-form',
  templateUrl: './registration-form.component.html',
  styleUrls: ['./registration-form.component.css']
})
export class RegistrationFormComponent extends NgxSubFormComponent<RegistrationForm> {
  @Output() register: EventEmitter<RegistrationForm> = new EventEmitter();

  protected getFormControls(): Controls<RegistrationForm> {
    return {
      username: new FormControl(null, [Validators.required]),
      email: new FormControl(null, [Validators.required]),
      name: new FormControl(null, [Validators.required]),
      surname: new FormControl(null, [Validators.required]),
      phoneNumber: new FormControl(null, [Validators.required]),
      nip: new FormControl(null, [Validators.required]),
      password: new FormControl(null, [Validators.required]),
      confirmPassword: new FormControl(null, [Validators.required]),
    }
  }

  protected getFormGroupControlOptions(): FormGroupOptions<RegistrationForm> {
    return {
      validators: [
        formGroup => {
          if (formGroup.value.password !== formGroup.value.confirmPassword) {
            return {
              passwordsMustMatch: true,
            };
          }

          return null;
        },
      ],
    };
  }

  public onSubmit(): void {
    this.register.emit(this.formGroupValues)
  }
}

Things to notice here:

  • export class RegistrationFormComponent extends NgxSubFormComponent<RegistrationForm> we're extending NgxSubFormComponent and we pass our form interface. That will provide us a lot utilities and also some type safety

  • The method getFormControls expects you to basically provide an object to create your form. I think it's self explanatory because it looks like the object you'd pass when creating a FormGroup

  • getFormGroupControlOptions is a hook provided by NgxSubFormComponent that allows you to set validators or asyncValidators at the FormGroup level

  • Finally, the onSubmit method is the one that'll be called when the user clicks on the Register button (once the form is fully valid)

Now, the last missing piece is the form HTML (for simplicity I'll only display in the response the first field and the password check because everything in between is pretty much the same)

<div class="jumbotron">
  <div class="container">
    <div class="row">
      <div class="col-md-6 offset-md-3">
        <h3>Fill in the form below to complete the registration process</h3>
        <form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
          <div class="form-group">
            <label>Username</label>
            <input type="text" [formControlName]="formControlNames.username" class="form-control" [ngClass]="{ 'is-invalid': formGroupErrors?.username }" />
            <div *ngIf="formGroupErrors && formGroupControls.username.touched" class="invalid-feedback">
              <div *ngIf="formGroupErrors?.username?.required">Username is required</div>
            </div>
          </div>

          ...


          <div class="form-group">
            <label>Confirm Password</label>
            <input type="text" [formControlName]="formControlNames.confirmPassword" class="form-control" [ngClass]="{ 'is-invalid': formGroupErrors?.confirmPassword }" />
            <div *ngIf="formGroupErrors && formGroupControls.confirmPassword.touched" class="invalid-feedback">
              <div *ngIf="formGroupErrors?.confirmPassword?.required">Confirm Password is required</div>
              <div *ngIf="formGroupErrors?.formGroup?.passwordsMustMatch">Passwords must match</div>
            </div>
          </div>

          <div class="form-group">
            <button class="btn btn-primary" [disabled]="formGroup.invalid">Register</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>

Things to notice from the HTML:

  • <form [formGroup]="formGroup" we haven't defined the formGroup ourselves. It's been created by NgxSubFormComponent

  • <input type="text" [formControlName]="formControlNames.username" the formControlNames property is also defined by NgxSubFormComponent and its main purpose is to bring some type safety! If you try to put formControlNames.random you'll get a run time error, but also a Typescript error when compiling with AOT!

  • <div *ngIf="formGroupErrors?.username?.required"> the property formGroupErrors is also provided by NgxSubFormComponent and gives you access to the form errors. Best part is that it works with nested sub forms and is also type safe! Even though here we don't have any sub form, I invite you to check the Github page of ngx-sub-form to learn more about that

The app is now fully ready and has a lot of type safety! Any refactor impacting the main interface will also require you to update the form (TS + HTML) otherwise Typescript will throw errors around.

Here's how it looks like:

在此处输入图片说明

Then once it's valid:

在此处输入图片说明

And then when we click on register:

在此处输入图片说明

Don't forget to check out the live example here: https://stackblitz.com/edit/angular-genpiv

Edit:

If you want to go further, I've just published a blog post to explain a lot of things about forms and ngx-sub-form here https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9

To activate the routing you must inject the routing module in the app.module.ts (ie root module). Also define the path for the route in your routing module file as :

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
    {path: 'customers' , component: CustomersComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Aslo don't forgrt to include <router-outlet></router-outlet> tag because its where the child view will be loaded after the routing is successfull.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM