Skip to content

Improvements & DRY Code

To reduce the amount of boilerplate needed with each component and to improve maintainability, you can set up a few helper objects. This way, should anything change, you only need to update one file.

The exact naming of each helper really is up to you.

ControlContainer is required for all controls and groups that will be used within ngx-formwork. Injection of the control container allows the components to use reactive forms functionality, without needing to pass the form group through inputs and wrapping the template into additional tags. See this YouTube Video for more detailed explanation: How to Make Forms in Angular REUSABLE (Advanced, 2023)

control-container.view-provider.ts
export const controlContainerViewProviders = [
{
provide: ControlContainer,
useFactory: () => inject(ControlContainer, { skipSelf: true }),
},
];
text-control.component.ts || group.component.ts
@Component({
// Other component decorator options
viewProviders: controlContainerViewProviders,
})

This is a convenience helper to apply the NgxfwControlDirective.

control.host-directive.ts
export const ngxfwControlHostDirective = {
directive: NgxfwControlDirective,
inputs: ['content', 'name'],
};

Use it like this:

text-control.component.ts
@Component({
// Other component decorator options
hostDirectives: [
// Apply here
ngxfwControlHostDirective
],
})

This is a convenience helper to apply the NgxfwGroupDirective.

group.host-directive.ts
export const ngxfwGroupHostDirective = {
directive: NgxfwGroupDirective,
inputs: ['content', 'name'],
};

Use it like this:

group.component.ts
@Component({
// Other component decorator options
hostDirectives: [
// Apply here
ngxfwGroupHostDirective
],
})

For official documentation of Union Types checkout the official docs.

Setting up a union type for your own controls is highly recommended, as it gives you much better type safety, when writing your forms in TypeScript.

export type MyAppControls = TestTextControl | TestGroup | InfoBlock;

Registering all controls. validators, etc. directly in the app.config.ts is not ideal. Setup dedicated files for your registrations.

Create a file with the following content, at whatever location makes sense.

controls.registerations.ts
export const componentRegistrations: ComponentRegistrationConfig = {
'text-control': TextControlComponent,
group: GroupComponent,
info: InfoBlockComponent,
// more regsitrations...
};

In app.config.ts use it like this

app.config.ts
import { componentRegistrations } from './controls.registerations.ts';
export const appConfig: ApplicationConfig = {
providers: [
// other providers
provideFormwork({
componentRegistrations
})
]
};

Create a file with the following content, at whatever location makes sense. You can also further split the files between sync and async validators

validators.registerations.ts
export const validatorRegistrations: ValidatorConfig<RegistrationRecord> = {
'min-chars': [Validators.minLength(3)],
letter: [letterValidator],
combined: ['min-chars', Validators.required, 'letter'],
'no-duplicates': [noDuplicateValuesValidator],
'forbidden-letter-a': [forbiddenLetterAValidator],
};
export const asyncValidatorRegistrations: AsyncValidatorConfig<RegistrationRecord> = {
async: [asyncValidator],
'async-group': [asyncGroupValidator],
};

In app.config.ts use it like this

app.config.ts
import { validatorRegistrations, asyncValidatorRegistrations } from './validators.registerations.ts';
export const appConfig: ApplicationConfig = {
providers: [
// other providers
provideFormwork({
validatorRegistrations,
asyncValidatorRegistrations,
})
],
};
misspelled.validators.registerations.ts
export const validatorRegistrations: ValidatorConfig<RegistrationRecord> = {
letter: [letterValidator],
// ⚠️ letter only spelled with one T.
// This will give an TS error in the provideFormwork function, but not in this case
combined: [Validators.required, 'leter'],
};