Search code examples
angularangular2-changedetection

ngOnChange() is not working secondtime in angular2


I am working in angular2. I am passing a data from one Parent component to another child component.

<app-datatable-repr [myFilterData]="filterData"></app-datatable-repr> 

filterData is a object.

First time OnChanges detect the change of "filterData" but in the second time ngChange is not detect the change of "filterData".

How to solve this issue?

har-file-upload.component.ts

import { Component, OnInit } from '@angular/core';
import { HarFileServiceService } from '../har-file-service.service';

@Component({
  selector: 'app-har-file-upload',
  templateUrl: './har-file-upload.component.html',
  styleUrls: ['./har-file-upload.component.css']
})

export class HarFileUploadComponent implements OnInit {

  constructor(private harFileServiceService:HarFileServiceService) { }

  ngOnInit() {
  }
  filterData:Object = {};
  changeListener(event:any){
    var that = this;
    var file = event.target.files[0];
    var myReader = new FileReader();
    var harFile = this.harFileServiceService;
    myReader.onload = function(e:any){
      var jsonData = JSON.parse(e.target.result);
      harFile.render(jsonData);
      that.filterData = harFile.responseFileData;
      // console.log(that.filterData)
    }
    // console.log(file);
    myReader.readAsText(file);
  }
}

har-file-service.service.ts

import { Injectable } from '@angular/core';
import * as $ from 'jquery';

@Injectable()
export class HarFileServiceService {
    constructor() { }
    log:Object = {  entries: {} };
    totals:Object = {};
    pads:Object = {};
    left;
    right;
    idctr:number = 0;
    reqCount:number = 0;
    totalReqSize:number = 0;
    totalRespSize:number = 0;
    requestObj:Object = {'resources':[]};
    responseFileData:any = {};
    render(har: any) {
        var pageref;
        var that = this;
        $.each(har.log.entries, function (index, entries) {
            console.log(index, entries);
            var singleReqOb = {};
            that.requestObj['resources'].push(singleReqOb);
            pageref = pageref || entries.pageref;
            if(entries.pageref === pageref) {
                that.entry(index, entries);
            }
        });
        this.responseFileData = this.requestObj;
        console.log(this.responseFileData);
    }
    entry(id:any, entry:any) {
        id = id || this.idctr++;
        this.log['entries'][id] = entry;
        var t = new Date(entry.startedDateTime).getTime();
        if(this.left && this.right) {
            this.left = (this.left < t) ? this.left : t;
            this.right = (this.right > t) ? this.right : t;
        }
        else {
            this.left = this.right = t;
        }
        if(entry.request) {
            this.request(id, entry.request);
        }
        if(entry.response) {
            this.response(id, entry.response);
        }
    }
    request(id:any, request:any) {
        if(this.log['entries'][id]) {
            this.log['entries'][id].request = request;
        }
        else {
            this.log['entries'][id] = {
                id: id,
                request: request
            };
        }
        this._updateRequest(id, request);
        this.reqCount = this.reqCount + 1;
        if(request.headersSize && request.headersSize > 0) {
            this.totalReqSize = this.totalReqSize + request.headersSize;
        }
        if(request.bodySize && request.bodySize > 0) {
            this.totalReqSize = this.totalReqSize + request.bodySize;
        }
    }
    response(id:any, response:any) {
        if(this.log['entries'][id]) {
            this.log['entries'][id].response = response;
            this._updateResponse(id, response);

            if(response.headersSize && response.headersSize > 0) {
                this.totalRespSize = this.totalRespSize + response.headersSize;
            }
            if(response.bodySize && response.bodySize > 0) {
                this.totalRespSize = this.totalRespSize + response.bodySize;
            } 
        }
        else {
        }
    }
     _updateRequest(id:any, request:any) {
        var reqObj = this.requestObj['resources'][id]; 
        if(request.url) {
            reqObj['filePath'] = request.url;
        }
    };
    _updateResponse(id:any, response:any) {
        var reqObj = this.requestObj['resources'][id];
        var type =  response.content.mimeType;
        var type_0 = type.split("/")[0];
        var type_1 = type.split("/")[1];
        switch (type_1) {
            case "javascript":
            case "x-javascript":
                reqObj['type'] = 'script';
                break;
            case "css":
            case "json":
            case "html":    
                reqObj['type'] = type_1;
                break;
            case "x-shockwave-flash":
                reqObj['type'] = 'flash';
                break;
            default:
                reqObj['type'] = type;
                break;
        }
        if(type_0 == 'image' || type_0 == 'video'){
            reqObj['type'] = type_0;
            reqObj['type'] = (reqObj['type'] == 'image') ? 'image' : reqObj['type'];
        }
        if(response.content && response.content.text) {   
            reqObj['size'] = response.bodySize;
        }else{
            reqObj['size'] = '';
        }
    }
}

ngOnChange Here

import { Component, OnInit , OnChanges, SimpleChanges, Input} from '@angular/core';
import * as $ from 'jquery';
import 'datatables.net'
import { HarFileServiceService } from '../har-file-service.service';

@Component({
  selector: 'app-datatable-repr',
  templateUrl: './datatable-repr.component.html',
  styleUrls: ['./datatable-repr.component.css']
})
export class DatatableReprComponent implements OnInit, OnChanges {
  @Input() myFilterData;
  constructor(private harFileServiceService:HarFileServiceService) { }
  public tableWidget: any;
  ngOnInit() {
    this.initDatatable();
  }

  ngOnChanges(changes:SimpleChanges){
    if(changes.myFilterData.currentValue.hasOwnProperty('resources')){
      this.tableWidget.clear().draw();
      this.tableWidget.rows.add(changes.myFilterData.currentValue.resources); // Add new data
      this.tableWidget.columns.adjust().draw(); // Redraw the DataTable
    }
  }
  private truncate(string:any, len:any){
    if (string.length > len)
      return string.substring(0,len)+'...';
    else
      return string;
  };
  parseURL(url:any) {
        var parsed_url:any = {}
        if (url == null || url.length == 0) return parsed_url;
    var protocol_i = url.indexOf('://');
    parsed_url.protocol = url.substr(0, protocol_i);
        var remaining_url = url.substr(protocol_i + 3, url.length);
        var domain_i = remaining_url.indexOf('/');
        domain_i = domain_i == -1 ? remaining_url.length - 1 : domain_i;
        parsed_url.domain = remaining_url.substr(0, domain_i);
        parsed_url.path = domain_i == -1 || domain_i + 1 == remaining_url.length ?
            null : remaining_url.substr(domain_i + 1, remaining_url.length);
        var domain_parts = parsed_url.domain.split('.');
        switch (domain_parts.length) {
            case 2:
                parsed_url.subdomain = null;
                parsed_url.host = domain_parts[0];
                parsed_url.tld = domain_parts[1];
                break;
            case 3:
                parsed_url.subdomain = domain_parts[0];
                parsed_url.host = domain_parts[1];
                parsed_url.tld = domain_parts[2];
                break;
            case 4:
                parsed_url.subdomain = domain_parts[0];
                parsed_url.host = domain_parts[1];
                parsed_url.tld = domain_parts[2] + '.' + domain_parts[3];
                break;
        }
        parsed_url.parent_domain = parsed_url.host + '.' + parsed_url.tld;
        return parsed_url;
  }
  imgExt:any = ["png", "gif", "jpeg", "jpg"]
    vidExt:any = ["mov", "flv", "mpg", "mpeg", "mp4", "ogv", "webm"]
  private initDatatable(): void {
    var myData =  this.myFilterData.hasOwnProperty('responseFileData') ? this.myFilterData.hasOwnProperty('responseFileData') : {};
    var that = this;
    let exampleId: any = $('#resourcesListing');
    this.tableWidget = exampleId.DataTable({
      aLengthMenu : [],
            aaData: myData,
            bPaginate: false,
            bAutoWidth: false,
            order: [],
            language: {
                search: "Search Resources : ",
                lengthMenu: "Display _MENU_ Resources",
                infoFiltered: "(filtered from _MAX_ Resources)",
                info: "Showing _START_ to _END_ of _TOTAL_ Resources",
                infoEmpty: "",
                zeroRecords: "<div style='padding: 10px;'>No resources match your search criteria.</div>",
                emptyTable: "<div style='padding: 10px;'>No resources available.</div>",
                paginate: {
                    first: " <i class='fa fa-fast-backward'></i> ",
                    previous: " <i class='fa fa-backward'></i> ",
                    next: " <i class='fa fa-forward'></i> ",
                    last: " <i class='fa fa-fast-forward'></i> "
                }
            }, 
      aoColumns: [
                {
                    mDataProp: 'filePath',
                    mRender: function (data, type, full) {
            var urlinfo = (that.parseURL(data));;
                        return that.truncate(urlinfo.domain, 50);
                    },
                    sWidth: "200px"
                },
                {
                    mDataProp: 'filePath',
                    mRender: function (data, type, full) {
            var urlinfo = (that.parseURL(data));
                        return "<a target='_blank' href='"+data+"'/>" + that.truncate(urlinfo.path ? urlinfo.path.split("?")[0] : "", 60) + "</a>";
          },
          sWidth: "400px"
                },
                // {
                //  mData: "filePath",
                //  mRender: function (data, type, full) {
                //      return '<a href="#" class="info"><i class="fa fa-info" aria-hidden="true"></i></a>';
                //  },
                //  sWidth: "60px",
                //  sClass: "center"
                // },
                {
                    mData: "type",
                    sWidth: "100px",
                    mRender: function (data, type, full) {
                        var resType = data,
                            resPath = full['filePath'];
                        if(resType == "css"){
                            // background-url will have type css, change to bg-img
                            var ext = resPath.split('.').pop();
                            if(that.imgExt.indexOf(ext) >= 0){
                                // showResType = "bg-img";
                                resType = "bg-image";
                            }
                        }else if(resType == "other" || resType == ""){
                            // video comes as type 'other' in firefox and empty in chrome
                            var ext = resPath.split('.').pop();
                            if(that.vidExt.indexOf(ext) >= 0){
                                // showResType = "video";
                                resType = "video";
                            }
                        }else if(resType == "img"){
                            resType = "image";
                        }
                        return resType;
                    }
                },
                {
                    mDataProp: 'filePath',
                    mRender: function (data, type, full) {
                        return (full.type == "image" || full.type == "img") ? '<div class="imageBackgroundParent"><div class="imageBackground" style="background-image: url('+ data +');"></div></div>' : "";
                    },
                    sWidth: "120px",
                    sClass: "center"
                },
                {
                    mData: "size",
                    sWidth: "80px"
                }
            ],
    });
  }
}

sampleData

"resources":[
  {
    "filePath": "http://www.cricbuzz.com/live-cricket-scores/18460/sl-vs-ind-2nd-test-india-tour-of-sri-lanka-2017",
    "type": "html",
    "size": 119362
  },
  {
    "filePath": "http://gc.kis.v2.scr.kaspersky-labs.com/EAA2612E-9291-A04E-A659-D0B272EEC835/main.js",
    "type": "script",
    "size": 104685
  },
  {
    "filePath": "http://i.cricketcb.com/statics/site/images/cbz-logo.png",
    "type": "image",
    "size": 0
  }
]

Solution

  • Because objects are mutable the ngOnChange() doesn't been called. That's happens because ngOnChange() it triggered only when the argument instance changes (And not one of it's properties).

    You can read more about it here

    What you can to is take advantage of immutable object. That's mean that the object can't be changed. There's a great library named immutable.js by facebook.

    You can also use Object.assign(). When you use it you're creating new instance of your object (Instead of changing reference).

    So to make long story short,

      constructor(private harFileServiceService:HarFileServiceService) { }
    
      ngOnInit() {
      }
      filterData:Object = {};
      changeListener(event:any){
        var file = event.target.files[0];
        var myReader = new FileReader();
        var harFile = this.harFileServiceService;
        myReader.onload = (e:any)=>{
          var jsonData = JSON.parse(e.target.result);
          harFile.render(jsonData);
          this.filterData = Object.assign({},harFile.responseFileData);
        }
        // console.log(file);
        myReader.readAsText(file);
      }
    }
    

    In the above example I've used the Object.assign(). Also I've change the syntax a little bit and use'd ecmascript arrow function.