Viky
Viky

Reputation: 63

Angular mat-table not refresh after adding new row

(Angular 8)

I have two component addgame and home, In home I displayed all games stored in database with the help of REST API.

In home component, by using Mat-dialog I called game component in dialog view.

The problem is if I have add game in that mat-dialog, it is successfully added But Only after refreshing home component manually that'll updated in mat-table

I need that mat-table update without any manual refreshing

Note : I tried ChangeDetectorRef, triggered MatPaginator and all possible answers from here but no changes

home.component.html

<div class="search-div">
    <button mat-raised-button (click)="onCreateGame()"><mat-icon >add</mat-icon>Create Guest</button>
    <mat-form-field class="search-form-field" floatLabel="never">
        <input matInput [(ngModel)]="searchKey" placeholder="Search" autocomplete="off" (keyup)="applyFilter()" >
        <button mat-button matSuffix mat-icon-button aria-label="Clear" *ngIf="searchKey" (click)= "onSearchClear()">
            <mat-icon>close</mat-icon>
        </button>
    </mat-form-field>

</div>

<div class="mat-elevation-z8">
    <mat-table [dataSource]="listData">
        <ng-container matColumnDef="gameName">
            <mat-header-cell *matHeaderCellDef>Game Name</mat-header-cell>
            <mat-cell *matCellDef="let element">{{element.gameName}}</mat-cell>
        </ng-container>
        <ng-container matColumnDef="gameDate">
            <mat-header-cell *matHeaderCellDef>Game Date</mat-header-cell>
            <mat-cell *matCellDef="let element">{{element.gameDate | date}}</mat-cell>
        </ng-container>
        <ng-container matColumnDef="gameVenue">
            <mat-header-cell *matHeaderCellDef>Game Venue</mat-header-cell>
            <mat-cell *matCellDef="let element">{{element.gameVenue}}</mat-cell>
        </ng-container>
        <ng-container matColumnDef="homeTeam">
            <mat-header-cell *matHeaderCellDef>Home Team</mat-header-cell>
            <mat-cell *matCellDef="let element">{{element.homeTeam}}</mat-cell>
        </ng-container>
        <ng-container matColumnDef="awayTeam">
            <mat-header-cell *matHeaderCellDef>Away Team</mat-header-cell>
            <mat-cell *matCellDef="let element">{{element.awayTeam}}</mat-cell>
        </ng-container>
        <ng-container matColumnDef="numberOfGuest">
            <mat-header-cell *matHeaderCellDef>No of Guest</mat-header-cell>
            <mat-cell *matCellDef="let element">{{element.numberOfGuest}}</mat-cell>
        </ng-container>
        <ng-container matColumnDef="loading">
            <mat-footer-cell *matFooterCellDef colspan="6">
                Loding data...
            </mat-footer-cell>
        </ng-container>
        <ng-container matColumnDef="noData">
            <mat-footer-cell *matFooterCellDef colspan="6">
                No data.
            </mat-footer-cell>
        </ng-container>
        
        <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
        <mat-row *matRowDef="let row; columns:displayedColumns;"></mat-row>

        <mat-footer-row *matFooterRowDef="['loading']" [ngClass]="{'hide': listData!=null}"></mat-footer-row>
        <mat-footer-row *matFooterRowDef="['noData']" [ngClass]="{'hide': !(listData!=null && listData.data.length==0)}"></mat-footer-row>
    </mat-table>
    <mat-paginator [pageSizeOptions]="[5,10,25,100]" pageSize="5" showFirstLastButtons></mat-paginator>

</div>

home.component.ts

import { LoginService } from './../service/login.service';
import { AddgameComponent } from './../addgame/addgame.component';
import { GameService } from './../service/game.service';
import { MatTableDataSource, MatTable } from '@angular/material/table';
import { Component, OnInit, ViewChild, ChangeDetectorRef, AfterContentChecked, OnDestroy } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';


@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit{

  constructor(public loginService : LoginService,public gameService :GameService, public dialog : MatDialog) { }

  listData : MatTableDataSource<any>;

  @ViewChild(MatPaginator) paginator : MatPaginator

 
  displayedColumns : string[] = ['gameName','gameDate','gameVenue', 'homeTeam','awayTeam','numberOfGuest'];
  ngOnInit(): void 
  {
    this.starter();

  }

  starter()
  {
    this.gameService.getAllGamesFromRemote().subscribe(
      data =>{
        let array = data;
        this.listData = new MatTableDataSource(array);
        this.listData.paginator = this.paginator;
        this.listData.filterPredicate = (data, filter) => (data.gameName.trim().toLowerCase().indexOf(filter.trim().toLowerCase()) !== -1 ||data.homeTeam.trim().toLowerCase().indexOf(filter.trim().toLowerCase()) !== -1 );
      });

  }

  searchKey : string = "";


  onSearchClear()
  {
    this.searchKey = "";
    this.applyFilter();
  }

  applyFilter()
  {
    this.listData.filter = this.searchKey.trim().toLowerCase();
  }

  onCreateGame()
  {
    const dialogConfig = new MatDialogConfig();
    dialogConfig.disableClose = true;
    dialogConfig.autoFocus=true;
    dialogConfig.width="60%";
    this.dialog.open(AddgameComponent, dialogConfig);
  }

}

addgame.component.html

<mat-toolbar>
    <span>Add new Game</span>
    <span class="filler"></span>
    <button class="btn-dialog-close" mat-stroked-button (click)="onClose()" tabindex="-1"><mat-icon>close</mat-icon></button>
</mat-toolbar>

<form class="normal-form" [formGroup]="gameService.gameForm" autocomplete="off" (submit)="onSubmit()">
    <div class="controls-container">
        <mat-form-field>
            <input matInput formControlName="gameName" placeholder="Game Name*">
            <mat-error *ngIf="gameService.gameForm.controls['gameName'].errors?.required">This is Mandatory Field</mat-error>
            <mat-error *ngIf="gameService.gameForm.controls['gameName'].errors?.notUnique">Game name should be Unique</mat-error>
        </mat-form-field>
        <mat-form-field>
            <input matInput formControlName="gameVenue" placeholder="Game Venue*">
            <mat-error>This is Mandatory Field</mat-error>
        </mat-form-field>
        <mat-form-field>
            <input matInput formControlName="homeTeam" placeholder="Home Team*">
            <mat-error>This is Mandatory Field</mat-error>
        </mat-form-field>
        <mat-form-field>
            <input matInput formControlName="awayTeam" placeholder="Away Team*">
            <mat-error>This is Mandatory Field</mat-error>
        </mat-form-field>
        <mat-form-field>
            <input matInput formControlName="gameDate"  [matDatepicker]="picker" placeholder="Game Date*">
            <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
            <mat-datepicker #picker></mat-datepicker>
            <mat-error>This is Mandatory Field</mat-error>
        </mat-form-field>
        <input type="hidden" formControlName="gameName" placeholder="Game Name*">
        <div style="text-align: center;">
            <button mat-raised-button color="primary" type="submit" [disabled]="gameService.gameForm.invalid">Add Game</button>
        </div>
    </div>
</form>

addgame.component.ts

import { Router } from '@angular/router';
import { NotificationService } from './../service/notification.service';
import { GameService } from './../service/game.service';
import { Component, OnInit } from '@angular/core';
import { DatePipe } from '@angular/common';
import { MatDialogRef } from '@angular/material/dialog';

@Component({
  selector: 'app-addgame',
  templateUrl: './addgame.component.html',
  styleUrls: ['./addgame.component.css']
})
export class AddgameComponent implements OnInit {

  constructor(public gameService : GameService, public notificationService : NotificationService, private datePipe: DatePipe, public  dialogRef : MatDialogRef<AddgameComponent>, public router : Router) { }

  ngOnInit(): void {


  }

  onSubmit()
  {
    this.gameService.addGameToRemote(this.gameService.gameForm.value).subscribe(
      data=>{
        
        this.notificationService.success("Game Added Sucessfully");
        this.gameService.initializeForm();
        console.log("Game added");
        this.gameService.gameForm.reset();
        this.onClose();
        

      },
      error=>{
        this.gameService.gameForm.controls['gameName'].setErrors({"notUnique": true});
        this.notificationService.error("Game name already taken");
        console.log("Error in adding Game")
      }
    )

    
  }

  onClose()
  {
    this.gameService.gameForm.reset();
    this.gameService.initializeForm();
    this.dialogRef.close();
  }

}

Upvotes: 1

Views: 2933

Answers (3)

Subrahmanya V
Subrahmanya V

Reputation: 41

Given:

Data source : listData

Data source type : MatTableDataSource

starter() : populates listData by invoking an API this.gameService.getAllGamesFromRemote()

dialogConfig, a MatDialog : used for entry of new Game details

Steps to follow:

  1. Get newGame data as a JSON object of type matching your Game details in listData array

  2. Push this new Game JSON object into DataSource Array object using;

    this.listData.data.push(<new Game JSON object)

  3. Add below line after .push()

    this.listData.data._updateChangeSubscription();

This will ensure that the new entry is immediately reflected in your table.

Note:

  1. You may want to persist the new Game data before updating the table
  2. push() adds the new Game to the end of the table array
  3. Since you are using MatPaginator, you may want to reset to First page

Upvotes: 1

Owen Kelvin
Owen Kelvin

Reputation: 15098

Below would be my approach

Declare an observable

allGamesFromRemote$ = this.gameService.getAllGamesFromRemote().pipe(
  tap( data =>{ 
    let array = data;
    this.listData = new MatTableDataSource(array);
    this.listData.paginator = this.paginator;
    this.listData.filterPredicate = (data, filter) => 
      (data.gameName.trim().toLowerCase().indexOf(filter.trim().toLowerCase()) !== -1 || data.homeTeam.trim().toLowerCase().indexOf(filter.trim().toLowerCase()) !== -1 );
  });

We now need a way to trigger the observable every time you make a change For this we can declare two variables

remoteChangedSubject$ = new BehaviorSubject<any>(true);
remoteChangedAction$ = this.remoteChangedSubject$.asObservable()

Next would be to combine the two observables

// Make sure combineLatest to import from 'rxjs'
allGames$ = combineLatest([this.allGamesFromRemote$, this.remoteChangedAction$]).pipe(
  map(([allGames]) => allGames)
)

Finally after successful addition, you would call

this.remoteChangedSubject$.next(true);

In your html you then wrap the table in an *ngIf using async pipe to handle subscriptions for us

<ng-container *ngIf='allGames$ | async'> 

<!-- Your Table Code Here-->

</ng-container>

This way whenever you call this.remoteChangedSubject$.next(true);, allGames$ observable is reevaluated and change detection kicks in

Upvotes: 1

Gopal Mishra
Gopal Mishra

Reputation: 1935

You can try to recall the REST API to fetch all games again after the dialog is closed. For that you can use the below code:

 const dialogConfig = new MatDialogConfig();
    dialogConfig.disableClose = true;
    dialogConfig.autoFocus=true;
    dialogConfig.width="60%";
    dialogConfig.data = {somedata: 'data'};
    const dialogId = this.dialog.open(AddgameComponent, dialogConfig);
    const dialogSubmitSubscription = dialogId .componentInstance.onDialogueClose.subscribe(result => {
      if(result) {
          this.starter();
        }
      dialogSubmitSubscription.unsubscribe();
  });

Also, if your table is not updating after data is changed dynamically then you might need to call Angular's ngOnChanges Hook.

  ngOnChanges(changes: SimpleChange) {
    if (changes["tableData"]) {
      this.data = new MatTableDataSource(this.tableData);
      if (this.data != undefined) {
        this.data = new MatTableDataSource(this.tableData);
        this.data.sort = this.sort;
      }
      setTimeout(() => {
        this.data.paginator = this.paginator;
        this.data.sort = this.sort;
      });
    }
  }

Hope this answer helps to solve your issue.

Upvotes: 1

Related Questions