Skip to main content

Dirty Check

The DirtyCheckPlugin is useful for cases when you want an indication whether the state is dirty (data in the store has been modified). For example, you may want to display a save button only if the user changes something.

To activate the plugin you need to create a new instance of DirtyCheckPlugin, providing it with the Query:

widgets.component.ts
import { DirtyCheckPlugin } from '@datorama/akita';

export class WidgetsComponent {
widgets$ = this.widgetsQuery.selectAll();
private dirtyCheck: DirtyCheckPlugin;

constructor(private widgetsQuery: WidgetsQuery) {}

ngOnInit() {
this.dirtyCheck = new DirtyCheckPlugin(this.widgetsQuery).setHead();
}

reset() {
this.dirtyCheck.reset();
}

ngOnDestroy() {
this.dirtyCheck.destroy();
}
}

From the moment you call setHead(), Akita's DirtyCheckPlugin takes the current store snapshot and saves it as the head (the value that we compare against). With every change to the store the plugin will compare it to the head value and notify you whether the state is dirty.

widgets.component.html
<button [disabled]="!(dirtyCheck.isDirty$| async)" (click)="save()">Save Changes</button>

By calling reset() you are telling the plugin to update the store with the head value. The plugin also provides a special method called isPathDirty() that checks whether a given path is dirty. For example:

const dirtyCheck = new DirtyCheckPlugin(widgetsQuery).setHead();
dirtyCheck.isPathDirty('check.this.path');

If you have set the head, you can get the current head value with getHead().

Options

comparator

The default comparator compares the object by using the native JSON.stringify() method, but you can pass a custom comparator, for example:

import { isEqual } from 'lodash.isequal';

const options = { comparator: (a, b) => !isEqual(a, b) };
const dirtyCheck = new DirtyCheckPlugin(widgetsQuery, options);

watchProperty

The dirty check plugin can watch specific properties in your store's state and not just the entire store, this can be achieved by passing the properties keys to DirtyCheckPlugin, for example:

// Tracks the entire store
new DirtyCheckPlugin(widgetsQuery);

// Tracks the store's state name property
new DirtyCheckPlugin(widgetsQuery, { watchProperty: 'name' });

// Tracks a set of properties
new DirtyCheckPlugin(widgetsQuery, { watchProperty: ['name', 'color'] });

// In case of an EntityStore we can also track all the entities
new DirtyCheckPlugin(widgetsQuery, { watchProperty: 'entities' });

After the first time you call setHead(), each subsequent call to this method will re-set the current store value as the head and update the dirtiness to false.

EntityDirtyCheckPlugin

In addition to the general dirty check functionality, Akita also provides a powerful API to help keep track of one or many entities, instead of the entire store.

A good example is when you have a table or a list of entities that the users can modify, and you want to give them a way to revert it per entity. Here is how you can do it:

widgets.component.ts
import { EntityDirtyCheckPlugin } from '@datorama/akita';

export class WidgetsComponent {
widgets$ = this.widgetsQuery.selectAll();
private collection: EntityDirtyCheckPlugin;

constructor(private widgetsQuery: WidgetsQuery) {}

ngOnInit() {
this.collection = new EntityDirtyCheckPlugin(this.widgetsQuery);
this.collection.setHead();
}

updateWidget(id: ID, name: string) {
this.widgetService.updateWidget(id, name);
}

reset(ids) {
this.collection.reset(ids);
}

ngOnDestroy() {
this.collection.destroy();
}
}

With this setup you can track the dirtiness per entity and revert it.

widgets.component.html
<tbody>
<tr *ngFor="let widget of widgets$ | async">
<td>
<input [value]="widget.name" #name />
</td>
<td>
<button (click)="updateWidget(widget.id, name.value)">Save</button>
</td>
<td>
<button (click)="revert(widget.id)" [disabled]="!(collection.isDirty(widget.id) | async)">Revert</button>
</td>
</tr>
</tbody>
tip

EntityDirtyCheckPlugin doesn't track the entities count. It only tracks changes on the entities themselves. If you want to track the addition or removal of entities, you can do so by using the DirtyCheckPlugin and watch the entities property.

Sometimes it's useful to partially reset the entity value when clicking on revert. The revert() method can accept a custom update function which receives as parameters the current head and the current entity value, and returns the modified entity. For example:

const updateFn = (head, current) => {
return {
...head,
title: current.title,
};
};

collection.reset(entityId, { updateFn });

In the above example we are reverting the whole entity except for the title (note that this will still mark the entity as dirty).

Sometimes it's also useful to check whether at least one of the entities is dirty. For this you can use the someDirty() method:

collection.someDirty().subscribe(console.log);

Options

entityIds

A single id or an array of entity ids (default: all).

const options = { entityIds: [1, 2] };
stateHistory = new EntityDirtyCheckPlugin(widgetsQuery, options);