How to use the Transaction service

    You may get advantage of the Transaction Service when using any component that needs to preserve the state of its data source and to commit many transactions at once.

    When working with the Ignite UI for Angular grid components, you may use the igxTransactionService and igxHierarchicalTransactionService that are integrated with the grids and provide batch editing out of the box. However, if you need to use transactions with any other Ignite UI for Angular or custom component, you may again use the igxTransactionService and implement similar behavior.

    Angular How to use the Transaction service Example

    In this topic we will use igxList component to demonstrate how to enable transactions. We will demonstrate how to add transactions, how to transform the data through a pipe and how to visually update the view in order to let the user see the changes that are about to be committed.

    Include Transaction Service

    Include Transaction Service in project

    We have two options to include IgxTransactionService in our application. The first one is to add it to AppModule or other parent module in the application, as it is done in the demo above:

    @NgModule({
        ...
        providers: [
            IgxTransactionService
        ]
    })
    export class AppModule { }
    

    The other option is to provide it in the component, where the transaction service is used:

    @Component({
        selector: 'transaction-base',
        styleUrls: ['./transaction-base.component.scss'],
        templateUrl: 'transaction-base.component.html',
        providers: [IgxTransactionService]
    })
    export class TransactionBaseComponent { }
    

    Inject Transaction Service in component

    In our ts file, we should import igxTransactionService from the igniteui-angular library, as well as the State and Transaction interfaces and the TransactionType enum, which will be needed by our application:

    import { IgxTransactionService, State, Transaction, TransactionType } from 'igniteui-angular';
    // import { IgxTransactionService, State, Transaction, TransactionType } from '@infragistics/igniteui-angular'; for licensed package
    

    Then Transaction Service should be imported in the constructor:

    constructor(private _transactions: IgxTransactionService<Transaction, State>) { ... }
    

    Define igxList

    In our html template, we define an igxList component with edit, delete and add actions, which modify the list and its items:

    <igx-list>
        <igx-list-item [isHeader]="true">Wishlist</igx-list-item>
        <igx-list-item *ngFor="let item of this.wishlist | transactionBasePipe"
            [ngClass]="{ deleted: isDeleted(item.id), edited: isEdited(item.id) }">
            <p igxListLineTitle>{{item.name}}</p>
            <p igxListLineSubTitle>Costs: {{item.price}}</p>
            <igx-icon igxListAction (click)="onEdit()" *ngIf="item.id === 1 && item.price !== '$999'">edit</igx-icon>
            <igx-icon igxListAction (click)="onDelete()" *ngIf="item.id === 2 && !isDeleted(item.id)">delete</igx-icon>
        </igx-list-item>
        <button igxButton (click)="onAdd()" [disabled]="itemAdded(4)">Add New</button>
    </igx-list>
    

    Pipe for pending changes

    The list component from above uses the transactionBasePipe to display changes to the items in the wishlist without affecting the original data. Here is how the pipe looks like:

    @Pipe({
        name: 'transactionBasePipe',
        pure: false
    })
    export class TransactionBasePipe implements PipeTransform {
        /**
         * @param transactions Injected Transaction Service.
         */
        constructor(public transactions: IgxTransactionService<Transaction, State>) { }
    
        public transform(data: WishlistItem[]) {
            // the pipe should NOT operate on the original dataset
            // we create a copy of the original data and then use it for visualization only
            const _data = [...data];
            const pendingStates = this.transactions.getAggregatedChanges(false);
    
            for (const state of pendingStates) {
                switch (state.type) {
                    case TransactionType.ADD:
                        // push the newValue property of the current `ADD` state
                        _data.push(state.newValue);
                        break;
                    case TransactionType.DELETE:
                        // pipe doesn't delete items because the demo displays them with a different style
                        // the record will be deleted once the state is committed
                        break;
                    case TransactionType.UPDATE:
                        const index = _data.findIndex(x => x.id === state.id);
                        // merge changes with the item into a new object
                        // to avoid modifying the original data item
                        _data[index] = Object.assign({}, _data[index], state.newValue);
                        break;
                    default:
                        return _data;
                }
            }
    
            return _data;
        }
    }
    

    Edit, delete, add functionality

    Define edit functionality

    The second list item contains an edit button, which updates the item's data.

    <igx-icon igxListAction (click)="onEdit()" *ngIf="item.id === 1 && item.price !== '$999'">edit</igx-icon>
    

    When the button is pressed, inside the onEdit event handler, an 'UPDATE' transaction is created:

    public onEdit(): void {
        const newPrice = "$999";
        // there can be multiple `UPDATE` transactions for the same item `id`
        // the `newValue` property should hold only the changed properties
        const editTransaction: Transaction = {
            id: this.wishlist[0].id,
            type: TransactionType.UPDATE,
            newValue: { price: newPrice }
        };
        // provide the first wishlist item as a `recordRef` argument
        this.transactions.add(editTransaction, this.wishlist[0]);
    }
    

    Additionally, there is a function that checks items for unsaved edits:

    public isEdited(id): boolean {
        const state = this.transactions.getState(id);
        return state && state.type === TransactionType.UPDATE;
    }
    

    Define delete functionality

    The third list item contains a delete button, which deletes the item's data.

    <igx-icon igxListAction (click)="onDelete()" *ngIf="item.id === 2 && !isDeleted(item.id)">delete</igx-icon>
    

    When the button is pressed, inside onDelete event handler, a 'DELETE' transaction is created:

    public onDelete(): void {
        // after a `DELETE` transaction, no further changes should be made for the same `id`
        // the `newValue` property should be set to `null` since we do not change any values,
        const deleteTransaction: Transaction = {
            id: this.wishlist[1].id,
            type: TransactionType.DELETE,
            newValue: null
        };
        // provide the second wishlist item as a `recordRef` argument
        this.transactions.add(deleteTransaction, this.wishlist[1]);
    }
    

    In addition, there is a function that checks items for unsaved deletion:

    public isDeleted(id): boolean {
        const state = this.transactions.getState(id);
        return state && state.type === TransactionType.DELETE;
    }
    

    Define add functionality

    At the end of the list an ADD button is added, which adds a new item to the list.

    <button igxButton (click)="onAdd()" [disabled]="itemAdded(4)">Add New</button>```
    

    When the button is pressed, inside the onAdd event handler, an 'ADD' transaction is created:

    public onAdd(): void {
        // it must have a unique 'id' property
        const item: WishlistItem = { id: 4, name: 'Yacht', price: 'A lot!' };
    
        // in an `ADD` transaction you do not need to provide a `recordRef` argument,
        // since there is nothing to refer to yet
        this.transactions.add({ id: 4, type: TransactionType.ADD, newValue: item });
    }
    

    In addition, there is a function that checks items for unsaved addition:

    public itemAdded(id: number): boolean {
        const found = this.transactions.getState(id) || this.wishlist.find(x => x.id === 4);
        return !!found;
    }
    

    Transaction Log

    The demo demonstrates the pending transactions inside a log:

    <div>
        <h5>Transaction Log</h5>
        <div *ngFor="let transaction of this.getTransactionLog()">
            {{transaction.type.toUpperCase()}} -> {{transaction.name}} Costs: {{transaction.price}}
        </div>
    </div>
    
    public getTransactionLog(): any[] {
        return this.transactions.getTransactionLog().map(transaction => {
            const item = this.wishlist.find(x => x.id === transaction.id);
            return Object.assign({ type: transaction.type }, item, transaction.newValue);
        });
    }
    

    We will also add a representation of the current state of our list. It will show how the data looks before the pending transactions are committed:

    <div>
        <h5>Data Items</h5>
        <div *ngFor="let item of this.wishlist">
            <div>{{item.name}} - {{item.price}}</div>
        </div>
    </div>
    

    Commit pending transactions

    Once we are done with all our changes, we may commit them all at once using the commit method of the igxTransactionService. It applies all transactions over the provided data:

    <button igxButton="contained" (click)="onCommit()" [disabled]="this.getTransactionLog().length === 0">Commit Transactions</button>
    
    public onCommit(): void {
        // the `commit` function expects the original data array as its parameter
        this.transactions.commit(this.wishlist);
    }
    
    

    If we are using the igxHierarchicalTransactionService we can also use an overload of the commit method which expects primaryKey and childDataKey as arguments.

    public onCommit(): void {
        this.transactions.commit(this.wishlist, primaryKey, childDataKey);
    }
    

    Clear pending transactions

    At any point of our interaction with the list, we may clear the Transaction log, using the clear method.

    <button igxButton="contained" (click)="onClear()" [disabled]="this.getTransactionLog().length === 0">Clear Transactions</button>
    
    public onClear(): void {
        this.transactions.clear();
    }
    
    

    Additional Resources