Search code examples
angularngrxng-template

Storing Angular templateRefs inside ngrx/store


We are now building a contextual help cards system for our app. The app is heavily using ngrx and we are trying to follow good practices.

We are currently discussing whether we should store cards content templates in the state. To me it seems like it kinda contradicts with ngrx principles a bit. I.e. templates are not serializable and can be mutated from outside. But problem here is that cards content can be quite complex and there can be variety of different cards. So, if we won't store templates, we would probably need to create a separate component for each case and create a {[contentId: string]: Type<ContentComponent>} mapping to be able to refer to those components with string ids.

First of all, I'd like to ask (a bit opinionated question, I guess) - maybe my concerns are wrong and it's normal to store such structures as angular templates in the state?

And if it is not a very good solution, how would one go about such use case?

Thank you and sorry for opinionated question. I know they are not very welcomed here.


Solution

  • Opinionated question requires an opinionated answer. I'll keep it short. Don't store any complex objects in your store. That's not what it's for. Here is a good read about this subject.

    Basically what a store should provide is:

    • provide an Observable-like pattern for decoupled component interaction
    • provide a client container for temporary UI state
    • provide a cache for avoiding excessive HTTP requests
    • provide a solution for concurrent data modification by multiple actors

    You also mention that your card contents are quite complex and they differ from one another. Then templates definitely is not the way to go. That means that all the logic is wrapped into one component.ts. Seems a bit excessive.

    So components are definitely the way to go. I can imagine that the cards in their core have some functionality shared. You could put this functionality in an abstract class, and have your card components extend this class

    export abstract class CardComponent {
      // some shared logic
    }
    

    And some card component be:

    @Component({...})
    export class SomeCardComponent extends CardComponent {
      constructor() {
        super();
      }
    }
    

    Your suggestion of making an exportable constant map containing a Record<string, Type<T>> sounds very feasible, you could use this in combination with the ngComponentOutlet or if you want more control by using the ComponentFactoryResolver. You can check either of those links for a nice implementation.

    Another way you could do it, is by using the ngSwitch.

    Basically you will have your root template with this switch:

    <ng-container [ngSwitch]="card.id">
      <some-card *ngSwitchCase="'some-card'"></some-card>
      <other-card *ngSwitchCase="'other-card'"></other-card>
      <!-- etc -->
    </ng-container>
    

    On a side note, if you would like to access all cards inside the parent component, you can use a little trick. Update your card components to include a provider:

    @Component({
      selector: 'some-card',
      providers: [
        { provide: CardComponent, useExisting: SomeCardComponent }
      ]
    })
    export class SomeCardComponent extends CardComponent { //... }
    

    In your parent component, you can then query them using @ViewChildren()

    @ViewChildren(CardComponent)
    cardComponents: QueryList<CardComponent>;
    

    This could come in handy for example, to access shared logic from the parent.

    guess I didn't keep it short