-
Notifications
You must be signed in to change notification settings - Fork 44
Description
Motivation
Add a new feature called withDirtyCheck that allows developers to deeply monitor dirty state in complex data structures. Unlike shallow dirty checks, this feature will track changes in nested objects and arrays, making it easier to determine if any part of the state has been modified. Unlike Angular forms' isDirty state, this fetaure will determine dirtiness based on structural changes rather than user interactions.
Part 1: Deep Dirty Signals (TBR)
In addition to the withDirtyCheck feature, this RFC proposes a new utility called deepDirtySignal. This utility will provide a way to create signals that can deeply track dirty state in nested data structures.
Note: I have a PoC of this utility available.
Usage Example
const source = signal({
id: 1,
name: 'Test',
details: {
age: 30,
address: {
city: 'New York',
zip: '10001',
},
},
});
const isDirty = deepDirtySignal(source);
console.log(isDirty()); // false
source.update((obj) => {
return {
...obj,
name: 'Updated Test',
};
});
console.log(isDirty()); // true
console.log(isDirty.name()); // true
console.log(isDirty.details()); // false
console.log(isDirty.details.address()); // falsePros of deepDirtySignal:
- provides a type-safe way to track dirty state in nested objects
- signals are created lazily, so only accessed paths are tracked
- signals internal caching optimizes performance by avoiding redundant computations
Cons of deepDirtySignal:
- complex implementation
- may not support complex types well (e.g., partials)
Alternative APIs considered
Instead of lazily creating signals for each path using the fluent API, we could eagerly create signals on initialization. For example, isNameDirty = deepDirtySignal(() => source().address) would eagerly create a dirty check signal for the address. It would dirty check based on structural changes to the address property but would not provide a way to dirty check nested properties.
Part 2: withDirtyCheck Feature
The withDirtyCheck feature will be implemented as a store feature that adds deep dirty checking to the store state. This feature will leverage the deepDirtySignal utility to provide a way to monitor dirty state in complex store states.
Proposed API
type BookSearchState = {
books: Book[];
isLoading: boolean;
filter: { query: string; order: 'asc' | 'desc' };
};
const initialState: BookSearchState = {
books: [],
isLoading: false,
filter: { query: '', order: 'asc' },
};
export const BooksStore = signalStore(
withState(initialState),
withDirtyCheck(), // <-- Adds deep dirty checking as `isDirty`
);
const booksStore = inject(BooksStore);
const enableSaveButton = computed(() => !booksStore.isLoading() && booksStore.isDirty.books());Alternatives considered
Instead of adding store wide dirty checking, the withDirtyCheck feature could create dirty check signals only for specific slices of state. For example, withDirtyCheck({ isNameDirty: (state) => state().books }) would only create dirty check signals for the books slice of state.
This may be equivalant to using deepDirtySignal within a withComputed store feature.
export const BooksStore = signalStore(
withState(initialState),
withComputed(({ books }) => ({
isBooksDirty: deepDirtySignal(() => books()),
}))
);