2

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
  }
]
Gmv
  • 2,008
  • 6
  • 29
  • 46
  • are you updating the only the property inside the `filterData` ? can you shouw the full code – Aravind Aug 26 '17 at 18:59
  • I added the code in the question @Aravind. – Gmv Aug 26 '17 at 19:05
  • When you say 'ngOnChanges', do you just mean the change detector is not detecting the changes? Bc I don't see that you are using the ngOnChanges method anywhere. – diopside Aug 26 '17 at 19:10
  • 1
    you can check now @diopside. – Gmv Aug 26 '17 at 19:12
  • See here: https://juristr.com/blog/2016/04/angular2-change-detection/ -- "ngOnChanges won’t be triggered when we mutate a (non-immutable) JavaScript object. Instead it triggers only when we reference-change the data-bound input property." – diopside Aug 26 '17 at 19:16
  • But the next line is more helpful ... "Also, we’ve seen that we can use ngDoCheck for such scenarios, that allows us to do a very fine-grained check of which property on our object changed." – diopside Aug 26 '17 at 19:18
  • May be will help you https://stackoverflow.com/a/50947188/1394045 – elciospy Oct 01 '22 at 14:59

3 Answers3

4

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.

Gili Yaniv
  • 3,073
  • 2
  • 19
  • 34
  • can you please help me to solve this https://stackoverflow.com/questions/50132063/ngonchanges-is-not-working-in-angular4 – Nikson May 02 '18 at 11:00
2

Alternatively use lodash cloneDeep to change the reference

this.filterData = _.cloneDeep(harFile.responseFileData);
Aravind
  • 40,391
  • 16
  • 91
  • 110
  • i ll check @Aravind. – Gmv Aug 27 '17 at 10:23
  • how does this work @aravind can you explain your answer bit more please. – Pardeep Jain Aug 27 '17 at 10:47
  • @PardeepJain when you are using `object.assign` it will change the reference of the object there by onChanges will be listened in the child component. event `cloneDeep` recursively changes the reference. – Aravind Aug 27 '17 at 10:49
  • 1
    @PardeepJain, yes we can use either of these but `object.assign` is not supported by IE documented [**here**](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) – Aravind Aug 27 '17 at 10:52
1

ngOnChanges do not detect Object changes when we have the same reference, the solution is to create a new object reference each time :

const object = Object.assign({}, oldObject)
Malik Zahid
  • 593
  • 5
  • 13