随着时间的推移,Angular 4变得越来越慢

时间:2017-08-30 14:06:12

标签: angular google-chrome raspberry-pi

我有一个角度4.3.5的应用程序,在使用一段时间后变得越来越慢(约20分钟)。

我的方案如下:

  • 在RaspberryPi B 3上运行的Rest API和静态角度html / css / js
  • ~30 RaspberryPI B 3通过Chromium(版本58和60)访问静态角度应用

会发生什么:

  • Angular的HTTP请求在时间过后变慢。示例:从~100 ms到~2秒

其他信息:

  • 如果我在Chromium上按F5,Angular应用程序将恢复正常
  • Angular使用此模板https://themeforest.net/item/primer-angular-2-material-design-admin-template/19228165
  • Angular使用我编写的Google Chrome / Chromium应用程序,通过串口与Chrome Arduino进行通信(Chrome API:chrome.runtime.sendMessage,chrome.runtime.connect和chrome.serial)
  • 客户端RaspberryPi在应用程序变慢时有可用资源(CPU和内存)
  • Angular应用程序几乎不存储浏览器

出现问题的组件如下:

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();
    }

}

每次访问组件时呈现缓慢的方法是:

getCategories() getSubcategories(UUID) 的getProducts(UUID) confirmDebit()

出于测试目的,我们为每种方法创建了一个新版本,这次使用了promises:

getCategoriesP() getSubcategoriesP(UUID) getProductsP(UUID)

无论被调用方法的版本如何,都会出现同样的问题。

3 个答案:

答案 0 :(得分:12)

如果您将应用程序作为SPA(单页应用程序)运行,那么这可能是随着时间的推移性能下降的原因之一。

在SPA中,每次用户访问新网页时,DOM都会变得更重。因此,您必须研究如何保持DOM轻量级。

以下是我发现显着提高应用程序性能的主要原因。

检查以下几点:

  • 如果您已使用制表符控件,则仅加载活动制表符内容,并且DOM上不应存在其他制表符内容。
  • 如果要加载任何弹出窗口,请确保仅在打开时加载正文。
  • confirmation popupmessage alert等常见组件应定义一次并可全局访问。
  • * ng使用trackby(https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5
  • 进行申请

服务完成后,在任何对象上调用destroy:(以下是调用服务的示例)

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);
});

请参阅以下链接了解更多详情:

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

答案 1 :(得分:2)

我认为问题出在get-Methods

中的订阅机制中
  

(getProducts,getCategories等)

如何创建从api-Service调用返回的observable?在您调用api-Service后,您将订阅该呼叫的返回值。这是来自http请求的原始响应吗?或者这是你自己创造的一个oeservable?

一般情况下,你不需要在角度的http-calls上调用unsubscribe,如下所述:

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

但是如果您没有通过原始的http-observable,而是创建自己的abservable,那么您可能需要自己清理它。

也许您可以发布一些api-Service的代码?你如何创造这些承诺?

另一件事: 你打电话给你getProducts - 每次都用不同的uuid方法?您将使用

调用该方法的每个uuid在localStorage中写入一个新条目

答案 2 :(得分:2)

不确定这是否会解决您的性能问题,但这可能是朝着正确方向迈出的一步。

每次路由参数更改时,您都在创建新的订阅,因此最终可能会有大量订阅:

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);
    }
  );
});

我认为你最好使用switchMap,这样只会有1个订阅。类似的东西:

this.route.params .switchMap(params => { this.cardId = params.id; return 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); } ); });