Decorator composition

It would be nice to encapsulate many decorators into one when their combined use is somewhat frequent.

As you know, we have a DTO to validate an id, the IdDto. As a serial id is a positive integer, we use both the @IsInt() and @IsPositive() decorators. We also use this same validation in both fields of the PaginationDto, and soon will have more DTOs with fields requiring it too. But is there a way to create a single decorator representing both these validations, so that we may avoid this repetition in the future?

Yes, with decorator composition. For this purpose we'll create the @IsCardinal() decorator. So, create the file common -> decorators -> is-cardinal.decorator with following content

export const IsCardinal = () => applyDecorators(IsInt(), IsPositive());

Our composite decorator is already working, but let's make some improvements to it before moving forward.

First, a cardinal number may have two slightly different meanings, the difference being the inclusion or not of the zero value. In our case, we do not want to include it. To make this completely clear, we can document our decorator with a JSDoc, which is a special comment that adds a readable description to an element. The shortcut for creating one is writing /**. Add the following above the decorator, to document it. Hovering the cursor over it will now show the description.

/**
 * Checks if the value is a positive integer greater than zero.
 */

We should always strive to write code that is clear, concise and self-documenting, in order to avoid unnecessary documentation. However, sometimes documentation can help us and other developers to quickly and easily understand a code element.

If you hover the cursor over the decorator, you'll notice that its return type is something kind of hard to understand. To easily identify that it is a property decorator, its return type can be explicitly defined as PropertyDecorator.

Finally, class-validator decorators accept ValidationOptions as parameter, with some interesting options such as each, which validates if the property is an array of the decorator's type. So, let's also have these options as parameter and pass them along to the encapsulated decorators.

We should have the following result

export const IsCardinal = (
  validationOptions?: ValidationOptions,
): PropertyDecorator =>
  applyDecorators(IsInt(validationOptions), IsPositive(validationOptions));

We can now use this decorator in the IdDto such as follows, and after that also in the PaginationDto. Hover the cursor over the decorator to see its improved tooltip.

@IsCardinal()
id: number;

It may not look like much right now, but with bigger combinations of decorators, this pattern is really helpful.

Commit - Creating a composite decorator

Last updated