0.0.8 • Published 2 years ago

ngx-mat-form-wizard v0.0.8

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

alt text

Description

Ngx mat form wizard is a lightweight, fast and intuitive JSON-powered template-driven based machine that allows Angular developers to easily create and handle highly complex forms using a JSON schema.

Among the rest, the wizard includes some significant features:

  • Responsive - It has an entire responsive grid that supports custom breakpoint, which is used to adjust your forms to any device.
  • Auto-scroll - A built-in automatic smooth-scroll to an invalid field or group on submit attempt.
  • Error management - The ability to control when your validations errors take effect.
  • Nested forms - Divide your form into subforms(groups), and manage each of them individually.
  • Events handling - Inject and use the powerful built-in "WizardEventBus", an RxJS subjects-based service that allows subscribing to any event that occurs in the wizard at run time.
  • Custom validations - Implement any sync or async custom validation in the world for any input or group, using the unique "VALIDATE" event.
  • Multiple validations - Provide multiple different error messages for each validation scenario, both for field or a group.

Out of the box functionality and styling is provided for Angular Material.

Motivation

During my career as a frontend software engineer, I have noticed how headache it can be to implement forms in your application. It requires quite a bit:

  • Architecture - The endless comparison between the "template-driven" approach and the "reactive forms" can be frustrating sometimes. It can cost some precious time deciding what method suits your application the best, while both are not easy to learn for new developers.
  • Validations - Implementation of complex validations can be a heavy task in some cases, and almost every form requires some of them. Wouldn't that be awesome if you only had some mechanism that could do this job for you?
  • Infrastructure - Nowadays, almost every web application can also be used on our mobile devices. UI developers need to spend more time creating responsive forms, which can be adjusted to all kinds of screen sizes, and this was never a simple mission.
  • Style - Creating a form isn't enough! It needs to be good-looking, attractive, and user-friendly. Styling your inputs to a professional level on your own can be challenging work! That's why I chose to make this package "Angular Material" Style based.

For such reasons, I decided to create this wizard and help developers benefit from my knowledge.

Table of contents

Demo and source code

Click here for a demo application on Heroku.\ Click here to access the demo application's repository on GitHub, which you can also clone and run locally on your machine.

Installation

So first thing first, install the wizard in your project:

npm install ngx-mat-form-wizard

Or

yarn add ngx-mat-form-wizard

Setup

First, import the NgxMatFormWizardModule into your target module:

import { NgxMatFormWizardModule } from 'ngx-mat-form-wizard';
imports: [
    NgxMatFormWizardModule,
    // The rest of your imports ...
],
export class MyTargetModule { }

Next, choose one of Angular material themes and import it into your global styles file.\ You may choose one of the followings: "purple-green", "pink-bluegrey", "indigo-pink", "deeppurple-amber"

@import "~@angular/material/prebuilt-themes/indigo-pink";

Click here to learn more about Angular material theming system.

Note: Don't forget to import BrowserAnimationsModule into your AppModule to enable Angular's animation system. Declining this will disable most of Angular Material's animations.

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
imports: [
    BrowserAnimationsModule,
    // The rest of your imports ...
],
export class AppModule { }

And that's it!!, your all setup and good to go ✌️

Usage

In your HTML file:

<ngx-mat-form-wizard
    [groups]="groups"
    [responsive]="true"
    [autoscroll]="true"
    [breakpoint]="625"
    [errorState]="'when-submitted'"
    (unsubmit)="handleUnsubmit($event)"
    (onsubmit)="handleSubmit($event)">
</ngx-mat-form-wizard>

The ngx-mat-form-wizard component

Hierarchy and technology

The wizard is based on the "Template Driven" technology and uses "Angular Material" as its source of functionality and styling.

In HTML terms, the wizard is one big <form> tag that is built from three fundamental core elements:

  • Group - A <form> tag inside the wizard represented by the IWizGroup object.
  • Container - A row/column inside the group represented by the IWizContainer object.
  • Field - A input component inside the container represented by the IWizField object.

alt text

Parameters

ParametertypeDescriptionKindRequiredDefault
idstringAttach an ID for this wizard.inputfalsenull
submitTextstringSets the text for the submit button.inputfalse'Submit'
fontFamilystringSets the font family of the wizard.inputfalseinherit
fontSizestringSets the font size of the wizard.inputfalseinherit
errorStatestateSets the error state of the wizard.inputfalsewhen-submitted
breakpointnumberSets the breaking point for a responsive wizard. (in pixels)inputfalse600
dirDirectionDetermine the direction of this wizard.inputfalseltr
offsetnumberPixels to offset from the top of the element when scrolling to.inputfalse15
autoscrollbooleanWhen true, the wizard will support an automatic smooth-scroll to an invalid input on submit attempt.inputfalsefalse
responsivebooleanWhen true, this wizard will be responsive.inputfalsefalse
hideSubmitbooleanWhen true, there will be no submit button for the wizard.inputfalsefalse
groupsArray<IWizGroup>An array of IWizGroup objects.inputfalse[]
onchangeEventEmitter<IWizGroup[]>Fires whenever some change occurs in the wizard.output
onsubmitEventEmitter<IWizGroup[]>Fires whenever the wizard is successfully submitted.output
unsubmitEventEmitter<IWizGroup[]>Fires whenever the wizard fails to submit.output

API

remoteSubmit(): void - Use this function to submit the wizard remotely.

Models

The IWizGroup model

The IWizGroup object represents a group (<form>) inside the wizard. The group can be used to divide your form into sections, while each group is independent and can be submitted and validated individually.

IWizGroup properties are: | Property | type | Description | Required | Default | :---------- | :----------- | :----------------------------------------| :----------- | :----------- | | id | string | Related unique ID of this form group. | false | null | | validation | IWizValidation | A validation notification to show when the group is invalid. | false | null | headline | string | Related title for this form group. | false | null | | style | style | Sets the border style of the group. | false | solid | | frameless | boolean | When true, this group will have no frame surrounding it, which means no padding or border. | false | false | | color | string | Sets the color of the title and border. | false | border: #000000, title: inherit | | events | boolean | When true, this entity will broadcast its events. | false | false | | containers | Array<IWizContainer> | An array of IWizContainer objects. | true | null |


The IWizContainer model

The IWizContainer object represents a row/column inside a group. It holds the input fields in horizontal or vertical view depending on the breaking point.

IWizContainer properties are: | Property | type | Description | Required | Default | :---------- | :----------- | :----------------------------------------| :----------- | :----------- | | id | string | Related unique ID of this container. | false | null | | fields | Array<IWizField> | An array of IWizField objects. | true | null |


The IWizField model

The IWizField object represents an input field inside a container.

IWizField properties are: | Property | type | Description | Required | Default | :---------- | :------------ | :----------------------------------------| :----------- | :----------- | | type | field | Related type of the form field. | true | null | | id | string | Related unique ID of the given field. | true | null | | placeholder | string | Related placeholder. | false | null | | innerPlaceholder | string | Related placeholder for a nested field. | false | 'Search...' | | validation | IWizValidation | A validation notification to show when the field is invalid. | false | null | | hint | string | Provide some guidelines regarding this field. | false | null | | dosubmit | boolean | When true, this button will submit its form or focus an invalid field. (applied for button field only) | false | false | | theme | theme | Select a style from the material design themes. (applied for button field only) | false | primary | | files | Array | An array of File objects. | false | null | | items | Array<IWizItem> | An array of IWizItem objects. | false | null | | selecteds | Array | An array of numbers represents the selected IWizItem objects. (applied in multiple selection fields only) | false | null | | selected | number | The ID of the selected IWizItem object. (applied in single selection fields only) | false | null | | value | string || number || boolean | Related value of the given field. (applied for text, number, or boolean fields) | false | null | | date | Date | Related date of the given date field. | false | null | | accept | string | Enable specific file formats to upload. (applied for files field only) | false | null | | maxFileSize | number | Limit the maximum file size(in Bytes) to upload. (applied for files field only) | false | Infinity | | maxTotalSize | number | Limit the maximum total files size(in Bytes) to upload. (applied for files field only) | false | Infinity | | rows | number | Number of rows for textarea field. | false | null | | cols | number | Number of columns for textarea field. | false | null | | minNumber | number | Validate minimum number for number field. | false | null | | maxNumber | number | Validate maximum number for number field. | false | null | | minLength | number | Validate minimum characters length of a string. (applied to all text fields) | false | null | | maxLength | number | Validate maximum characters length of a string. (applied to all text fields) | false | null | | minItems | number | Validate minimum selected objects. | false | null | | maxItems | number | Validate maximum selected objects. | false | null | | minDate | Date | Limit the minimum date selection for the date field. | false | null | | maxDate | Date | Limit the maximum date selection for the date field. | false | null | | minDateOf | string | Provide an ID of another date field, used to limit the minimum date selection of the current date field by the date value of the other one. | false | null | | maxDateOf | string | Provide an ID of another date field, used to limit the maximum date selection of the current date field by the date value of the other one. | false | null | | pattern | string | Validate a regular expression for the given field. (applied for all text and number fields) | false | null | | matchTo | string | Provide an ID of another field, used to validate a match between the current field's value to the value of the other one. | false | null | | matchWith | string | Provide an ID of another field, used to validate a match between the current field's value with the value of the other one. | false | null | | pending | boolean | When true, the field will show a progress indicator. | false | false | hideQuantity | boolean | When true, the files field will not show the quantity of the selected files. | false | false | multiple | boolean | When true, the files field will provide multiple selections. | false | false | events | boolean | When true, this entity will broadcast its events. | false | false | required | boolean | When true, this field will be required by the form validation. | false | false | disabled | boolean | When true, this field will be frozen and inaccessible. | false | false


The IWizItem model

The IWizItem object represents an interface for a wizard item.

IWizItem properties are: | Property | type | Description | Required | Default | :---------- | :----------- | :----------------------------------------| :----------- | :----------- | | id | number | Related unique ID of this item. | true | null | | name | string | Related label to show for this item. (supports innerHTML) | true | null | | color | string | Sets the color of the lable and icon. | false | #000000 | | icon | string | Choose one of angular material icons to show for this item. (not available in checklists components) | false | null | | disabled | boolean | When true, this item will be frozen and inaccessible. | false | false |


The IWizValidation model

The IWizValidation object represents an interface for all validation types. You can show different error messages for each case or general error messages for all scenarios (using the "default" property).

PropertytypeDescriptionRequiredDefault
minLengthstringProvide an error message to show in case a string is too short.falsenull
maxLengthstringProvide an error message show in case a string is too long.falsenull
minNumberstringProvide an error message to show in case a number is too small.falsenull
maxNumberstringProvide an error message to show in case a number is too large.falsenull
isNanstringProvide an error message to show in case a value is not a number.falsenull
minItemsstringProvide an error message to show in case there are less selected items than required.falsenull
maxItemsstringProvide an error message to show in case there are more selected items than required.falsenull
requireMatchstringProvide an error message to show in case two fields are mismatched.falsenull
maxTotalSizestringProvide an error message to show in case the total size of files is larger than required.falsenull
emailstringProvide an error message to show in case of an invalid email address.falsenull
patternstringProvide an error message to show in case the pattern is incorrect.falsenull
requiredstringProvide an error message to show in case of a missing required value.falsenull
customstringProvide an error message to show in case a custom validation has been activated.falsenull
defaultstringProvide some default error message to show in case there is no specific validation scenario.falsenull

Types

The state type

A state type determines how and when your wizard will show its validation errors. | type | Description | | :------------------------------ | :---------------------------------------| | when-submitted | Show all validation errors only when the form has been submitted. (this is the default behavior) | when-invalid | Instantly show all validation errors of all invalid inputs. | when-touched | Show validation errors when the input has been touched or when the form has been submitted. | when-pristine | Instantly show all validation errors only for pristine inputs or when the form has been submitted. | when-dirty | Show validation error when the input is dirty or when the form has been submitted.


The Direction type

A Direction type determines the wizard's direction and all of its components, and it can be imported from @angular/cdk/bidi.

import { Direction } from '@angular/cdk/bidi';
typeDescription
ltrLeft to right. (this is the default behavior)
rtlRight to left.

The style type

A style type is nothing more than the border style of the group. You can choose one of the following:\ solid | dashed | dotted | none | unset | hidden | double | groove | ridge | inset | outset | initial | inherit


The theme type

The theme type allows you to style your group buttons with one of the following Material styles:\ primary | basic | warn | accent | disabled | link


The field type

The field type is used to determine the type of input you are going to use.\ Now let's discuss each of them individually:

text

The text type is a simple text box where the user can edit strings and numbers.

Applied fields/eventsName
Fieldstype, id, placeholder, validation, hint, value, minLength, maxLength, pattern, matchTo, matchWith, events, required, disabled
EventsCHANGE, FOCUS, VALIDATE
number

The number type is a simple numeric box where only numbers can be typed in.

Applied fields/eventsName
Fieldstype, id, placeholder, validation, hint, value, minNumber, maxNumber, pattern, matchTo, matchWith, events, required, disabled
EventsCHANGE, FOCUS, VALIDATE
password

The password type provide a way to securely enter a password. | Applied fields/events | Name | | :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, value, minLength, maxLength, pattern, matchTo, matchWith, events, required, disabled | Events | CHANGE, FOCUS, VALIDATE

textarea

The textarea type represents a multi-line plain-text editing control, useful when the user wants to enter a sizeable amount of free-form text. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, value, minLength, maxLength, pattern, rows, cols, events, required, disabled | Events | CHANGE, FOCUS, VALIDATE

email

The email type defines a field for an e-mail address. The input value is automatically validated to ensure it is a properly formatted e-mail address. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, value, events, required, disabled | Events | CHANGE, FOCUS, VALIDATE

search

The search type also supports a progress spinner and a delayed CHANGE event(650ms). It is comfortable to use where a search is needed. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, value, pending, minLength, maxLength, pattern, matchTo, matchWith, events, required, disabled | Events | CHANGE, FOCUS, VALIDATE

date

The date type create input field that let the user enter a date, the resulting value includes the year, month, and day. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, date, minDate, maxDate, minDateOf, maxDateOf, events, required, disabled | Events | CHANGE, TOGGLE, VALIDATE

files

The files type enables to upload any kind of file. It also displays the files list and enables files search and selection. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, files, hideQuantity, innerPlaceholder, pending, multiple, accept, maxFileSize, maxTotalSize, minItems, maxItems, events, required, disabled | Events | CHANGE, TOGGLE, SELECT, SEARCH, VALIDATE

single-select

The single-select type represents a control that provides a menu of options and enables a single selection. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, items, selected, events, required, disabled | Events | CHANGE, TOGGLE, VALIDATE

multiple-select

The multiple-select type represents a control that provides a menu of options and enables multiple selections. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, items, selecteds, minItems, maxItems, events, required, disabled | Events | CHANGE, TOGGLE, VALIDATE

autocomplete-single

The autocomplete-single type represents a control that provides a menu of options and enables a single selection with autocomplete search. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, items, selected, innerPlaceholder, pending, events, required, disabled | Events | CHANGE, SEARCH, TOGGLE, VALIDATE

autocomplete-multiple

The autocomplete-multiple type represents a control that provides a menu of options and enables multiple selections with autocomplete search. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, hint, items, selecteds, innerPlaceholder, pending, minItems, maxItems, events, required, disabled | Events | CHANGE, SEARCH, TOGGLE, VALIDATE

checklist-single

The checklist-single type represents a control that provides a list of radio buttons and enables a single selection. (note that the labels are innerHTML) | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, items, selected, events, required, disabled | Events | CHANGE, VALIDATE

checklist-multiple

The checklist-multiple type represents a control that provides a list of checkboxes and enables multiple selections. (note that the labels are innerHTML) | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, validation, items, selecteds, minItems, maxItems, events, required, disabled | Events | CHANGE, VALIDATE

content

The content type is used to show any innerHTML content inside a group. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, value, disabled | Events | no events available

button

The button type represents a clickable button used to submit a group or just for accessible, standard button functionality. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id, placeholder, dosubmit, theme, hint, pending, events, disabled | Events | CLICK

void

The void type is nothing. It can be used to create a blank space between inputs. Note that this element will be ceased to exist on a vertical view. | Applied fields/events | Name |
| :---------------------------| :----------------------------| | Fields | type, id | Events | no events available


Events handling with WizardEventBus

Any Group or Field entity has its events, and it's possible to subscribe to those events on your class constructor using a service called "WizardEventBus". This service uses RxJS subjects as its core functionality. A subject is a special type of Observable that allows values to be multicasted to many Observers. While plain Observables are unicast (each subscribed Observer owns an independent execution of the Observable), Subjects are multicast. You can read more about subjects here.

Field entity event types:

CHANGE\ A CHANGE event occurs every time the input field has changed its value.

Payload

{
    field: IWizField,            // The input field object.
    formControl: formControl,    // The associated form control.
    content: any,                // The new value.
}

FOCUS\ A FOCUS event occurs every time the input field has been focused or blurred.

Payload

{
    field: IWizField,            // The input field object.
    formControl: formControl,    // The associated form control.
    content: FocusEvent          // The focus event.
}

TOGGLE\ A TOGGLE event occurs every time the input field has been toggled.

Payload

{
    field: IWizField,            // The input field object.
    formControl: formControl,    // The associated form control.
    content: boolean             // Determine whether the input is open/closed.
}

SEARCH:\ A SEARCH event occurs when there is a change in the value of an inner search field.

Payload

{
    field: IWizField,            // The input field object.
    formControl: formControl,    // The associated form control.
    content: string              // The search keyword of the inner search field.
}

SELECT\ A SELECT event occurs when a file is selected from the dropdown menu of the files input.

Payload

{
    field: IWizField,            // The input field object.
    formControl: formControl,    // The associated form control.
    content: File                // The selected file object.
}

CLICK\ A CLICK event can occur by clicking a group button or by clicking an innerHtml element.

Payload

{
    field?: IWizField,                    // The input field object.
    content: any                         // The associated data of the event.
}

VALIDATE\ A VALIDATE event occurs every time the input field's value is validated.

Payload

{
    field: IWizField,            // The input field object.
    formControl: formControl,    // The associated form control.
    content: IWizValidate        // Contains a boolean validation flag that allows triggering the validation manually.
}

Group entity event types:

CHANGE\ A CHANGE event occurs every time one of the group's fields has changed its value.

Payload:

{
    group: IWizGroup,       // The group object.
    ngForm: ngForm,         // The associated ngForm.
    content: IWizField,     // The input field object.
}

SUBMIT\ A SUBMIT event occurs when the group successfully submitted.

Payload:

{
    group: IWizGroup,       // The group object.
    ngForm: ngForm,         // The associated ngForm.
    content: SubmitEvent    // The submit event.
}

UNSUBMIT\ A UNSUBMIT event occurred when the group failed to submit.

Payload

{
    group: IWizGroup,       // The group object.
    ngForm: ngForm,         // The associated ngForm.
    content: SubmitEvent    // The submit event.
}

VALIDATE\ A VALIDATE event occurs every time the group's value is validated.

Payload:

{
    group: IWizGroup,       // The group object.
    ngForm: ngForm,         // The associated ng form.
    content: IWizValidate   // Contains a boolean validation flag that allows triggering the validation manually.
}

Subscribing events:

To subscribe to an event, we need to inject the WizardEventBus service, call the "subscribe" method, and provide three parameters:\ id: - (string) The id of the entity(field/group) we want to subscribe to.\ type: - (WizardEventTypes) The type of the event we want to subscribe to.\ callback: - (Function) Attach a function to call when the event occurs.

For example, let's subscribe to the CHANGE event of a simple text field.\ The IWizField config object should look like that:

{   
    type: "text",
    placeholder: "Enter your full name",
    id: "full-user-name",                       // The unique id of the component.
    events: true,                               // Enable events.                            
}

Note: remember to set the events flag to true.

In your ts file:

import { IWizData, WizardEventBus, WizardEventTypes, } from 'ngx-mat-form-wizard';

constructor(
    private _wizardEventBus: WizardEventBus,
){
    this._wizardEventBus.subscribe("full-user-name", WizardEventTypes.CHANGE, (event: IWizData) => {
        // The `CHANGE` event occurs every time the input field has changed its value.
    });
}

This way, we can subscribe to all the rest of the text field's available events:

import { IWizData, WizardEventBus, WizardEventTypes, } from 'ngx-mat-form-wizard';

constructor(
    private _wizardEventBus: WizardEventBus,
){
    this._wizardEventBus.subscribe("full-user-name", WizardEventTypes.FOCUS, (event: IWizData) => {
        // The `FOCUS` event occurs every time the input field has been focused or blurred.
    });
    
    this._wizardEventBus.subscribe("full-user-name", WizardEventTypes.VALIDATE, (event: IWizData) => {
        // The `VALIDATE` event occurs every time the input field’s value was validated.
    });
}

Isn't that awesome? as you can see, it is easy to listen to any event using this service.\ Note: remember to unsubscribe those subscriptions when the current view is destroyed. Watch the demo for examples.

InnerHTML events:

You can also subscribe to a 'CLICK' event of an innerHTML element. All you need to do is provide this element with a unique id and use the 'WizardEventBus' service. For example:

    {   
        id: "accept-terms-and-conditions",          // The unique id of the component.
        type: "checklist-multiple",         
        events: true,                               // Enable events.
        required: true                              // Define this field as required.
        validation: {
            required: "You must accept terms and conditions",
        },
        items: [
            // the `IWizItem` object.
            { 
                id: 4200,                           // The unique id of the item.
                name: `   
                    Please accept our
                    <span id="terms-and-conditions-link">
                        terms and conditions      
                    </span>
                    before sign-in.
                `
            }
        ],
    }

And in your ts file:

import { IWizData, WizardEventBus, WizardEventTypes, } from 'ngx-mat-form-wizard';

constructor(
    private _wizardEventBus: WizardEventBus,
){
    this._wizardEventBus.subscribe("terms-and-conditions-link", WizardEventTypes.CLICK, (event: IWizData) => {
        // Your logic here...
    });
}

Custom validations:

In some cases, you would like to implement your own custom sync/async validations in your form. For example, we want to use a text type field with IP address validation attached to it. You can achieve that by subscribing to the "VALIDATE" event.\ So first, let's set the config object for our field:

{
    type: "text",
    id: "user-ip-address",                // The unique id of the component.
    placeholder: "Enter your IP address",
    events: true,                         // Enable events.
    validation: {
        custom: "Wrong IP address",
        required: "This field is required"
    },
    required: true                     
},

Next, let's install a library that can do the IP address check for us and return a boolean value as a result. I have chosen to use the is-ip library for this example. (but you can use any other library or implement this functionality yourself)

npm i is-ip --save

Then, let's subscribe the VALIDATE event in our class constructor and set the invalid flag by the library's result:

import { IWizData, WizardEventBus, WizardEventTypes, } from 'ngx-mat-form-wizard';
import * as isIpAddress from 'is-ip';

constructor(
    private _wizardEventBus: WizardEventBus,
){
    this._wizardEventBus.subscribe("user-ip-address", WizardEventTypes.VALIDATE, (event: IWizData) => {
        // This is the perfect place to implement your custom sync/async validations functionality and set the `invalid` flag as you wish.
        
        event.content.invalid = !isIpAddress(String(event.field.value));
    });
}

And that's it! The invalid flag will be switched false/true by the response of the IP validator.

Now let's see another example. This time, we will implement a custom validation for a group, ready?

Let's suppose we want to have a group with four fields, and we would like the user to fill only 2 of them by his choice. Obviously, we cannot define all the fields as required, but we can make the group obligate the user to fill only two fields by a custom validation. So first, let's set our group config object:

{
    id: "fill-only-2-fields-group",     // The unique id of the component.
    headline: "Choose Two Fields To Fill Up",
    events: true,                       // Enable events.
    validation: { 
        custom: "This group requires exactly 2 fields to be filled",
        default: "This group has some issues"
    },                      
    containers: [   
        {
            id: "name-and-email",
            fields: [
                {
                    type: "text",
                    id: "user-full-name",
                    placeholder: "Username",
                    pattern: "[a-zA-Z ]*",
                    minLength: 2,
                    maxLength: 8,
                    validation: { 
                        maxLength: "Username should include up to 8 characters",
                        minLength: "Username should include at least 2 characters",
                        pattern: "Use upper/lower case letters only"
                    },
                },
                {
                    type: "email",
                    id: "user-email",
                    placeholder: "Email",
                    validation: "Invalid email",
                    validation: {
                        email: "Invalid email address",
                    },
                },
            ]
        },
        {
            id: "birthdate-and-docs",
            fields: [
                {
                    type: "date",
                    id: "user-birthdate",
                    placeholder: "Birthdate",
                    hint: "Verify that you are older than 18 years old",
                    maxDate: moment(moment().subtract(18, "year")).format("YYYY-MM-DD")
                },
                {   
                    type: "files",
                    id: "user-docs", 
                    validation: { 
                        maxItems: "Please provide up to 5 files only",
                        minItems: "Please provide at least 2 files",
                    },                                      
                    hint: "Up to 5MB per file.",                                      
                    maxFileSize: 5000000,
                    accept: ".scss, .svg, .json, .gif, .html",                                      
                    multiple: true,                                     
                    minItems: 2,                                          
                    maxItems: 5,
                    files: []                                       
                },
            ]
        },
    ]
},

So those fields are not required, but they have their own validations that will still take effect if any value is inserted.
Then, we need to create the count-filled-values.validator.ts file:

import { FormGroup } from '@angular/forms';

export const countFilledValuesValidator = (formGroup: FormGroup, numberOfValues: number): boolean => {
    return Object.keys(formGroup.controls).map((key: string) => {
        const value: any = formGroup.controls[key].value;
        return value != null && value != "";
    }).filter(value => value).length == numberOfValues;
};

Next in our ts file, let's subscribe the VALIDATE event in our class constructor and set the invalid flag by the validator's result:

import { IWizData, WizardEventBus, WizardEventTypes, } from 'ngx-mat-form-wizard';
import { countFilledValuesValidator } from './count-filled-values.validator';

constructor(
    private _wizardEventBus: WizardEventBus,
){
    this._wizardEventBus.subscribe("fill-only-2-fields-group", WizardEventTypes.VALIDATE, (event: IWizData) => {
        event.content.invalid = !countFilledValuesValidator(event.ngForm.form, 2) && event.ngForm.submitted;
    });
}

Perfecto! so the group is now guided by our custom validator. Note that I also used the 'submitted' flag to prevent the group from showing its validation before submitting the form. (but that's also up to your scenario)

As you can see, the VALIDATE event provides a boolean flag that can be toggled by your command and affect the validation state of the control. (this flag is a reference to an inner deep down nested local object, which is attached to the control's validation state)