I'm starting to use Angular new Signals API. But I've been told that we need to treat data as inmutable when using signals.
And I'm not sure if I can do something like this with JS Maps:
// code in my service.
// I'm using this Map to save the messages in a object and get the messages by queue name.
messages = signal(new Map<string, Object>());
queryForMessagesInQueue(queue) {
this.httpClient.get('...').subscribe((newMessagesArray) => {
const messages = this.messages().get(queue) || {};
// validate if newMessages already exists in the message object
let addedMessages = 0;
newMessagesArray.forEach((newMsg) => {
const messageExists = !!messages[newMsg.id]
if (messageExists == false) {
messages[newMsg.id] = newMsg;
addedMessages++;
}
});
if (addedMessages !== 0) {
this.messages.update((msgMap) => msgMap.set(queue, messages));
}
});
}
Then, in my components I'm doing something like this:
export class MyComponent {
private service = inject(MyService)
messages = service.messages; // this is the service signal.
}
Am I using it correctly?
Can signals be used with data structures like JS Maps? Will change detection work correctly?
Thanks in advance.
By default, signals use referential equality (=== comparison). In this.messages.update
you are returning the same Map
, so in the equality check the "old" value of Map
and the "new" value of Map
are the same reference.
So you need to return a new Map
for your code to work. Also you don't really need msgMap.set(queue, messages)
, because you have already added values to messages
, and so you are setting the same reference of messages
.
The final code looks like this:
messages = signal(new Map<string, Object>());
queryForMessagesInQueue(queue) {
this.httpClient.get('...').subscribe((newMessages) => {
const messages = this.messages().get(queue) || {};
const addedMessages = newMessages.filter((newMsg) => !!messages[newMsg.id]);
if (addedMessages.length) {
addedMessages.forEach((addedMsg) => messages[addedMsg.id] = addedMsg);
this.message.update((msgMap) => new Map(msgMap));
}
});
}
I would also change Object
to Record<string, Message>
or index signature { [messageId: string]: Message }
.