I have a list of accounts in the store that is displayed on the screen.
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 }));
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
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)

