Подтвердить что ты не робот

Angular 4 замедляется во времени

У меня есть приложение angular 4.3.5, которое становится медленнее после его использования некоторое время (~ 20 минут).

Мой сценарий похож:

  • Rest API и статический angular html/css/js, запущенный на RaspberryPi B 3
  • ~ 30 RaspberryPI B 3 доступ к статическому приложению angular через Chromium (версии 58 и 60)

Что происходит:

  • HTTP-запросы angular становились все медленнее при переходе времени. Пример: от ~ 100 мс до ~ 2 секунд

Дополнительная информация:

  • Если я нажму F5 на Chromium, приложение angular вернется к нормальному
  • Angular использует этот шаблон https://themeforest.net/item/primer-angular-2-material-design-admin-template/19228165
  • Angular использует приложение Google Chrome/Chromium, которое я написал для связи с последовательным портом Arduino (Chrome API: chrome.runtime.sendMessage, chrome.runtime.connect и chrome.serial).
  • Клиент, RaspberryPi, имеет доступные ресурсы (процессор и память), когда приложение становится медленным.
  • 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)

Независимо от версии вызываемого метода возникает та же проблема.

4b9b3361

Ответ 1

Если вы используете приложение как SPA (приложение с одной страницей), это может быть одной из причин ухудшения производительности по прошествии времени.

В SPA DOM становится тяжелее каждый раз, когда пользователь посещает новую страницу. Следовательно, вы должны работать над тем, как поддерживать легкий вес DOM.

Ниже приведены основные причины, по которым я нашел существенное улучшение производительности приложений.

Проверьте ниже пункты

  • Если вы использовали элемент управления вкладками, загрузите только активный контент вкладки, а другое содержимое вкладки не должно существовать в DOM.
  • Если настроено какое-либо всплывающее окно, убедитесь, что оно загружает тело только тогда, когда оно открывается.
  • Общие компоненты, такие как всплывающие окна и оповещения, должны быть определены один раз и доступны по всему миру.
  • * ngFor применять с трекби (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

Ответ 2

Я думаю, что проблема лежит где-то внутри этого механизма подписки в ваших методах get-Methods

(getProducts, getCategories и т.д.)

Как вы создаете наблюдаемое, которое возвращается из вызовов в api-Service? После того, как вы позвоните в api-Service, вы подпишитесь на возвращаемую стоимость этого звонка. Это оригинальный ответ от http-запроса? Или это возможно, что вы создали себя?

В общем случае вам не нужно вызывать отмену подписки на http-вызовы в angular, как описано здесь:

Вам нужно отписаться от Angular 2 http-звонков, чтобы предотвратить утечку памяти?

Но если вы не проходите через этот оригинальный http-наблюдаемый, но вместо этого создаете свой собственный абсурдный, вам может понадобиться очистить его самостоятельно.

Может быть, вы можете опубликовать код вашего api-Service? Как вы создаете эти promises?

Другая вещь: Вы называете вас методом getProducts-Method с разными uuid каждый раз? Вы будете писать новую запись в localStorage с каждым единственным uuid, который вы вызываете этим методом с помощью

Ответ 3

Не уверен, что это решит вашу проблему с производительностью, но это может быть шагом в правильном направлении.

Вы создаете новую подписку каждый раз при изменении параметра маршрута, поэтому потенциально вы можете получить много подписки:

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