Update store from detail view

81 Views Asked by At

I have a list of accounts in the store that is displayed on the screen.

enter image description here When the user presses a button a single entity is fetched from the store via a selector "selectEntity".

The user can then update the item and the store is updated accordingly via an action

this.store.dispatch(updateAccount({ account }));

enter image description here

This also works. The problem is, that after that the entity should not be visible on the screen anymore. For that reset is called

  reset() {
    this.editAccount = {
      id: 0,
      name: '',
      number: 0,
      bookId: 0,
      parentId: 0,
      children: [],
    };
  }

The entity is still visible on the screen (which is unexpected behaviour). If I call reset without updating the store, the screen does not show the entity anymore (which is expected behaviour). I guess what happens is that the store is updated and because there is a subscription on the selector "selectEntity" the entity is loaded again

enter image description here

Is there a better way to load a single enity from the store and dont have it updated after the first load?

The component

export class AccountListComponent implements OnInit {
  private _transformer = (node: AccountNode, level: number) => {
    return {
      expandable: !!node.children && node.children.length > 0,
      name: node.name,
      level: level,
      id: node.id,
      number: node.number,
    };
  };
  editAccount: Account = {
    id: 0,
    name: '',
    number: 0,
    bookId: 0,
    parentId: 0,
    children: [],
  };
  treeControl = new FlatTreeControl<AcccountFlatNode>(
    (node) => node.level,
    (node) => node.expandable
  );

  treeFlattener = new MatTreeFlattener(
    this._transformer,
    (node) => node.level,
    (node) => node.expandable,
    (node) => node.children
  );
  dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
  accounts: Account[] = [];
  loading$ = this.store.pipe(select(selectAccountState));
  hasChild = (_: number, node: AcccountFlatNode) => node.expandable;
  selectAccountTreeSubscription: Subscription;
  modalSubscription: Subscription | undefined;

  constructor(
    private store: Store,
    public dialog: MatDialog,
    private router: Router
  ) {
    this.selectAccountTreeSubscription = this.store
      .pipe(select(selectAccountTree))
      .subscribe((accountsAsTree) => {
        this.dataSource.data = accountsAsTree;
        this.treeControl.expandAll();
      });

    this.selectAccountTreeSubscription = this.store
      .pipe(select(selectAllAccounts))
      .subscribe((accounts) => {
        this.accounts = accounts;
      });

    this.dataSource.data = [];

  }

  ngOnInit(): void {
    this.loadAccounts();
  }
  @ViewChild('tree') tree: any;
  ngOnDestroy() {
    this.selectAccountTreeSubscription.unsubscribe();
    if (this.modalSubscription) this.modalSubscription.unsubscribe();
  }
  edit(id: number): void {
    this.store
      .pipe(
        select(
          selectEntity(
            ((): number => {
              return id;
            })()
          )
        )
      )
      .subscribe((selectedAccount) => {
        this.editAccount = Object.assign({}, selectedAccount);
      });
  }
  cancel() {
    this.reset();
  }
  reset() {
    this.editAccount = {
      id: 0,
      name: '',
      number: 0,
      bookId: 0,
      parentId: 0,
      children: [],
    };
  }
  
  update() {
    const account: Account = {
      id: this.editAccount.id,
      name: this.editAccount.name,
      number: this.editAccount.number,
      bookId: this.editAccount.bookId,
      parentId: this.editAccount.parentId,
      children: [],
    };

    this.store.dispatch(updateAccount({ account }));
    this.reset();
  }

  loadAccounts(): void {
    this.store.dispatch(loadAccounts());
  }
}

the template

<button mat-raised-button (click)="addAccountDialog()">Add Account</button>

<mat-tree #tree [dataSource]="dataSource" [treeControl]="treeControl">
  <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
    <a
      [routerLink]="['/accounts', node.id]"
      style="padding-left: 20px; margin-right: 10px"
    >
      {{ node.number }} -
      {{ node.name }}
    </a>
    <button mat-raised-button (click)="edit(node.id)">edit</button>
  </mat-tree-node>

  <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
    <button
      mat-icon-button
      matTreeNodeToggle
      [attr.aria-label]="'Toggle ' + node.name"
    >
      <mat-icon class="mat-icon-rtl-mirror">
        {{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
      </mat-icon>
    </button>
    <a [routerLink]="['/accounts', node.id]" style="margin-right: 10px">
      {{ node.number }} -
      {{ node.name }}
    </a>
    <button mat-raised-button (click)="edit(node.id)">edit</button>
  </mat-tree-node>
</mat-tree>
<mat-card *ngIf="editAccount.id != 0" style="max-width: 500px">
  <mat-card-header>
    <mat-card-title>edit account</mat-card-title>
    <mat-card-subtitle>{{ editAccount.name }}</mat-card-subtitle>
  </mat-card-header>

  <mat-form-field class="account-form-field">
    <mat-label>Name input</mat-label>
    <input matInput type="text" [(ngModel)]="editAccount.name" />
  </mat-form-field>
  <mat-form-field class="account-form-field">
    <mat-label>Number input</mat-label>
    <input matInput type="text" [(ngModel)]="editAccount.number" />
  </mat-form-field>

  <mat-card-actions align="end">
    <button mat-raised-button (click)="update()" color="primary">Save</button>
    <button mat-raised-button (click)="cancel()">Cancel</button>
  </mat-card-actions>
</mat-card>

the selectors

import { createFeatureSelector, createSelector } from '@ngrx/store';
import { Account } from 'src/app/shared/account';
import * as fromAccount from './account.reducer';

export const selectAccountState = createFeatureSelector<fromAccount.State>(
  fromAccount.accountFeatureKey
);

export const selectSelectorsLoading = createSelector(
  selectAccountState,
  (state) => state.loading
);

export const selectAllAccounts = createSelector(
  selectAccountState,
  fromAccount.selectAll
);

export const selectEntity = (id: number) =>
  createSelector(selectAccountState, (state) => state.entities[id]);

export const selectAccountTree = createSelector(selectAllAccounts, (accounts) =>
  getTree(accounts)
);

function getTree(nodes: Account[]): Account[] {
  var mutableNodes: Account[] = JSON.parse(JSON.stringify(nodes));
  var tree = new Array<Account>();
  mutableNodes
    .filter((n) => n.parentId === null)
    .forEach((n) => tree.push(getNodeWithChildren(mutableNodes, n)));
  return tree;
}

function getNodeWithChildren(nodes: Account[], node: Account): Account {
  var children = new Array<Account>();
  nodes
    .filter((n) => n.parentId === node.id)
    .forEach((n) => children.push(getNodeWithChildren(nodes, n)));

  if (children.length > 0) {
    node.children = children;
  }

  return node;
}

This is an aproach that gets close

  const outerObservable$ = interval(1000).pipe(
      take(3),
      tap((x) => console.log(`outer ${x}`))
    );

    const innerObservable$ = interval(300).pipe(
      take(20),
      tap((x) => console.log(`inner ${x}`)),
      map((x) => x + 1000)
    );

    const example = outerObservable$
      .pipe(
        withLatestFrom(innerObservable$),
        map(([first, second]) => {
          return `First Source (5s): ${first} Second Source (1s): ${second}`;
        })
      )
      .subscribe((x) => console.log(x));

The only problem unsolved is that I need to feed a the parameter into innerObservable something like

  const innerObservable$ = interval(intervalComesFromOuterObservable)
0

There are 0 best solutions below