Skip to main content

requiredIf()

🧭 API map​

I want to…Details
See every accepted call styleSignatures
Conditionally require a valueUsage and behavior
Customize the failure messageMessage configuration
Understand signal trackingReactive behavior
Compare it with unconditional presencerequired()

πŸ“ Signatures​

requiredIf(condition)
requiredIf(condition, message)
requiredIf(condition, options)

condition should return boolean; its declared return type is intentionally unchecked to support self-references. The options customize only the failure message, which may itself be reactive.

It is a concise alternative to required({ when }) when the condition does not need the validator context:

requiredIf(() => businessAccount())
required({ when: () => businessAccount() })

πŸ“– Usage and behavior​

Use requiredIf() when whether a value is mandatory depends on reactive application or form state:

const businessAccount = signal(false);

const myForm = form({
companyName: field('', [requiredIf(() => businessAccount())]),
});

myForm.companyName.required(); // false

businessAccount.set(true);
myForm.companyName.required(); // true
myForm.companyName.getError('required'); // { kind: 'required', ... }

When the condition returns false, the validator passes and contributes required() === false metadata. When it returns true, it behaves exactly like required(): it rejects null, undefined, '', and NaN, while both false and empty arrays, sets, maps, and objects remain present values. For conditional acceptance, use requiredTrue({ when: () => condition() }).

The condition may also depend on a sibling node declared in the same form:

const myForm = form({
accountType: field<'personal' | 'business'>('personal'),
companyName: field('', [
requiredIf(() => myForm.accountType() === 'business'),
]),
});

πŸ’¬ Message configuration​

Pass a string for a static message:

field('', [requiredIf(() => businessAccount(), 'Enter a company name.')]);

Use an options object for a reactive message:

field('', [
requiredIf(() => businessAccount(), { message: () => translations().companyNameRequired })
]);

Returning undefined from the message function continues through node, Angular provider, process-wide, and built-in message fallbacks. See Validator messages.

⚑ Reactive behavior​

Signals read by condition are tracked. Changing one invalidates validation and required metadata; the condition is evaluated again when either state is next consumed. A reactive consumer of errors(), valid(), invalid(), validationStatus(), or required() observes the change.

The message function is evaluated only for an active, failing rule. Disabled, readonly, and hidden nodes skip validation until they become interactive again, while the configured required metadata continues to reflect the condition.

Class self-references​

A condition can reference the form being declared, directly or through a computed signal declared later in the class. No explicit form type, computed type, or callback return annotation is needed. The form's fields and the computed signal retain their inferred types.

profile-model.ts
import { computed } from '@angular/core';
import { field, form, required, requiredIf } from '@ngblocks/form-nodes';

class ProfileModel {
form = form({
other: field<number>(23, [required]),
subType: field<string>(null, [requiredIf(() => this.isTypeVisible())]),
});

isTypeVisible = computed(() => (this.form.other() ?? 0) > 30);
}

const model = new ProfileModel();
model.isTypeVisible(); // false
if (model.form.subType.required() || !model.form.valid()) throw new Error('The hidden subtype must be optional.');
model.form.other.set(31);
model.isTypeVisible(); // true
if (!model.form.subType.required() || !model.form.invalid()) throw new Error('The visible subtype must be required.');
model.form.other.set(23);
if (model.form.subType.required() || !model.form.valid()) throw new Error('The condition must remain reactive.');

The condition parameter is typed () => any to break TypeScript's circular contextual-return inference, just like parameterless validator callbacks. This means TypeScript does not reject non-boolean returns: always return a boolean. Parameterless when callbacks on other validators support the same convention; callbacks receiving a context retain their checked boolean result. It does not cause eager condition evaluation or change reactive tracking. A nullable numeric field still requires null handling in comparisons, as shown by ?? 0 above.

For custom helper signatures and explicit return-type alternatives, see troubleshooting circular type inference.