Conditional Tasks in Dynamic Forms

Pseudo-diagram of Conditions and Matchers

We have support for conditional behaviors in
@myndpm/dyn-forms documented at and we are able to provide them easily like the Controls, Validators, AsyncValidators, etc.

In complex forms use-cases, some controls directly depend on the value or status of some other form control. Then we implement custom behaviors, like hiding a field when another control has some value, or disabling it depending on a complex condition, etc.

To support this, we’ve added Matchers and Conditions, which can be provided just like the Validators and AsyncValidators as we saw in the previous chapter of this series. If you want to get an initial idea from the code, you can check this source file and this real use-case demo.


Each dynamic control has a composed Node instance which holds the data of this point in the form hierarchy. It provides the API and the data to manipulate the form in a customized way when needed.

The node has the control Form Instance, the params object, some utility methods to query or select parent and child controls, manipulate the visibility, etc. We will use this node inside the Conditions, Matchers, and any other custom Handlers.


To match a special requirement, we need to define one or more conditions, so when all (AND) or one (OR) of them are fulfilled we run a particular task. The Condition Function type consists of:

interface DynControlConditionFn {
(node: DynTreeNode): Observable<boolean>;

it streams a boolean whenever the condition is fulfilled or not, for example, we could check if a specific control has the expected value:

(node: DynTreeNode) => {
return node.query('specific.control').valueChanges.pipe(
map(controlValue => controlValue === 'xValue'),

we can join these conditions with the required operator (AND | OR) for our use-case, and then execute a specific Matcher.


We define our requirement with the Matchers that we want to run when all or a single condition is satisfied:

match: {
matchers: ['DISABLE'], // one or more matchers
when: [{
// the library provides a DEFAULT condition handler to process path, value and negate
path: 'other.field',
value: 'expectedValue'

the DISABLE matcher is included in the library with ENABLE, SHOW, HIDE (display: none) and INVISIBLE (visibility: hidden).

One matcher consists of a function which performs a task in the form hierarchy; to do so, it receives the DynTreeNode instance:

interface DynControlMatcherFn {
(node: DynTreeNode, hasMatch: boolean): void;

So, for example the DISABLE matcher operates into the form control when the specified conditions are fulfilled (has match):

id: 'DISABLE',
fn: (): DynControlMatcherFn => {
return (node: DynTreeNode, hasMatch: boolean) => {
hasMatch ? node.control.disable() : node.control.enable();

Advanced Stuff

This conditional processing enables us to do some additional logical operations, like negate the output of a condition or the input of a matcher, so we are able to play with conditions upside down and have the simplest specification of our requirements.

Matcher Example

For example, if we want to run a Matcher for all the options of a SELECT except a few of them, OR without another condition, we can define that requirement with the few known values instead listing all the other values (which can be a long list), and negate the matcher input:

match: {
matchers: ['MyMatcherID'],
negate: true,
operator: 'OR', // the operator is AND by default
when: [
path: 'selectorName',
value: ['A', 'B', 'C'] // this will check if selectorName.value is IN this array
path: 'other.control',
value: 'anotherValue'

the Matcher will receive hasMatch: true when the selector has a value NOT in the provided list.

Also note that you can provide your Matcher factories with a custom id like 'MyMatcherID' just like we will do with conditions in the following section.

Condition Factory

We can register Factories with an id and a fn as we do with Validators, and parametrize them in the Config Object:

export interface DynControlCondition {
id: string;
fn: (...args: any[]) => DynControlConditionFn;

Remember that DynControlConditionFn returns an Observable<boolean> so you can implement and provide your custom conditions like:

const conditions = [{
id: 'MyConditionId',
fn: (...args: any[]) => { // Factory
return (node: DynTreeNode) => { // Condition
return node.control.valueChanges.pipe(map(...));
imports: [
DynFormsModule.forFeature({ conditions });

Conditions Config

You can use your custom conditions in these ways:

// inline function
when: [
(node: DynTreeNode) => {
// manipulate the form via DynTreeNode
// factory ID without arguments
when: [
// parametrized factory
when: [
['MyConditionId', args],
// or declarative inline config
when: [
condition: 'MyConditionId',
path: 'other.control', // path is the only mandatory field in this format,
param1: 'anyValue', // the whole object will be passed to your DynControlConditionFn

in the last notation the whole config object is passed to the Factory, that’s how the DEFAULT condition handler receives the path, value and negate config values.

Note: If no value is configured, the DEFAULT handler emits true everytime the configured path control value changes:

id: 'DEFAULT',
fn: ({ path, value, negate }): DynControlConditionFn => {
return (node: DynTreeNode): Observable<boolean> => {
if (value === undefined) {
return node.query(path).valueChanges.pipe(mapTo(true));


We have covered most of the details of Matchers and Conditions, and how one or many conditions can be configured so when one or all of them are fulfilled, they trigger a matcher which can modify the state of the form hierarchy via the DynTreeNode API.

If you have an idea after this reading, or after using this library in an Angular App, please share it with us! :)

You can request features and join our discussions.

// PS. We are hiring!

Senior Frontend Developer