Files
Alex Rickabaugh 9b9769479b perf(forms): shortcut deepSignal writes if value is unchanged
Avoid deep write path traversal and triggering source signal updates
when calling `deepSignal.set(value)` with a new value that is
identical to the current value (`Object.is`).

This shortcuts the entire write path and unnecessary array/object
copying early on. This approach relies on the guarantee that `source`'s
value is non-nullable in the context where `deepSignal` is created and
used.

TAG=agy
CONV=9e5bd277-0d0a-466c-be36-5e3a8e6910be
2026-05-05 09:28:00 -07:00

82 lines
2.1 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {computed, signal} from '@angular/core';
import {deepSignal} from '../../src/util/deep_signal';
describe('deepSignal', () => {
it('should allow reading and writing', () => {
const source = signal({foo: 'bar'});
const prop = signal('foo' as const);
const deep = deepSignal(source, prop);
expect(deep()).toBe('bar');
deep.set('baz');
expect(deep()).toBe('baz');
expect(source().foo).toBe('baz');
});
it('should optimize sets with same value on objects', () => {
const source = signal({foo: 'bar'});
const prop = signal('foo' as const);
const deep = deepSignal(source, prop);
let sourceTriggerCount = 0;
const derived = computed(() => {
source();
sourceTriggerCount++;
return source().foo;
});
// Initial evaluation
derived();
expect(sourceTriggerCount).toBe(1);
// Set to different value
deep.set('baz');
derived();
expect(deep()).toBe('baz');
expect(source().foo).toBe('baz');
expect(sourceTriggerCount).toBe(2);
// Set to SAME value
deep.set('baz');
derived();
expect(sourceTriggerCount).toBe(2); // Should STILL be 2 if optimization works
});
it('should optimize sets with same value on arrays', () => {
const source = signal(['a', 'b', 'c']);
const prop = signal(1); // index 1, value 'b'
const deep = deepSignal(source, prop);
let sourceTriggerCount = 0;
const derived = computed(() => {
source();
sourceTriggerCount++;
return source()[1];
});
// Initial evaluation
derived();
expect(sourceTriggerCount).toBe(1);
// Set to different value
deep.set('x');
derived();
expect(deep()).toBe('x');
expect(source()[1]).toBe('x');
expect(sourceTriggerCount).toBe(2);
// Set to SAME value
deep.set('x');
derived();
expect(sourceTriggerCount).toBe(2); // Should STILL be 2 if optimization works
});
});