mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(forms): allow multiple async validators
When a parent form element defines an async validator, its resource's `params` function needs to evaluate `syncValid()`, which causes unvisited child form nodes to be lazily instantiated. If any of these lazily instantiated child nodes also define an async validator, their resource is initialized while the parent's `params` function is still evaluating. This incorrectly triggers Angular core's `NG0992` guard (`Cannot create a resource inside the params of another resource`). This commit exports `ɵsetInParamsFunction` and `ɵisInParamsFunction` from `@angular/core` and uses them in `FieldMetadataState.runMetadataCreateLifecycle` to explicitly detach the lazy creation of form metadata from the parent's reactive `params` context.
This commit is contained in:
committed by
Leon Senft
parent
cd8351a3af
commit
953cdfd66a
@@ -9,4 +9,8 @@
|
||||
export * from './api';
|
||||
export {debounced} from './debounce';
|
||||
export {resourceFromSnapshots} from './from_snapshots';
|
||||
export {resource} from './resource';
|
||||
export {
|
||||
isInParamsFunction as ɵisInParamsFunction,
|
||||
resource,
|
||||
setInParamsFunction as ɵsetInParamsFunction,
|
||||
} from './resource';
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
import {
|
||||
computed,
|
||||
runInInjectionContext,
|
||||
untracked,
|
||||
ɵRuntimeError as RuntimeError,
|
||||
untracked,
|
||||
ɵisInParamsFunction,
|
||||
ɵsetInParamsFunction,
|
||||
} from '@angular/core';
|
||||
import {MetadataKey} from '../api/rules/metadata';
|
||||
import {RuntimeErrorCode} from '../errors';
|
||||
@@ -34,20 +36,26 @@ export class FieldMetadataState {
|
||||
return;
|
||||
}
|
||||
|
||||
untracked(() =>
|
||||
runInInjectionContext(this.node.structure.injector, () => {
|
||||
for (const key of this.node.logicNode.logic.getMetadataKeys()) {
|
||||
if (key.create) {
|
||||
const logic = this.node.logicNode.logic.getMetadata(key);
|
||||
const result = key.create!(
|
||||
this.node,
|
||||
computed(() => logic.compute(this.node.context)),
|
||||
);
|
||||
this.metadata.set(key, result);
|
||||
const wasInParams = ɵisInParamsFunction();
|
||||
if (wasInParams) ɵsetInParamsFunction(false);
|
||||
try {
|
||||
untracked(() =>
|
||||
runInInjectionContext(this.node.structure.injector, () => {
|
||||
for (const key of this.node.logicNode.logic.getMetadataKeys()) {
|
||||
if (key.create) {
|
||||
const logic = this.node.logicNode.logic.getMetadata(key);
|
||||
const result = key.create!(
|
||||
this.node,
|
||||
computed(() => logic.compute(this.node.context)),
|
||||
);
|
||||
this.metadata.set(key, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
if (wasInParams) ɵsetInParamsFunction(true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the value of an `MetadataKey` for the field. */
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {ApplicationRef, computed, Injector, linkedSignal, signal} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import * as z from 'zod';
|
||||
import {form, schema, validateStandardSchema} from '../../../../public_api';
|
||||
import {form, schema, validateHttp, validateStandardSchema} from '../../../../public_api';
|
||||
|
||||
interface Flight {
|
||||
id: number;
|
||||
@@ -460,4 +460,28 @@ describe('standard schema integration', () => {
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
const Schema = z.object({code: z.string().min(1)});
|
||||
|
||||
const injector = TestBed.inject(Injector);
|
||||
const model = signal({code: ''});
|
||||
|
||||
function createForm() {
|
||||
form(
|
||||
model,
|
||||
(p) => {
|
||||
validateStandardSchema(p, Schema); // (A) schema validation
|
||||
validateHttp(p.code, {
|
||||
request: ({value}) => `/api/check?code=${value()}`,
|
||||
onSuccess: () => null,
|
||||
onError: () => null,
|
||||
});
|
||||
},
|
||||
{injector},
|
||||
);
|
||||
}
|
||||
|
||||
expect(createForm).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -507,4 +507,32 @@ describe('resources', () => {
|
||||
|
||||
expect(success).toBeTrue();
|
||||
});
|
||||
|
||||
it('should not throw NG0992 when using validateAsync on parent and lazily-created child', () => {
|
||||
const model = signal({child: ''});
|
||||
const injector = TestBed.inject(Injector);
|
||||
function createForm() {
|
||||
form(
|
||||
model,
|
||||
(p) => {
|
||||
validateAsync(p, {
|
||||
params: () => 1,
|
||||
factory: () => resource({loader: async () => []}),
|
||||
onSuccess: () => null,
|
||||
onError: () => null,
|
||||
});
|
||||
|
||||
validateAsync(p.child, {
|
||||
params: () => 1,
|
||||
factory: () => resource({loader: async () => []}),
|
||||
onSuccess: () => null,
|
||||
onError: () => null,
|
||||
});
|
||||
},
|
||||
{injector},
|
||||
);
|
||||
}
|
||||
|
||||
expect(() => createForm()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user