8000 refactor: refactor builder api · angular/angular@86f8370 · GitHub
[go: up one dir, main page]

Skip to content

Commit 86f8370

Browse files
committed
refactor: refactor builder api
1 parent de4b134 commit 86f8370

File tree

1 file changed

+108
-117
lines changed

1 file changed

+108
-117
lines changed

packages/forms/experimental/src/logic_node_2.ts

Lines changed: 108 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -7,137 +7,61 @@ import {
77
Predicate,
88
} from './logic_node';
99

10-
/**
11-
* Container for all the different types of logic that can be applied to a field
12-
* (disabled, hidden, errors, etc.)
13-
*/
14-
export class Logic {
15-
readonly hidden: BooleanOrLogic;
16-
readonly disabled: BooleanOrLogic;
17-
readonly errors: ArrayMergeLogic<FormError>;
18-
private readonly metadata = new Map<MetadataKey<unknown>, AbstractLogic<unknown>>();
19-
20-
constructor(private predicates: ReadonlyArray<Predicate>) {
21-
this.hidden = new BooleanOrLogic(predicates);
22-
this.disabled = new BooleanOrLogic(predicates);
23-
this.errors = new ArrayMergeLogic<FormError>(predicates);
24-
}
25-
26-
getMetadata<T>(key: MetadataKey<T>): AbstractLogic<T> {
27-
if (!this.metadata.has(key as MetadataKey<unknown>)) {
28-
this.metadata.set(key as MetadataKey<unknown>, new MetadataMergeLogic(this.predicates, key));
29-
}
30-
return this.metadata.get(key as MetadataKey<unknown>)! as AbstractLogic<T>;
31-
}
32-
33-
readMetadata<T>(key: MetadataKey<T>, arg: FieldContext<unknown>): T {
34-
if (this.metadata.has(key as MetadataKey<unknown>)) {
35-
return this.metadata.get(key as MetadataKey<unknown>)!.compute(arg) as T;
36-
} else {
37-
return key.defaultValue;
38-
}
39-
}
40-
41-
getMetadataKeys() {
42-
return this.metadata.keys();
43-
}
44-
}
45-
46-
/**
47-
* A tree structure of `Logic` corresponding to a tree of fields.
48-
*/
49-
export class LogicNode {
50-
readonly logic: Logic;
51-
52-
constructor(private builder: LogicNodeBuilder | undefined) {
53-
this.logic = builder ? createLogic(builder) : new Logic([]);
54-
}
55-
56-
getChild(key: PropertyKey): LogicNode {
57-
// The logic for a particular child may be spread across multiple builders. We lazily combine
58-
// this logic at the time the child logic node is requested to be created.
59-
const childBuilders = this.builder ? getAllChildren(this.builder, key) : [];
60-
if (childBuilders.length <= 1) {
61-
return new LogicNode(childBuilders[0]);
62-
} else {
63-
// If there are multiple child builders, combine them all into one we ca pass to the new logic
64-
// node.
65-
const combined = LogicNodeBuilder.newRoot();
66-
for (const child of childBuilders) {
67-
combined.mergeIn(child);
68-
}
69-
return new LogicNode(combined);
70-
}
71-
}
72-
}
73-
74-
/**
75-
* A builder for `LogicNode`, which itself is a tree.
76-
*/
77-
export abstract class LogicNodeBuilder {
78-
protected constructor(readonly predicates: ReadonlyArray<Predicate>) {}
10+
abstract class AbstractLogicNodeBuilder {
11+
constructor(readonly predicates: ReadonlyArray<Predicate>) {}
7912

8013
abstract addHiddenRule(logic: LogicFn<unknown, boolean>): void;
8114
abstract addDisabledRule(logic: LogicFn<unknown, boolean>): void;
8215
abstract addErrorRule(logic: LogicFn<unknown, ValidationResult>): void;
8316
abstract addMetadataRule<T>(key: MetadataKey<T>, logic: LogicFn<unknown, T>): void;
84-
abstract getChild(key: PropertyKey): MergableLogicNodeBuilder;
85-
abstract predicate(predicates: ReadonlyArray<Predicate>): LogicNodeBuilder;
17+
abstract getChild(key: PropertyKey): LogicNodeBuilder;
18+
abstract predicate(predicates: ReadonlyArray<Predicate>): AbstractLogicNodeBuilder;
8619

8720
build(): LogicNode {
8821
return new LogicNode(this);
8922
}
90-
91-
static newRoot(): MergableLogicNodeBuilder {
92-
return new CompositeLogicNodeBuilder([]);
93-
}
9423
}
9524

9625
/**
97-
* A subclass of `LogicNodeBuilder` that supports merging with other logic trees.
26+
* A builder for `LogicNode`. Used to add logic to the final `LogicNode` tree.
9827
*/
99-
export interface MergableLogicNodeBuilder extends LogicNodeBuilder {
100-
mergeIn(other: MergableLogicNodeBuilder, predicate?: Predicate): void;
101-
predicate(predicates: ReadonlyArray<Predicate>): MergableLogicNodeBuilder;
102-
}
28+
export class LogicNodeBuilder extends AbstractLogicNodeBuilder {
29+
private current: NonMergableLogicNodeBuilder | undefined;
30+
readonly all: AbstractLogicNodeBuilder[] = [];
10331

104-
/**
105-
* A `MergableLogicNodeBuilder` that delegates to its "current" builder and creates a new current
106-
* builder after another builder tree is merged in.
107-
*/
108-
class CompositeLogicNodeBuilder extends LogicNodeBuilder implements MergableLogicNodeBuilder {
109-
private current: SimpleLogicNodeBuilder | undefined;
110-
readonly all: LogicNodeBuilder[] = [];
32+
constructor(predicates: ReadonlyArray<Predicate>) {
33+
super(predicates);
34+
}
11135

112-
override addHiddenRule(logic: LogicFn<unknown, boolean>): void {
36+
addHiddenRule(logic: LogicFn<unknown, boolean>): void {
11337
this.getCurrent().addHiddenRule(logic);
11438
}
11539

116-
override addDisabledRule(logic: LogicFn<unknown, boolean>): void {
40+
addDisabledRule(logic: LogicFn<unknown, boolean>): void {
11741
this.getCurrent().addDisabledRule(logic);
11842
}
11943

120-
override addErrorRule(logic: LogicFn<unknown, FormError>): void {
44+
addErrorRule(logic: LogicFn<unknown, ValidationResult>): void {
12145
this.getCurrent().addErrorRule(logic);
12246
}
12347

124-
override addMetadataRule<T>(key: MetadataKey<T>, logic: LogicFn<unknown, T>): void {
48+
addMetadataRule<T>(key: MetadataKey<T>, logic: LogicFn<unknown, T>): void {
12549
this.getCurrent().addMetadataRule(key, logic);
12650
}
12751

128-
override getChild(key: PropertyKey): MergableLogicNodeBuilder {
52+
getChild(key: PropertyKey): LogicNodeBuilder {
12953
return this.getCurrent().getChild(key);
13054
}
13155

132-
override predicate(predicates: ReadonlyArray<Predicate>) {
56+
predicate(predicates: ReadonlyArray<Predicate>) {
13357
const newPredicates = [...this.predicates, ...predicates];
134-
const clone = new CompositeLogicNodeBuilder(newPredicates);
58+
const clone = new LogicNodeBuilder(newPredicates);
13559
clone.all.push(...this.all.map((b) => b.predicate(newPredicates)));
13660
clone.current = this.current?.predicate(newPredicates);
13761
return clone;
13862
}
13963

140-
mergeIn(other: MergableLogicNodeBuilder, predicate?: Predicate): void {
64+
mergeIn(other: LogicNodeBuilder, predicate?: Predicate): void {
14165
// Add the other builder to our collection, we'll defer the actual merging of the logic until
14266
// the logic node is requested to be created. In order to preserve the original ordering of the
14367
// rules, we close off the current builder to any further edits. If additional logic is added,
@@ -154,28 +78,29 @@ class CompositeLogicNodeBuilder extends LogicNodeBuilder implements MergableLogi
15478
this.current = undefined;
15579
}
15680

157-
private getCurrent(): SimpleLogicNodeBuilder {
81+
private getCurrent(): NonMergableLogicNodeBuilder {
15882
// All rules added to this builder get added on to the current builder. If there is no current
15983
// builder, a new one is created. In order to preserve the original ordering of the rules, we
16084
// clear the current builder whenever a separate builder tree is merged in.
16185
if (this.current === undefined) {
162-
this.current = new SimpleLogicNodeBuilder(this.predicates);
86+
this.current = new NonMergableLogicNodeBuilder(this.predicates);
16387
this.all.push(this.current);
16488
}
16589
return this.current;
16690
}
91+
92+
static newRoot(): LogicNodeBuilder {
93+
return new LogicNodeBuilder([]);
94+
}
16795
}
16896

16997
/**
170-
* A `LogicNodeBuilder` that directly adds logic to its backing `Logic` instance.
171-
*
172-
* The user should not be given a reference to this class, it is used internally to keep track of
173-
* concrete tree chunks within the `CompositeLogicNodeBuilder`. When handing a `LogicNodeBuilder`
174-
* to the user it should always be a `CompositeLogicNodeBuilder`.
98+
* A type of `AbstractLogicNodeBuilder` used internally by the `LogicNodeBuilder` to record "pure"
99+
* chunks of logic that do not require merging in other builders.
175100
*/
176-
class SimpleLogicNodeBuilder extends LogicNodeBuilder {
101+
class NonMergableLogicNodeBuilder extends AbstractLogicNodeBuilder {
177102
logic = new Logic([]);
178-
readonly children = new Map<PropertyKey, MergableLogicNodeBuilder>();
103+
readonly children = new Map<PropertyKey, LogicNodeBuilder>();
179104

180105
override addHiddenRule(logic: LogicFn<unknown, boolean>): void {
181106
this.logic.hidden.push(logic);
@@ -195,31 +120,94 @@ class SimpleLogicNodeBuilder extends LogicNodeBuilder {
195120

196121
override predicate(predicates: ReadonlyArray<Predicate>) {
197122
const newPredicates = [...this.predicates, ...predicates];
198-
const clone = new SimpleLogicNodeBuilder(newPredicates);
123+
const clone = new NonMergableLogicNodeBuilder(newPredicates);
199124
for (const [prop, child] of this.children) {
200125
clone.children.set(prop, child.predicate(newPredicates));
201126
}
202127
clone.logic = this.logic;
203128
return clone;
204129
}
205130

206-
override getChild(key: PropertyKey): MergableLogicNodeBuilder {
207-
// We always create a `CompositeLogicNodeBuilder` for children since someone may call `apply` on
208-
// the child.
131+
override getChild(key: PropertyKey): LogicNodeBuilder {
209132
if (!this.children.has(key)) {
210-
this.children.set(key, new CompositeLogicNodeBuilder(this.predicates));
133+
this.children.set(key, new LogicNodeBuilder(this.predicates));
211134
}
212135
return this.children.get(key)!;
213136
}
214137
}
215138

139+
/**
140+
* Container for all the different types of logic that can be applied to a field
141+
* (disabled, hidden, errors, etc.)
142+
*/
143+
export class Logic {
144+
readonly hidden: BooleanOrLogic;
145+
readonly disabled: BooleanOrLogic;
146+
readonly errors: ArrayMergeLogic<FormError>;
147+
private readonly metadata = new Map<MetadataKey<unknown>, AbstractLogic<unknown>>();
148+
149+
constructor(private predicates: ReadonlyArray<Predicate>) {
150+
this.hidden = new BooleanOrLogic(predicates);
151+
this.disabled = new BooleanOrLogic(predicates);
152+
this.errors = new ArrayMergeLogic<FormError>(predicates);
153+
}
154+
155+
getMetadata<T>(key: MetadataKey<T>): AbstractLogic<T> {
156+
if (!this.metadata.has(key as MetadataKey<unknown>)) {
157+
this.metadata.set(key as MetadataKey<unknown>, new MetadataMergeLogic(this.predicates, key));
158+
}
159+
return this.metadata.get(key as MetadataKey<unknown>)! as AbstractLogic<T>;
160+
}
161+
162+
readMetadata<T>(key: MetadataKey<T>, arg: FieldContext<unknown>): T {
163+
if (this.metadata.has(key as MetadataKey<unknown>)) {
164+
return this.metadata.get(key as MetadataKey<unknown>)!.compute(arg) as T;
165+
} else {
166+
return key.defaultValue;
167+
}
168+
}
169+
170+
getMetadataKeys() {
171+
return this.metadata.keys();
172+
}
173+
}
174+
175+
/**
176+
* A tree structure of `Logic` corresponding to a tree of fields.
177+
*/
178+
export class LogicNode {
179+
readonly logic: Logic;
180+
181+
constructor(private builder: AbstractLogicNodeBuilder | undefined) {
182+
this.logic = builder ? createLogic(builder) : new Logic([]);
183+
}
184+
185+
// TODO: cache here, or just rely on the user of this API to do caching?
186+
getChild(key: PropertyKey): LogicNode {
187+
// The logic for a particular child may be spread across multiple builders. We lazily combine
188+
// this logic at the time the child logic node is requested to be created.
189+
const childBuilders = this.builder ? getAllChildren(this.builder, key) : [];
190+
if (childBuilders.length <= 1) {
191+
return new LogicNode(childBuilders[0]);
192+
} else {
193+
// If there are multiple child builders, combine them all into one we ca pass to the new logic
194+
// node.
195+
const combined = LogicNodeBuilder.newRoot();
196+
for (const child of childBuilders) {
197+
combined.mergeIn(child);
198+
}
199+
return new LogicNode(combined);
200+
}
201+
}
202+
}
203+
216204
/**
217205
* Gets all of the builders that contribute logic to the given child of the parent builder.
218206
*/
219-
function getAllChildren(builder: LogicNodeBuilder, key: PropertyKey): MergableLogicNodeBuilder[] {
220-
if (builder instanceof CompositeLogicNodeBuilder) {
207+
function getAllChildren(builder: AbstractLogicNodeBuilder, key: PropertyKey): LogicNodeBuilder[] {
208+
if (builder instanceof LogicNodeBuilder) {
221209
return builder.all.flatMap((node) => getAllChildren(node, key));
222-
} else if (builder instanceof SimpleLogicNodeBuilder) {
210+
} else if (builder instanceof NonMergableLogicNodeBuilder) {
223211
if (builder.children.has(key)) {
224212
return [builder.children.get(key)!];
225213
}
@@ -232,9 +220,10 @@ function getAllChildren(builder: LogicNodeBuilder, key: PropertyKey): MergableLo
232220
/**
233221
* Creates the full `Logic` for a given builder.
234222
*/
235-
function createLogic(builder: LogicNodeBuilder): Logic {
236-
const logic = new Logic(builder.predicates);
237-
if (builder instanceof CompositeLogicNodeBuilder) {
223+
function createLogic(builder: AbstractLogicNodeBuilder): Logic {
224+
if (builder instanceof LogicNodeBuilder) {
225+
// Build the logic for all sub-builders, and merge them into one.
226+
const logic = new Logic([]);
238227
const builtNodes = builder.all.map((b) => b.build());
239228
for (const node of builtNodes) {
240229
logic.disabled.mergeIn(node.logic.disabled);
@@ -244,15 +233,17 @@ function createLogic(builder: LogicNodeBuilder): Logic {
244233
logic.getMetadata(key).mergeIn(node.logic.getMetadata(key));
245234
}
246235
}
247-
} else if (builder instanceof SimpleLogicNodeBuilder) {
236+
return logic;
237+
} else if (builder instanceof NonMergableLogicNodeBuilder) {
238+
const logic = new Logic(builder.predicates);
248239
logic.disabled.mergeIn(builder.logic.disabled);
249240
logic.hidden.mergeIn(builder.logic.hidden);
250241
logic.errors.mergeIn(builder.logic.errors);
251242
for (const key of builder.logic.getMetadataKeys()) {
252243
logic.getMetadata(key).mergeIn(builder.logic.getMetadata(key));
253244
}
245+
return logic;
254246
} else {
255247
throw new Error('Unknown LogicNodeBuilder type');
256248
}
257-
return logic;
258249
}

0 commit comments

Comments
 (0)
0