javascriptangularrxjsangular-akitaakita

Architecting program so that Angular does not re-render the MediaStream component every time an update is pushed to the store?


The example Angular application I have created demonstrates the problem I am having, this application can be found here

Basically I have two stores, I am using the state management library Akita for these stores. Both are Entity Stores, the first store is for all the messages and it has a signature like this:

interface MessageInterface {
 // Unique identifier
 id: ID;
 // The time is was created
 time: number;
 // The actual string message
 value: string;
} 

The second store is for all my connections, a connection has a signature like this:

interface ConnectionInterface {
  // Unique identifier
  id: ID;
  // The MediaStream (webcam stream object)
  stream: MediaStream;
  // An array of unique ids where each id relates to an id in the MessageStore
  messages: ID[] | MessageInterface[]
}

Here are the steps the app takes when it begins:

  1. Starts a webcam stream using getUserMedia.
  2. It then saves the generated stream into the ConnectionStore.
  3. It then begins to create 1 message every second, after each message is created, it saves the message into the MessageStore and then relates that message to the ConnectionStore by adding the index of the created message to the beginning of the messages array on the ConnectionStore entity.

Here is an example of what the two stores will look like at the end:

Message Store:

entities: {
  3ea9454682: {id: "3ea9454682", time: 1548117443764, value: "CIAO"}
  4a66b34aad: {id: "4a66b34aad", time: 1548117444765, value: "SALAAM"}
  4e165d4887: {id: "4e165d4887", time: 1548117445767, value: "SALAAM"}
  4eb22b42a6: {id: "4eb22b42a6", time: 1548117441763, value: "BONJOUR"}
  5cbc594983: {id: "5cbc594983", time: 1548117447771, value: "HELLO"}
  6c574e44a8: {id: "6c574e44a8", time: 1548117446770, value: "NAMASTE"}
  bfdcc344aa: {id: "bfdcc344aa", time: 1548117448771, value: "NAMASTE"}
  c7c55d45a7: {id: "c7c55d45a7", time: 1548117440762, value: "NAMASTE"}
  cc86a045af: {id: "cc86a045af", time: 1548117442764, value: "HOLA"}
  fd2cf744ab: {id: "fd2cf744ab", time: 1548117439760, value: "CIAO"}
}

Connection Store:

entities: {
  fda7464bbe: {
     id: "fda7464bbe",
     mediaStream: {
       active: true
       id: "bQO4NI86L7pEHNzEwEK9dVht5wtHf5s81TfV"
       onactive: null
       onaddtrack: null
       oninactive: null
       onremovetrack: null
     },
     messages: [
       "bfdcc344aa"
       "5cbc594983"
       "6c574e44a8"
       "4e165d4887"
       "4a66b34aad"
       "3ea9454682"
       "cc86a045af"
       "4eb22b42a6"
       "c7c55d45a7"
       "fd2cf744ab"
     ]
  }
}

The problem I am experiencing is every time I generate a message and save it into the stores, you can see the webcam video flicker. Maybe I am architecting the structure wrong? Maybe there is a way to only re-render just the messages and not have it affect the stream? Any suggestions would be a huge help, thanks to anyone who tries solving this!


Solution

  • you can solve the problem by using a trackBy in your ngFor:

    <li *ngFor="let connection of connections | async; trackBy: trackByFunction">
    

    Declare the trackByFunction like this in your typescript:

    trackByFunction(index, item) {
      return index;
    }
    

    This helps Angular to track which items are added or removed from the list and rerender only the items that changed.

    I had to add the following to your polyfills.ts file to make it compile:

    import 'core-js/es7/reflect';
    

    Hope that helps.