简体   繁体   中英

Angular mat-table not updating upon click

Hello i have an issue where my mat-table is not updating after subscribing to a service that retrieves all the details for a particular site. I have a structure that consists of a parent component(DepotSelectionComponent) and two child components InteractiveMapComponent, MapResultComponent. I am retrieving the click event from InteractiveMapComponent which is bubbled up to the parent(DepotSelectionComponent) which is then calling the service and then updating the datasource in MapResultComponent.

Current behaviour:

Table does not update upon clicking and will only update when i click the back button in InteractiveMapComponent, which makes no sense to me.

Expected behaviour:

Table updates upon click and table is updated immediately.

Parent(DepotSelectionComponent) TS:

import { Component, OnInit, ViewChild, NgZone } from '@angular/core';
import { MapResultComponent } from '../map-result/map-result.component';
import { InteractiveMapComponent } from '../interactive-map/interactive-map.component';
import { SiteService } from '../shared/services/site-service';
import { siteDetails } from '../shared/interfaces/site-details.interface';
import { MatTableDataSource } from '@angular/material';

@Component({
  selector: 'app-depot-selection',
  templateUrl: './depot-selection.component.html',
  styleUrls: ['./depot-selection.component.scss']
})
export class DepotSelectionComponent implements OnInit {

  page:string;
  countyName: string;
  @ViewChild(MapResultComponent, {static: false})
  private MapResultComponent: MapResultComponent;

  constructor(private zone: NgZone,
    private _siteService: SiteService) { }

  ngOnInit() {
    this.page = "siteLandingPage";
  }

  home() {
    this.page = "siteLandingPage";
  }

  getDetailsPage() {
    this.page = "siteMoreDetails";
  }

  depotMoreDetails() {
    this.page = "depotMoreDetails"
  }

  populateResults(countyName) {
    this.MapResultComponent.mapResultHeader = countyName.dataObj.label;
    this.MapResultComponent.siteResults.length = 0;
    this._siteService.getSites(countyName.dataObj.label)
    .subscribe((data: any[]) => {
      data.forEach(e => {
      this.MapResultComponent.siteResults.push(new siteDetails(e.Site_ID, e.Address1, e.Address2, e.Town, e.County, e.PostCode, e.Name, e.Description, e.TelephoneNumber));
      })     
    }); 

  this.MapResultComponent.dataSource = new MatTableDataSource(this.MapResultComponent.siteResults);   
  console.log(this.MapResultComponent.dataSource)
}

}

Parent(DepotSelectionComponent) html:

  <div class="main">  

    <div [ngSwitch]="page">

        <div *ngSwitchCase="'siteLandingPage'">
            <div class="interactiveMap">
                <app-interactive-map (siteDetailTable)="populateResults($event)"></app-interactive-map>
            </div>
            <div class="mapResult">
                <app-map-result (moreDetails)="getDetailsPage($event)"></app-map-result>
            </div>
        </div>

        <div *ngSwitchCase="'siteMoreDetails'">
            <div>
                <button mat-raised-button (click)="home()">Back</button>
            </div>
            <div class="clearFloat"></div>
            <app-site-details (depotMoreDetails)="depotMoreDetails($event)"></app-site-details>
        </div>

        <div *ngSwitchCase="'depotMoreDetails'">
            <div>
                <button mat-raised-button (click)="getDetailsPage()">Back</button>
            </div>
            <div class="clearFloat"></div>
            <app-depot-parent></app-depot-parent>
        </div>
    </div>

</div>

Child(InteractiveMapComponent) TS:

import { Component, OnInit, NgZone, EventEmitter, Output, ChangeDetectorRef } from '@angular/core';
import { FusionCharts } from 'fusioncharts';
import { InteractiveMapService } from './interactive-map.service';
import { Observable } from 'rxjs/Rx';
import { CountyDetails } from './county-details.interface';

@Component({
  selector: 'app-interactive-map',
  templateUrl: './interactive-map.component.html',
  styleUrls: ['./interactive-map.component.scss']
})

export class InteractiveMapComponent implements OnInit {

  dataSource: Object;
  map: string;
  mapType:string;
  FusionCharts: FusionCharts;
  test: Array<{id: string, value: string, color: string}> = [];

  @Output() siteDetailTable = new EventEmitter();

  constructor(private _interactiveMapService: InteractiveMapService,
    private zone: NgZone) { }

  ngOnInit() {   
    this.getUKMapData();

    this.mapType = "Site Locations";

    this.dataSource = {
      chart: {
        includevalueinlabels: "1",
        labelsepchar: ": ",
        entityFillHoverColor: "#EE204D",
        theme: "fint",
      }, 
      // Source data as JSON --> id represents counties of england.
      data : this.test,   
    };

  }

  ukMap() {
   this.mapType = "Site Locations";
   this.getUKMapData();
  }

  selectMap(map) {
    switch(map) {
      case "England":
          this.getEnglandMapData();
          this.mapType="England";
      break;

      case "Wales":
        this.getWalesMapData();
        this.mapType="Wales";
      break;

      case "Scotland":
          this.getScotlandMapData();
          this.mapType="Scotland";
      break;

      case "North Ireland":
          this.mapType="Northern Ireland";
      break;

      default:
        ;
    }
  }

  getGBMapData() {  
    this.test.length = 0;   
    this._interactiveMapService.getGBMapData()
    .subscribe((data: any[]) => {
      data.forEach(e => {
        this.test.push(new CountyDetails(e.Map_Data_ID.toString(), e.County, e.Color))    
      })
    })
  }

  getUKMapData() {
    this.test.length = 0;
    this._interactiveMapService.getUKMapData()
    .subscribe((data: any[]) => {
      data.forEach(e => {
        this.test.push(new CountyDetails(e.Map_Data_ID.toString(), e.Country, e.Color));      
      }) 
    });
  }

  getScotlandMapData() {
    this.test.length = 0;
    this._interactiveMapService.getScotlandMapData()
    .subscribe((data: any[]) => {
      data.forEach(e => {
        this.test.push(new CountyDetails(e.Map_Data_ID.toString(), e.County, e.Color));
      })
    })
  }

  getWalesMapData() {
    this.test.length = 0;
    this._interactiveMapService.getWalesMapData()
    .subscribe((data: any[]) => {
      data.forEach(e => {
        this.test.push(new CountyDetails(e.Map_Data_ID.toString(), e.County, e.Color));
      })
    })
  }

  getEnglandMapData() {
    this.test.length = 0;
    this._interactiveMapService.getEnglandMapData()
    .subscribe((data: any[]) => {
      data.forEach(e => {
        this.test.push(new CountyDetails(e.Map_Data_ID.toString(), e.County, e.Color));
      })
    })
  }

  populateResultsTable(event: string) { 
    this.siteDetailTable.emit(event);
  }

  update($event) {
    // Run inside angular context
    this.zone.run(() => {
      this.selectMap($event.dataObj.label);
    })
  }

}

Child(InteractiveMapComponent) html:

    <div class="mapContainer">
    <div class="interactiveMapHeader">
        <span>{{mapType}}</span>
        <div *ngIf="mapType != 'Site Locations'">
            <button mat-raised-button (click)="ukMap()">Back</button>
        </div>
    </div>

    <div [ngSwitch]="mapType">
        <div *ngSwitchCase="'Scotland'">
            <fusioncharts
            width = "500"
            height = "900"
            type="maps/scotland"
            [dataSource]="dataSource"
            (entityClick)="populateResultsTable($event)"
            >
            </fusioncharts>
        </div>

        <div *ngSwitchCase="'Wales'">
            <fusioncharts
            width = "500"
            height = "900"
            type="maps/wales"
            [dataSource]="dataSource"
            (entityClick)="populateResultsTable($event)"
            >
            </fusioncharts>
        </div>

        <div *ngSwitchCase="'England'">     
            <fusioncharts
            width = "500"
            height = "900"
            type="maps/england"
            [dataSource]="dataSource"
            (entityClick)="populateResultsTable($event)"
            >
            </fusioncharts>
        </div>

        <div *ngSwitchCase="'Northern Ireland'">
            <fusioncharts
            width = "500"
            height = "900"
            type="maps/northernireland"
            [dataSource]="dataSource"
            (entityClick)="populateResultsTable($event)"
            >
            </fusioncharts>
        </div>

        <div *ngSwitchDefault>
            <fusioncharts
            width = "500"
            height = "1000"
            type="maps/uk7"
            [dataSource]="dataSource"
            (entityClick)="update($event)"
            >
            </fusioncharts>
        </div>
    </div>          

</div>

Child(MapResultComponent) TS:

import { Component, OnInit, ViewChild, Output, EventEmitter, ChangeDetectorRef, Input, NgZone } from '@angular/core';
import { MatTableDataSource, MatPaginator, MatDialogConfig, MatDialog } from '@angular/material';
import { NewSiteDialogComponent } from '../new-site-dialog/new-site-dialog.component';
import { siteDetails } from '../shared/interfaces/site-details.interface';
import { SiteService } from '../shared/services/site-service';

@Component({
  selector: 'app-map-result',
  templateUrl: './map-result.component.html',
  styleUrls: ['./map-result.component.scss']
})
export class MapResultComponent implements OnInit {

  displayedColumns: string[] = ['name', 'address1', 'moreDetails'];
  number: number;
  mapResultHeader: string;
  result : string;
  mapResults: Array<{name: string, address: string}> = [];
  dataSource : MatTableDataSource<any>;
  siteResults: Array<{Site_ID: number, Address1: string, Address2: string, Town: string, County: string, PostCode: string, Name: string, Description: string, TelephoneNumber: string}> = [];

  @Output() moreDetails = new EventEmitter();

  @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;

  constructor(public dialog: MatDialog,
    private _siteService: SiteService) {
  }

  loadMoreDetailsComp(changed: string) {
    this.moreDetails.emit(changed);
  }

  ngOnInit() {
    this.dataSource = new MatTableDataSource();
    this.mapResultHeader = "Kent";
  }

    /**
   * Set the paginator and sort after the view init since this component will
   * be able to query its view for the initialized paginator and sort.
   */
  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
  }

  applyFilter(filterValue: string) {
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }

  openNewSiteDialog(){
    const dialogConfig = new MatDialogConfig();

    dialogConfig.autoFocus = true;
    dialogConfig.disableClose = true;

    this.dialog.open(NewSiteDialogComponent, dialogConfig);
  }

}

export interface Data {
  name: string;
  town: string;
  address: string;
  telephone: string;
}

Child(MapResultComponent) html:

<div class="resultsContainer">
    <div class="mapResultHeader">
        <span>{{mapResultHeader}}</span>
    </div>

    <div class="mat-elevation-z8">

            <mat-form-field class="filter">
                <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
            </mat-form-field>

            <table mat-table [dataSource]="dataSource">

            <ng-container matColumnDef="name">
                <th mat-header-cell *matHeaderCellDef> Site Name </th>
                <td mat-cell *matCellDef="let element"> {{element.Name}}</td>
            </ng-container>

            <ng-container matColumnDef="address1">
                <th mat-header-cell *matHeaderCellDef> Address </th>
                <td mat-cell *matCellDef="let element"> {{element.Address1}}</td>
            </ng-container>

            <ng-container matColumnDef="moreDetails">
                <th mat-header-cell *matHeaderCellDef> More Details </th>
                <td mat-cell *matCellDef="let element"> 
                    <button mat-raised-button (click)="loadMoreDetailsComp($event)">More Details</button> 
                </td>
            </ng-container>

            <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
            <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
            </table>

            <mat-paginator [pageSize]="[5]" showFirstLastButtons></mat-paginator>

    </div>

    <div class="addNewSite">
        <button mat-raised-button (click)="openNewSiteDialog()">Add New Site</button>
    </div>
</div>

I am absolutely stumped with this, im assuming it has something to do with async behavior but i cant figure it out

Looks like the datasource is being set outside of the subscribe block which is causing it to get set before the data has been loaded. You should just be able to move that line up into the subscribe block like this:

populateResults(countyName) {
    this.MapResultComponent.mapResultHeader = countyName.dataObj.label;
    this.MapResultComponent.siteResults.length = 0;
    this._siteService.getSites(countyName.dataObj.label)
        .subscribe((data: any[]) => {
            data.forEach(e => {
                this.MapResultComponent.siteResults.push(new siteDetails(e.Site_ID, e.Address1, e.Address2, e.Town, e.County, e.PostCode, e.Name, e.Description, e.TelephoneNumber));
            });
            this.MapResultComponent.dataSource = new MatTableDataSource(this.MapResultComponent.siteResults);
        });
}

You may also need a changeDetectorRef.detectChanges() call afterwards if you get a 'x value has changed after change detection' error as this does seem like a chain of updates.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM