Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 change detection through event emitter consumes massive CPU time

I noticed that my Angular 2 app gets painfully slow after a while of usage.

I profiled the CPU time and found out that there are massive change detection executions going on.

CPU profile right after page load ...

CPU profile after page load

... compared to the CPU profile after using the page for a while.

CPU profile after using the page for a while

I used a lot of EventEmitter in different services to communicate between a lot of components.

After testing a while it seems that the emitter for the window scroll event causes a big part of the heavy load.

The CPU profile after using the page for a while without emitting scroll events:

CPU profile without emiting scroll event

Here the implementation of the service:

@Injectable()
export class WindowService {

  @Output() scrolled$: EventEmitter<WindowScrolled> = new EventEmitter();

  private scrollDebounceTime = 25;

  constructor() {
    this.addEvent(window, 'scroll', this.debounce((event) => {
      this.scrolled$.emit(new WindowScrolled(window.scrollX, window.scrollY));
    }, this.scrollDebounceTime));
  }

  // ... other functions
}

Questions

  1. How can I debug the change detection calls to see where are they applied to?
  2. What else can cause so many change detection calls?
  3. If I'm using the EventEmitter wrong, how do I use it correctly?

Edit 1

In addition I post the grid tree component, because the changes might be caused by the recursive tree structure my components build.

@Component({
  selector: 'hierarchy-grid-tree',
  moduleId: __moduleName, // use `__moduleName` from System.js for relative styleUrls and templateUrls
  styleUrls : [`hierarchy-grid.css`],
  template: `<div class="flex-container">
    <div class="flex-item" *ngFor="#node of nodes; #i = index" [ngClass]="{'intermediate': node.has()}" [ngStyle]="{'flex-grow': flexGrow(node), 'flex-basis': visRepConf.nodeWidth+'px', 'order': (i+1)}">
      <hierarchy-node [node]="node" [visRepConf]="visRepConf" #hnInstance></hierarchy-node>
      <hierarchy-grid-tree [nodes]="node.children()" [visRepConf]="visRepConf" [show-depth]="showDepth" [curr-depth]="currDepth + 1" *ngIf="(showDepth === -1 || currDepth < depth) && node.has() && !hnInstance.isCollapsed"></hierarchy-grid-tree>
    </div>
  </div>`,
  providers:  [],
  directives: [HierarchyGridTreeComponent, HierarchyNodeComponent]
})
export class HierarchyGridTreeComponent {

  @Input() nodes: Array<Node> = [];

  @Input() visRepConf:VisRepresentationConfig;

  @Input('show-depth') showDepth = -1;

  @Input('curr-depth') currDepth = 1;

  constructor() {

  }

  flexGrow(node) {
    if(node.has()) {
      return node.numChildrenRecursive();
    }
    return 'auto';
  }
}

// see html demo at http://codepen.io/anon/pen/pgqjPP
@Component({
  selector: 'hierarchy-grid',
  moduleId: __moduleName, // use `__moduleName` from System.js for relative styleUrls and templateUrls
  styleUrls : [`hierarchy-grid.css`],
  template: `<div class="color-{{color}}" [ngClass]="{'selects-infra':selectsInfra}" (click)="selectInfra($event)">
    <p *ngIf="showInfraTitle" class="title">{{title}}</p>
    <hierarchy-node *ngIf="external" [node]="external" [visRepConf]="visRepConf"></hierarchy-node>
    <hierarchy-grid-tree [nodes]="nodes" [visRepConf]="visRepConf"></hierarchy-grid-tree>
  </div>`,
  providers:  [],
  directives: [HierarchyGridTreeComponent, HierarchyNodeComponent]
})
export class HierarchyGridComponent implements OnInit, OnChanges {

  @Input('vis-config') visConfig:string = '';

  @Input('infra') infra:Infrastructure;

  @Input('show-external') showExternal:boolean = false;

  @Input('show-infra-title') showInfraTitle:boolean = false;

  @Input('selects-infra') selectsInfra:boolean = false;

  private visRepConf:VisRepresentationConfig;

  private external:ExternalNode;

  private nodes:Array<Node>;

  private color:string;

  private title:string;

  constructor(private nodeSelection:NodeSelectionService) {
  }

  ngOnChanges(changes) {
    if(changes.infra !== undefined && this.infra !== undefined) {
      this.visRepConf = this.infra.visConfig.get(this.visConfig);

      this.nodes = [this.infra.root];

      if(this.showExternal) {
        this.external = this.infra.external;
      }

      this.color = this.infra.color;
      this.title = this.infra.name;
    }
  }

  selectInfra($event) {
    if(this.selectsInfra) {
      this.nodeSelection.infra = this.infra;
    }
  }
}

The resulting hierarchy grid:

Hierarchy grid

like image 643
Holger Stitz Avatar asked Mar 04 '16 14:03

Holger Stitz


1 Answers

  1. Stop using Event Emitter inside services
  2. You may use bindings for inter component communication instead of event emitter
like image 163
Tek choudhary Avatar answered Oct 04 '22 01:10

Tek choudhary