I've made a set of interfaces which describes JSON data. Then I used classes to compose the same data but as Angular Signals:
// TToSignal.ts
import { WritableSignal } from '@angular/core';
export type TToSignal<T> = {
[k in keyof T]: WritableSignal<T[k]>;
};
// CDataItem.ts
import { IDataItem } from '../interfaces';
import { TToSignal } from '../types';
export class CDataItem implements TToSignal<IDataItem> {
prop1: signal<string>('');
prop2: signal<number>(0);
constructor(init: Partial<IDataItem>) {
if (!init) return;
if (init.prop1) this.prop1.set(init.prop1);
if (init.prop2) this.prop2.set(init.prop2);
}
}
// CDataProfile.ts
import { IDataProfile } from '../interfaces';
import { TToSignal } from '../types';
export class CDataProfile implements TToSignal<IDataProfile> {
prop1: signal<string>('');
prop2: signal<number>(0);
constructor(init: Partial<IDataProfile>) {
if (!init) return;
if (init.prop1) this.prop1.set(init.prop1);
if (init.prop2) this.prop2.set(init.prop2);
}
}
// CData.ts
import { CDataProfile, CDataItem } from '../classes';
import { IData } from '../interfaces';
import { TField, TToSignal } from '../types';
export class CData implements TToSignal<IData> {
name: signal<string>('');
items: signal<CDataItem[]>([]);
profile: signal<CDataProfile>(new CDataProfile());
constructor(init: Partial<IData>) {
if (!init) return;
if (init.name) this.name.set(init.name);
if (init.items) this.items.set(init.items);
if (init.profile) this.profile.set(init.profile);
}
}
The data is more complex than this example and I'm wondering: which are the best practices, or how can I approach my data profiling with Signals, considering the use of it inside forms, to interact with it?
Am I overthinking the use of Angular Signals?
Yes, just use a normal data object and set that value to a single signal.
The signals implementation of Angular forms is not yet released, so whatever code you write to make the data object work with forms could be potential rework (Note: [(ngModel)]
works great with signals, just not with the entire form as signal).
You can use a signal to achieve reactivity and to compute asynchronous (rxResource
, httpResource
or resource
) or synchronous (computed
or linkedSignal
) derived state from the original signal.
Currently the entire object can be a signal, then we can use set
or update
(Preferred) to notify the signal changes.
Then we can compute derived state when the source signal changes.
cDataS = signal({ ... });
...
...
onButtonClick() {
this.cDataS.update((cDataS: any) => {
// update gives access to previous state.
cDataS.profile.push({ ... });
// after doing the manipulations create a new memory reference for the signal
// so that the change is detected and the state is recalculated.
return { ...cDataS }; // shallow copy -> creates new memory reference.
});
}