Angular 2 – large scale application forms’ handling

I commented elsewhere about @ngrx/store, and while I still recommend it, I believe I was misunderstanding your problem slightly.

Anyway, your FormsControlService is basically a global const. Seriously, replace the export class FormControlService ... with

export const formControlsDefinitions = {
   // ...
};

and what difference does it make? Instead of getting a service, you just import the object. And since we’re now thinking of it as a typed const global, we can define the interfaces we use…

export interface ModelControl<T> {
    name: string;
    validators: ValidatorFn[];
}

export interface ModelGroup<T> {
   name: string;
   // Any subgroups of the group
   groups?: ModelGroup<any>[];
   // Any form controls of the group
   controls?: ModelControl<any>[];
}

and since we’ve done that, we can move the definitions of the individual form groups out of the single monolithic module and define the form group where we define the model. Much cleaner.

// personal_details.ts

export interface PersonalDetails {
  ...
}

export const personalDetailsFormGroup: ModelGroup<PersonalDetails> = {
   name: 'personalDetails$';
   groups: [...]
}

But now we have all these individual form group definitions scattered throughout our modules and no way to collect them all 🙁 We need some way to know all the form groups in our application.

But we don’t know how many modules we’ll have in future, and we might want to lazy load them, so their model groups might not be registered at application start.

Inversion of control to the rescue! Let’s make a service, with a single injected dependency — a multi-provider which can be injected with all our scattered form groups when we distribute them throughout our modules.

export const MODEL_GROUP = new OpaqueToken('my_model_group');

/**
 * All the form controls for the application
 */
export class FormControlService {
    constructor(
        @Inject(MMODEL_GROUP) rootControls: ModelGroup<any>[]
    ) {}

    getControl(name: string): AbstractControl { /etc. }
}

then create a manifest module somewhere (which is injected into the “core” app module), building your FormService

@NgModule({
   providers : [
     {provide: MODEL_GROUP, useValue: personalDetailsFormGroup, multi: true}
     // and all your other form groups
     // finally inject our service, which knows about all the form controls
     // our app will ever use.
     FormControlService
   ]
})
export class CoreFormControlsModule {}

We’ve now got a solution which is:

  • more local, the form controls are declared alongside the models
  • more scalable, just need to add a form control and then add it to the manifest module; and
  • less monolithic, no “god” config classes.

Leave a Comment