Search code examples
angulartypescriptdictionarysignals

Can I use Angular Signals to store a Map?


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.


Solution

  • 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 }.