Search code examples
angularangular2-templateangular2-componentsangular2-highcharts

How to remove a chart and create a new one each time that my behaviorSubject change its values


I have a problem because I am trying to show some results about countries into a Pie Chart(HightCharts). I assign this information to a BehaviorSubject after it comes from an API call into the ApiService.

The first time, I receive the information in my chartComponent, it creates a Chart but the second one I need delete this chart and create a new one because users can modify results through a form that filters by continent and kind of metrics like population or AreaInSqkm.

This is the Api Call in the service:

      public dataObservable = this.data.asObservable();
      private data = new BehaviorSubject<any>(null);

  
  direction: String = "dsc";
  

  public filters: any = {
        continent : "All",
        metric: "None",
        quantity: 5,
  }; 


  url = "some URL";  

    constructor(private http : Http) { }
      getCountries(){
          return this.http.get(this.url)
            .map(res=>res.json())
            .map((data)=>data.geonames.filter((country)=>{
              if(this.filters.continent !== "All"){
                return country.continent == this.filters.continent
              }else{
                return country.continent
              }
            })
          )
            .map((data)=>{        
              if(this.filters.metric == "population"){
                return this.sortByPopulation(data, this.direction).slice(0, this.filters.quantity)
              }else if(this.filters.metric == "areaInSqKm"){
                return this.sortByArea(data, this.direction).slice(0, this.filters.quantity)        
              }
              return data.slice(0, this.filters.quantity);
            })
            .subscribe((data)=>{
              this.setData(data);
            })
          
        }


  sortByPopulation(data, direction){
    if(direction =="asc"){
      data.sort((a, b)=>{
        return a["population"] - b["population"]
       })
    } else{
      data.sort((a, b)=>{
        return b["population"] - a["population"]
       })
    }
    return data
  }

  sortByArea(data, direction){
    if(direction =="asc"){
      data.sort((a, b)=>{
        return a["areaInSqKm"] - b["areaInSqKm"]
       })
    } else{
      data.sort((a, b)=>{
        return b["areaInSqKm"] - a["areaInSqKm"]
       })
    }
    return data
  }

  updateFilters(filters){
    this.filters = filters
  }


  setData(data){
    this.data.next(data)
  }
  

As you can see, I pass the data as the value of the BehaviorSubject and I receive information the changes in this value every time that someone modifies the form.

Later I create the chart in my chartComponent like this:

@Component({
  selector: 'chart',
  templateUrl: './chart.component.html',
  styleUrls: ['./chart.component.scss']
})
export class ChartComponent implements OnInit, AfterViewInit {
  
    element : any;
    opts: Object = {};
    container : any;

    @Input()
    values: Array<Object>;

    getCountriesSubscription: Subscription = null;

    
    
    chartValues: any = []

  @ViewChild('chart') chart: ElementRef
  constructor(
      private renderer : Renderer2,
      private api : ApiService, 
      private cdRef : ChangeDetectorRef
      
      ) { }

  ngOnInit() {

 

  }

  ngAfterViewInit(){

    this.getCountriesSubscription = this.api.dataObservable
    .subscribe((data)=>{
        if(data){
            data.map((element)=>{
                this.generateChartValues(element, this.api.filters.metric)
            })

            this.generateChartOptions(this.chartValues);
            this.createChart() 
            
            this.cdRef.markForCheck();
            console.log(this.chartValues[0])
        } 

    })
  
  }

  createChart(){

    this.container = this.renderer.createElement("div");
    this.renderer.addClass(this.container, "mychart");
    let chart = new Highcharts.Chart(this.container,this.opts)
    this.renderer.appendChild(this.chart.nativeElement, this.container)
    console.log(this.container) 
     
    this.cdRef.markForCheck();
    
  }
  


  generateChartOptions(chartValues){
    this.opts = {
        chart: {
            plotBackgroundColor: null,
            plotBorderWidth: null,
            plotShadow: false,
            type: 'pie'
        },
        title: {
            text: `Countries in by ${this.api.filters.metric}`
        },
        series: [{
        name: 'Total',
        colorByPoint: true,
        data: chartValues
        }]
    }; 
  }

  generateChartValues(data, metric){
    if(metric == "population"){
        this.chartValues.push({ name: data.countryName, y: parseInt(data.population) });
    }
    if(metric == "areaInSqKm"){
        this.chartValues.push({ name: data.countryName, y: parseInt(data.areaInSqKm) });
    } 
    if(metric == "None"){      
        this.chartValues.push({ name: data.countryName, y: 1});
    }
  }


}
<div #chart class="chartplace">
    
</div>

The main problem is that I am not able to check if exist a chart before and if it exists delete it a create a new one with the result caught through the subscription. I suppose that I should add something at the beginning of the createChart function with the Rederer2 Service but I don't know how.

Thanks!


Solution

  • I would avoid renderer altogether and use the angular2-highcharts library.

    You can pass in the options to the chart element the library provides. You can also attach an event that will give you the chart object after the chart renders. This will allow you to remove/add series as you need.

    // component.html
    <chart [options]="options" (load)="saveInstance($event.context)"></chart>
    
    // component.ts
    saveInstance(chartInstance) {
        this.chart = chartInstance;
    }
    

    In your subscribe to your behavior subject observable, you can check if the chart exists. If it does, update the existing chart. If it does not, create the initial chart

    // component.ts
    .subscribe(data => {
        // if chart exists, update chart
        if(this.chart){
            // remove existing series, and add new series
            this.chart.series[0].remove(false);
            this.chart.addSeries({
                data: data
            }, true);
        }else{
            // else generate chart initially
            this.options = this.generateOptions(data);
        }
    });
    

    Here is a plnkr demoing this functionality


    Sidenote: you typically shouldn't subscribe within your service. Instead the service should expose observables that the component can subscribe to. In this case the solution may look something like this plnkr