Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 is getting slower through time

Tags:

I have an angular 4.3.5 application that is getting slower after it is used for a while (~20 minutes).

My scenario is like:

  • Rest API and static angular html/css/js running on RaspberryPi B 3
  • ~30 RaspberryPI B 3 accessing static angular application through Chromium (versions 58 and 60)

What occurs:

  • The Angular's HTTP requests got slower at time pass. Example: from ~100 ms to ~2 seconds

Additional information:

  • If I press F5 on Chromium, the Angular application come back to normal
  • Angular uses this template https://themeforest.net/item/primer-angular-2-material-design-admin-template/19228165
  • Angular uses a Google Chrome/Chromium app, that I wrote, for communication with an Arduino through serial port (Chrome API: chrome.runtime.sendMessage, chrome.runtime.connect and chrome.serial)
  • The client, RaspberryPi, has available resources (CPU and memory) when the application get slow
  • Angular application stores almost nothing on the browser

The component that presents the problem is the following:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';

import { SweetAlertService } from 'ng2-cli-sweetalert2';

import { ApiService } from '.././api.service';
import { NFCService } from '.././nfc.service';

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

  private ngUnsubscribe: Subject<void> = new Subject<void>();

  cardId: string;
  userId: string;
  userName: string;
  is_secure_bar: boolean = false;

  requestInProgress = false;

  userBalance: number = 0;

  step: number = 1;
  // showCheckout: boolean = false;

  categories = new Array();
  subcategories = new Array();
  products = new Array();

  cartItems = new Array();

  countCartItems: number = 0;
  totalCartValue: number = 0;

  table_scroller;
  table_scroller_height;
  show_scroller_btns = false;

  constructor(
    public router: Router,
    public route: ActivatedRoute,
    private _nfcService: NFCService,
    private _apiService: ApiService,
    private _swal: SweetAlertService
  ) { }

  ngOnInit() {
    var account = localStorage.getItem('account');
    if (account) {
      // set variable to catch user data
      // JSON.parse(
    } else {
      this.router.navigate(['login']);
    }

    this.route.params
    .takeUntil(this.ngUnsubscribe)
    .subscribe(params => {
      this.cardId = params.id;
      this._apiService.getCardUser(params.id)
      .takeUntil(this.ngUnsubscribe)
      .subscribe(
        response => {
          // SUCCESS
          this.userId = response.data[0].uuid;
          this.userBalance = response.data[0].balance;
          this.userName = response.data[0].name;
        },
        error => {
          // ERROR
          console.log('Failed ;(', error);
        }
      );
    });

    this.getEvents()
    .takeUntil(this.ngUnsubscribe)
    .subscribe(
      response => {
        if (response.data[0].options.sales_auth_after_buy_is_required) {
          this.is_secure_bar = true;
        }
      },
      error => {
        console.log('Erro ao verificar Evento.')
      }
    );

    var categories = localStorage.getItem('cache_categories');
    if (categories) {
      this.categories = JSON.parse(categories);
    } else {
      // this.getCategories();
      this.getCategoriesP()
    }

  }

  //@felipe_todo
  getEvents()
  {
    return this._apiService.getEvents();

    //COMO FAZER LOGOUT ABAIXO
    //localStorage.clear();
  }

  getCategories() {
    this._apiService.getProductsCategories()
      .takeUntil(this.ngUnsubscribe)
      .subscribe(response => {
        // SUCCESS
        this.categories = response.data;
        localStorage.setItem('cache_categories', JSON.stringify(this.categories));
      }, error => {
        // ERROR
        console.log('Failed ;(', error);
      });
  }

  getCategoriesP() {
    let categories;
    this._apiService.getCategories()
      .then(response => categories = response)
      .then(() => {
        this.categories = categories;
        console.log(categories);
      });
  }

  categorySelected(item) {
    this.step = 2;

    var subcategories = localStorage.getItem('cache_subcategories_' + item.uuid);
    if (subcategories) {
      this.subcategories = JSON.parse(subcategories);
    } else {
      // this.getSubcategories(item.uuid);
      this.getSubcategoriesP(item.uuid);
    }
  }

  getSubcategories(uuid) {
    this._apiService.getProductsSubcategories(uuid)
      .takeUntil(this.ngUnsubscribe)
      .subscribe(response => {
        // SUCCESS
        this.subcategories = response.data;
        localStorage.setItem('cache_subcategories_' + uuid, JSON.stringify(this.subcategories));
      }, error => {
        // ERROR
        console.log('Failed ;(', error);
      });
  }

  getSubcategoriesP(uuid) {
    let subcategories;
    this._apiService.getSubcategories(uuid)
      .then(response => subcategories = response)
      .then(() => {
        this.subcategories = subcategories;
        console.log(subcategories);
      });
  }

  subCategorySelected(item) {
    this.step = 3;

    var products = localStorage.getItem('cache_products_' + item.uuid);
    if (products) {
      this.products = JSON.parse(products);
    } else {
      // this.getProducts(item.uuid);
      this.getProductsP(item.uuid);
    }
  }

  getProducts(uuid) {
    this._apiService.getProducts(uuid)
      .takeUntil(this.ngUnsubscribe)
      .subscribe(response => {
        // SUCCESS
        this.products = response.data;
        localStorage.setItem('cache_products_' + uuid, JSON.stringify(this.products));
      }, error => {
        // ERROR
        console.log('Failed ;(', error);
      });
  }

  getProductsP(uuid) {
    let products;
    this._apiService.getProductList(uuid)
      .then(response => products = response)
      .then(() => {
        this.products = products;
        console.log(products);
      });
  }

  addToCard(product) {
    var existentItems = this.cartItems.filter(function(item) {
      return item.uuid === product.uuid
    });

    if (existentItems.length) {
      existentItems[0].quantity += 1
    } else {
      product.quantity = 1;
      this.cartItems.unshift(product);
    }
    let that = this;
    this.calculateTotal();
    setTimeout(function(){
      that.setScroller();
    }, 300);
  }

  removeProduct(index) {
    let product = this.cartItems[index]
    var existentItems = this.cartItems.filter(function(item) {
      return item.uuid === product.uuid
    });

    if (existentItems.length) {
      existentItems[0].quantity -= 1
      if (existentItems[0].quantity == 0) {
        this.cartItems.splice(index, 1);
      }
    } else {
      product.quantity = 1;
      this.cartItems.splice(index, 1);
    }

    this.calculateTotal();
    let that = this;
    setTimeout(function(){
      if (that.table_scroller.offsetHeight < 270) {
        that.show_scroller_btns = false;
      }
    }, 300);
  }

  calculateTotal() {
    this.countCartItems = 0;
    this.totalCartValue = 0;

    var that = this;
    this.cartItems.forEach(function(item) {
      that.countCartItems += item.quantity;
      that.totalCartValue += item.value * item.quantity;
    });
  }

  backStep() {
    if (this.step == 2) {
      this.subcategories = new Array();
    } else if (this.step == 3) {
      this.products = new Array();
    }

    this.step--;
  }

  setScroller() {
    if (this.cartItems.length) {
      if (!this.table_scroller) {
        this.table_scroller = document.querySelector('#table-scroller');
      }else {
        console.log(this.table_scroller.offsetHeight)
        if (this.table_scroller.offsetHeight >= 270) {
          this.show_scroller_btns = true;
        } else {
          this.show_scroller_btns = false;
        }
      }
    }
  }

  scrollDown() {
    (<HTMLElement>this.table_scroller).scrollTop = (<HTMLElement>this.table_scroller).scrollTop+50;
  }

  scrollUp() {
    (<HTMLElement>this.table_scroller).scrollTop = (<HTMLElement>this.table_scroller).scrollTop-50;
  }

  confirmDebit() {

    if (this.requestInProgress) return;

    if (this.userBalance < this.totalCartValue) {
      this._swal.error({ title: 'Salto Insuficiente', text: 'Este cliente não possui saldo suficiente para essa operação.' });
      return;
    }

    this.requestInProgress = true;

    var order = {
      card_uuid: this.cardId,
      event_uuid: 'c7b5bd69-c2b5-4226-b043-ccbf91be0ba8',
      products: this.cartItems
    };

    let is_secure_bar = this.is_secure_bar;

    this._apiService.postOrder(order)
       .takeUntil(this.ngUnsubscribe)
         .subscribe(response => {
        console.log('Success');
        // this.router.navigate(['customer', this.userId]);

        let that = this;
        this._swal.success({
          title: 'Debito Efetuado',
          text: 'O débito foi efetuado com sucesso',
          showCancelButton: false,
          confirmButtonText: 'OK',
          allowOutsideClick: false,
        }).then(function(success) {
          console.log("Clicked confirm");
          if (is_secure_bar) {
            that.logout();
          } else {
            that.router.navigate(['card']);
          }
        });

        this.requestInProgress = false;

      }, error => {
        // ERROR
        console.log('Request Failed ;(', error);

        if (error.status !== 0) {
          // TODO: Should display error message if available!
          this._swal.error({ title: 'Erro', text: 'Ocorreu um erro inesperado ao conectar-se ao servidor de acesso.' });
        } else {
          this._swal.error({ title: 'Erro', text: 'Não foi possível conectar-se ao servidor de acesso. Por favor verifique sua conexão.' });
        }

        this.requestInProgress = false;
      }
      );
  }

  logout() {
    let that = this;
    localStorage.clear();
    that.router.navigate(['login']);
  }

  clearCheckout() {
    this.cartItems = new Array();
    this.calculateTotal();

    this.router.navigate(['card']);
  }

    ngOnDestroy() {
      console.log('uhul')
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }

}

The methods that present slowness each time we access the component are:

getCategories() getSubcategories(uuid) getProducts(uuid) confirmDebit()

For testing purposes we have created a new version for each of these methods, this time working with promises:

getCategoriesP() getSubcategoriesP(uuid) getProductsP(uuid)

Regardless of the version of the called method, the same problem occurs.

like image 543
Diego Andrade Avatar asked Aug 30 '17 14:08

Diego Andrade


People also ask

Why is my Angular app so slow?

If you've done your research and figured out that your app doesn't render that much and doesn't render that often, then your code may just simply be quite slow. This is probably due to some heavy scripting and not DOM-related. Use WebWorkers. The Angular CLI also provides a command to generate a WebWorker in a snap.

Is Angular material slow?

Many Angular developers are frustrated with Angular because they say it is slow. Some think that it's hard to build a fast Angular application. Especially given that the bundle sizes of its biggest rivals - React & Vue. js - are usually about half as small and take about half the time to parse and run the JavaScript.


2 Answers

If you are running your application as an SPA (Single Page Application), then this may be one of the causes for the performance degradation as time elapses.

In SPA, the DOM gets heavier every time the user visits a new page. Hence you have to work on how to keep the DOM lightweight.

Below are the main reasons which I found significantly improved my application's performance.

Check below points:

  • If you have used tab control then load only active tab content and other tab content should not exist on the DOM.
  • If any popup is going to load make sure it loads the body only when it opens.
  • Common components like confirmation popup and message alert should be defined once and globally accessible.
  • *ngFor apply with trackby (https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5)

After the service finishes, call destroy on any objects: (below is the example of calling a service)

import { Subject } from 'rxjs/Subject'
import 'rxjs/add/operator/takeUntil';

ngOnDestroy() {        
    this.ngUnsubscribe.next(true);
    this.ngUnsubscribe.complete();
}

this.frameworkService.ExecuteDataSource().takeUntil(this.ngUnsubscribe).subscribe((data: any) => {
    console.log(data);
});

Refer below links for more details:

https://medium.com/paramsingh-66174/catalysing-your-angular-4-app-performance-9211979075f6

like image 104
Sandip - Frontend Developer Avatar answered Dec 07 '22 23:12

Sandip - Frontend Developer


I think the problem lies somewhere within that subscription mechanism in your get-Methods

(getProducts, getCategories etc.)

How do you create that observable that is returned from the calls to your api-Service? After you call your api-Service you subscribe on the return-value of that call. Is that the original response from the http-request? Or is it an oeservable that you created yourself?

In general, you do not need to call unsubscribe on http-calls in angular, as is described here:

Do you need to unsubscribe from Angular 2 http calls to prevent memory leak?

But in case you not passing through that original http-observable, but instead creating your own abservable, then you might need to clean it up yourself.

Maybe you can post some code of your api-Service? How do you create those promises?

Another Thing: Are you calling you getProducts-Method with a different uuid every time? You would be writing a new entry into localStorage with every single uuid that you call that method with

like image 45
Tobias Gassmann Avatar answered Dec 08 '22 00:12

Tobias Gassmann