loading...

Implement Cross Component Validation in Angular Forms using Akita

arielgueta profile image Ariel Gueta ・3 min read

In this article, I want to show you how you can use AkitaFormsManager plugin and achieve the same result (and more) described in this ngrx article.

The Plugin Abilities

  • Persist Forms - Automatically saves the current control value and update the form value according to the value in the store when the user navigates back to the form.

  • Provides an API so we can query a form’s values and properties from anywhere. This can be useful for things like multi-step forms, cross-component validation, and more.

Installation

npm i @datorama/akita-ng-forms-manager

Create the Routes

We will have two pages, one is PersonPage and the second is ConfigPage:

const routes: Routes = [
  {
    path: '',
    component: PersonComponent,
    pathMatch: 'full'
  },
  {
    path: 'config',
    component: ConfigComponent
  }
];

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

The Person Component

Now let's create the PersonComponent form component:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { AkitaNgFormsManage } from '@datorama/akita-ng-forms-manager';

@Component({
  selector: 'person-form',
})
export class HomeComponent implements OnInit, OnDestroy {
  person = new FormGroup({
    firstName: new FormControl(),
    lastName: new FormControl(),
    age: new FormControl(21, Validators.required)
  });

  constructor(private formsManager: AkitaNgFormsManager) {}

  ngOnInit() {
    this.formsManager.upsert('person', this.person);
  }

  ngOnDestroy() {
    this.formsManager.unsubscribe();
  }
}

and the template:

<form [formGroup]="person">
  <input formControlName="firstName" placeholder="First Name" />
  <input formControlName="lastName" placeholder="Last Name" />
  <input
    formControlName="age"
    placeholder="Age"
    type="number"
    [ngStyle]="person.invalid && { border: '1px solid red' }"
  />
</form>

My favorite part of this plugin is that we still work with the existing Angular API to create our form.

We created a basic reactive form group which contains the person's details. We are injecting the AkitaNgFormsManager and calling the upsert method, giving it the form name and the formGroup.

From that point on, AkitaNgFormsManager will track the form value changes and update the store accordingly. It also persists the form state when the user navigates back to the form.

We can also see everything in the dev-tools:

The Config Component

Now, let's create the ConfigComponent:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { AkitaNgFormsManager } from '@datorama/akita-ng-forms-manager';

@Component({
  selector: 'app-config',
  templateUrl: './config.component.html',
  styleUrls: ['./config.component.css']
})
export class ConfigComponent implements OnInit, OnDestroy {
  config = new FormGroup({
    minAge: new FormControl(21)
  });

  constructor(private formsManager: AkitaNgFormsManager) {}

  ngOnInit() {
    this.formsManager.upsert('config', this.config);
  }

  ngOnDestroy() {
    this.formsManager.unsubscribe();
  }
}

Same as before, we create a FormGroup which contains one FormControl - the minimum age that the user is required to type at the PersonComponent form. We also register the form with the AkitaNgFormsManager so we can query it from any other component in our application.

Cross Component Validation

Now, let's go back to the PersonComponent and see how we can make the age control validation dynamic based on the minAge control value:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { AkitaNgFormsManager, setValidators } from '@datorama/akita-ng-forms-manager';

export class HomeComponent implements OnInit, OnDestroy {
  person = new FormGroup({
    firstName: new FormControl(),
    lastName: new FormControl(),
    age: new FormControl(21, Validators.required)
  });

  dispose;

  constructor(private formsManager: AkitaNgFormsManager) {}

  ngOnInit() {
    this.formsManager.upsert('person', this.person);

    this.dispose = this.formsManager.selectValue<number>('config', 'minAge')
      .subscribe(minAge => {
        setValidators(this.person.get('age'), [Validators.min(minAge)]);
      });
  }

  ngOnDestroy() {
    this.formsManager.unsubscribe();
    this.dispose.unsubscribe();
  }
}

We can query any form that is registered in AkitaNgFormsManager. In our case, we use the selectValue method passing the form name and the form field we need. Note that we also can infer the value using a generic.

Now the only thing that is left is to call the setValidators method (provides by the plugin) passing the control and a Validators array.

To preserve existing validators, we can do:

this.dispose = this.formsManager.selectValue<number>('config', 'minAge')
 .subscribe(minAge => {
  const age = this.person.get('age');
  const validators = age.validator;
  setValidators(age, [...validators, Validators.min(minAge)]);
});

Read more about the plugin in the docs.

Posted on by:

Discussion

markdown guide