Skip to content

Commit

Permalink
feat(core): add preset module to enable form config re-use (#3256)
Browse files Browse the repository at this point in the history
fix #3208
  • Loading branch information
MaxKless committed May 21, 2022
1 parent a44f2a5 commit 597f5fa
Show file tree
Hide file tree
Showing 19 changed files with 502 additions and 27,200 deletions.
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'core',
'testing',
'select',
'preset',
'schematics',
'json-schema',
'material',
Expand Down
2 changes: 2 additions & 0 deletions demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class AppComponent implements OnInit {
{ path: '/guide/custom-formly-field', title: 'Custom Type' },
{ path: '/guide/custom-formly-wrapper', title: 'Custom Wrapper' },
{ path: '/guide/custom-formly-extension', title: 'Custom Extension' },
{ path: '/guide/formly-field-presets', title: 'Formly Field Presets' },
],
},
{
Expand Down Expand Up @@ -100,6 +101,7 @@ export class AppComponent implements OnInit {
{ path: '/examples/other/button', title: 'Button Type' },
{ path: '/examples/other/json-powered', title: 'JSON powered' },
{ path: '/examples/other/input-file', title: 'File input' },
{ path: '/examples/other/presets', title: 'Presets' },
],
},
];
Expand Down
4 changes: 4 additions & 0 deletions demo/src/app/examples/examples.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ import { SharedModule } from '../shared';
path: 'input-file',
loadChildren: () => import('./other/input-file/config.module').then((m) => m.ConfigModule),
},
{
path: 'presets',
loadChildren: () => import('./other/presets/config.module').then((m) => m.ConfigModule),
},
],
},
],
Expand Down
3 changes: 3 additions & 0 deletions demo/src/app/examples/other/presets/app.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<form [formGroup]="form">
<formly-form [model]="model" [fields]="fields" [options]="options" [form]="form"></formly-form>
</form>
29 changes: 29 additions & 0 deletions demo/src/app/examples/other/presets/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormlyFormOptions, FormlyFieldConfig } from '@ngx-formly/core';

@Component({
selector: 'formly-app-example',
templateUrl: './app.component.html',
})
export class AppComponent {
form = new FormGroup({});
model: any = {};
options: FormlyFormOptions = {};

fields: FormlyFieldConfig[] = [
{
type: '#salutation',
},
{
type: '#firstName',
},
{
type: '#lastName',
props: {
label: 'Last Name!!!!',
required: true,
},
},
];
}
58 changes: 58 additions & 0 deletions demo/src/app/examples/other/presets/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { FormlyModule, FORMLY_CONFIG } from '@ngx-formly/core';
import { FormlyBootstrapModule } from '@ngx-formly/bootstrap';
import { MatTabsModule } from '@angular/material/tabs';

import { AppComponent } from './app.component';
import { FormlyPresetModule } from 'src/core/preset/src/preset.module';
import { registerSalutationPreset, SALUTATION_OPTIONS } from './salutation.preset';

@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
MatTabsModule,
FormlyBootstrapModule,
FormlyPresetModule,
FormlyModule.forRoot({
presets: [
{
name: 'firstName',
config: {
key: 'firstName',
type: 'input',
props: {
label: 'First Name',
},
},
},
{
name: 'lastName',
config: {
key: 'lastName',
type: 'input',
props: {
label: 'Last Name',
},
},
},
],
}),
],
declarations: [AppComponent],
providers: [
{
provide: SALUTATION_OPTIONS,
useValue: ['Mr.', 'Ms.', 'Dr.', 'Dude'],
},
{
provide: FORMLY_CONFIG,
useFactory: registerSalutationPreset,
deps: [SALUTATION_OPTIONS],
multi: true,
},
],
})
export class AppModule {}
49 changes: 49 additions & 0 deletions demo/src/app/examples/other/presets/config.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule, ExamplesRouterViewerComponent } from '../../../shared';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
imports: [
SharedModule,
AppModule,
RouterModule.forChild([
{
path: '',
component: ExamplesRouterViewerComponent,
data: {
examples: [
{
title: 'Field Presets',
description: `
This example demonstrates how to use the <code>preset</code> feature.
The displayed fields are all standard fields that are defined locally and then re-used in the form.
Check out the salutation field to get an idea of how to create smart, self-configuring presets with dependencies.
`,
component: AppComponent,
files: [
{
file: 'app.component.html',
content: require('!!highlight-loader?raw=true&lang=html!./app.component.html'),
filecontent: require('!!raw-loader!./app.component.html'),
},
{
file: 'app.component.ts',
content: require('!!highlight-loader?raw=true&lang=typescript!./app.component.ts'),
filecontent: require('!!raw-loader!./app.component.ts'),
},
{
file: 'app.module.ts',
content: require('!!highlight-loader?raw=true&lang=typescript!./app.module.ts'),
filecontent: require('!!raw-loader!./app.module.ts'),
},
],
},
],
},
},
]),
],
})
export class ConfigModule {}
31 changes: 31 additions & 0 deletions demo/src/app/examples/other/presets/salutation.preset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Inject, Injectable, InjectionToken } from '@angular/core';
import { ConfigOption, FormlyFieldConfig } from '@ngx-formly/core';

export const SALUTATION_OPTIONS = new InjectionToken<string[]>('SALUTATION_OPTIONS');

@Injectable()
export class SalutationPresetProvider {
constructor(@Inject(SALUTATION_OPTIONS) private salutationOptions: string[]) {}
getConfiguration(): FormlyFieldConfig {
return {
key: 'salutation',
type: 'select',
props: {
label: 'Salutation',
placeholder: 'Please Select',
options: this.salutationOptions.map((salutation) => ({ label: salutation, value: salutation })),
},
};
}
}

export function registerSalutationPreset(salutationOptions: string[]): ConfigOption {
return {
presets: [
{
name: 'salutation',
config: new SalutationPresetProvider(salutationOptions),
},
],
};
}
67 changes: 67 additions & 0 deletions demo/src/app/guides/formly-field-presets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Formly Field Presets
With formly templates, wrappers and extensions, it's easy to reuse form logic throughout your app. However, in larger projects, you will often find yourself defining the same things over and over; think of common form fields like email, name or password fields which should be the same throughout your app.

The `@ngx-formly/core/preset` enables you to define reusable `FormlyFieldConfig`s centrally.

## Defining a Preset
To define a preset, two steps are necessary:
- Importing the `FormlyPresetModule`
- Providing presets to `FormlyConfig`

Have a look at the following example which defines a simple `firstName` preset:

```typescript
@NgModule({
imports: [
...
FormlyPresetModule,
FormlyModule.forRoot({
presets: [
{
name: 'firstName',
config: {
key: 'firstName',
type: 'input',
props: {
label: 'First Name',
},
},
},
],
}),
],
})
export class AppModule {}

```

It is also possible to define more complex presets by using a `FormlyFieldConfigPresetProvider` (which contains only a single `getConfiguration` method). This enables presets that contain dependencies and can be self-configuring.
Refer to [Example: Presets](https://formly.dev/examples/other/presets) to see this behavior in action.

## Using Presets
After you have defined a preset, it's very easy to use them in your forms. Presets are accessed using so-called *pseudo-types*, which are prefixed with a single `#`:

```typescript
fieldConfig = [
...
{
type: '#firstName'
}
]
```

This will substitute all properties with those defined in the preset.

To reuse fields while overriding only certain properties, simply specify those properties:

```typescript
fieldConfig = [
...
{
type: '#firstName'
props: {
label: 'alternative label'
}
}
]
```
1 change: 1 addition & 0 deletions demo/src/app/guides/guides.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class GuidesComponent implements OnInit {
'custom-formly-wrapper': require('!!raw-loader!!highlight-loader!markdown-loader!./custom-formly-wrapper.md'),
validation: require('!!raw-loader!!highlight-loader!markdown-loader!./validation.md'),
'expression-properties': require('!!raw-loader!!highlight-loader!markdown-loader!./expression-properties.md'),
'formly-field-presets': require('!!raw-loader!!highlight-loader!markdown-loader!./formly-field-presets.md'),
};

constructor(private renderer: Renderer2, private route: ActivatedRoute, private elementRef: ElementRef) {}
Expand Down
Loading

0 comments on commit 597f5fa

Please sign in to comment.