web-dev-qa-db-ja.com

Angular 4は時間とともに遅くなります

angular 4.3.5アプリケーションがしばらく使用された後(〜20分)遅くなっています。

私のシナリオは次のようなものです:

  • REST APIとstatic angular RaspberryPi B 3で実行されているhtml/css/js
  • 〜30 RaspberryPI B 3静的にアクセスangular Chromiumを介したアプリケーション(バージョン58および60)

何が起こるか:

  • AngularのHTTPリクエストは、時間の経過とともに遅くなりました。例:〜100ミリ秒から〜2秒

追加情報:

  • ChromiumでF5を押すと、Angularアプリケーションは通常に戻ります
  • Angularはこのテンプレートを使用します https://themeforest.net/item/primer-angular-2-material-design-admin-template/19228165
  • Angularは、シリアルポート(Chrome API:chrome.runtime.sendMessage、chrome.runtime.connect、chrome.serial)を介したArduinoとの通信に、私が書いたGoogle Chrome/Chromiumアプリを使用します
  • クライアントのRaspberryPiには、アプリケーションが遅くなったときに利用可能なリソース(CPUとメモリ)があります
  • 角度アプリケーションはブラウザにほとんど何も保存しません

問題を提示するコンポーネントは次のとおりです。

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

テストの目的で、これらのメソッドごとに新しいバージョンを作成しました。今回はプロミスを使用します:

getCategoriesP()getSubcategoriesP(uuid)getProductsP(uuid)

呼び出されたメソッドのバージョンに関係なく、同じ問題が発生します。

18
Diego Andrade

アプリケーションをSPA(シングルページアプリケーション)として実行している場合、これは時間の経過に伴うパフォーマンス低下の原因の1つである可能性があります。

SPAでは、ユーザーが新しいページにアクセスするたびにDOMが重くなります。したがって、DOMを軽量に保つ方法に取り組む必要があります。

以下は、アプリケーションのパフォーマンスが大幅に向上した主な理由です。

以下の点を確認してください:

  • タブコントロールを使用した場合は、アクティブなタブコンテンツのみをロードし、他のタブコンテンツはDOMに存在しないようにします。
  • ポップアップが読み込まれる場合は、開くときにのみボディが読み込まれるようにしてください。
  • confirmation popupmessage alertなどの一般的なコンポーネントは、一度定義して、グローバルにアクセスできるようにする必要があります。
  • * ngtrackbyで適用( 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

これがパフォーマンスの問題を解決するかどうかはわかりませんが、正しい方向への一歩になる可能性があります。

ルートパラメーターが変更されるたびに新しいサブスクリプションを作成しているため、多くのサブスクリプションが発生する可能性があります。

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

2
David Bulté

問題は、get-Methodsのサブスクリプションメカニズム内のどこかにあると思います

(getProducts、getCategoriesなど)

Api-Serviceの呼び出しから返されるオブザーバブルをどのように作成しますか? api-Serviceを呼び出した後、その呼び出しの戻り値をサブスクライブします。それはhttp-requestからの元の応答ですか?それとも、あなたが自分で作成したことは可測ですか?

一般的に、ここで説明するように、http-callsのアンサブスクライブを角度で呼び出す必要はありません。

メモリリークを防ぐためにAngular 2つのhttp呼び出しから退会する必要がありますか?

ただし、元のhttp-observableを通過せず、代わりに独自のabservableを作成する場合は、自分でクリーンアップする必要があります。

たぶん、あなたのAPIサービスのいくつかのコードを投稿できますか?これらの約束をどのように作成しますか?

別のこと:毎回異なるuuidでgetProducts-Methodを呼び出していますか?そのメソッドを呼び出すすべてのuuidを使用して、localStorageに新しいエントリを書き込みます。

2
Tobias Gassmann