J'ai une application angular 4.3.5 qui ralentit après avoir été utilisée pendant un certain temps (~ 20 minutes).
Mon scénario est comme:
Ce qui se passe:
Informations supplémentaires:
Le composant qui présente le problème est le suivant:
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();
}
}
Les méthodes qui présentent une lenteur chaque fois que nous accédons au composant sont:
getCategories () getSubcategories (uuid) getProducts (uuid) confirmDebit ()
À des fins de test, nous avons créé une nouvelle version pour chacune de ces méthodes, cette fois avec des promesses:
getCategoriesP () getSubcategoriesP (uuid) getProductsP (uuid)
Quelle que soit la version de la méthode appelée, le même problème se produit.
Si vous exécutez votre application en tant que SPA (application à page unique), cela peut être l'une des causes de la dégradation des performances au fil du temps.
Dans SPA, le DOM devient plus lourd à chaque fois que l'utilisateur visite une nouvelle page . Par conséquent, vous devez travailler sur la façon de garder le DOM léger.
Voici les principales raisons pour lesquelles j'ai trouvé une amélioration significative des performances de mon application.
Vérifiez les points ci-dessous:
confirmation popup
et message alert
doit être défini une seule fois et accessible à l'échelle mondiale.Une fois le service terminé, appelez destroy sur tous les objets: (ci-dessous est l'exemple d'appel d'un 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);
});
Reportez-vous aux liens ci-dessous pour plus de détails:
https://medium.com/paramsingh-66174/catalysing-your-angular-4-app-performance-9211979075f6
Vous ne savez pas si cela résoudra votre problème de performances, mais cela pourrait être un pas dans la bonne direction.
Vous créez un nouvel abonnement chaque fois qu'un paramètre d'itinéraire change, vous pourriez donc vous retrouver avec beaucoup d'abonnements:
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);
}
);
});
Je pense que vous feriez mieux d'utiliser switchMap, de cette façon, il n'y aura qu'un seul abonnement. Quelque chose comme:
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); } ); });
Je pense que le problème se situe quelque part dans ce mécanisme d'abonnement dans vos méthodes get
(getProducts, getCategories etc.)
Comment créez-vous cet observable qui est renvoyé des appels à votre api-Service? Après avoir appelé votre service api, vous vous abonnez à la valeur de retour de cet appel. Est-ce la réponse originale de la requête http? Ou est-ce un devoir que vous avez créé vous-même?
En général, vous n'avez pas besoin d'appeler désabonnement sur les appels http en angulaire, comme cela est décrit ici:
Devez-vous vous désinscrire de Angular 2 appels http pour éviter les fuites de mémoire?
Mais au cas où vous ne passeriez pas par cet observable http original, mais créeriez votre propre observable, alors vous pourriez avoir besoin de le nettoyer vous-même.
Peut-être pouvez-vous publier un code de votre service api? Comment créez-vous ces promesses?
Une autre chose : Appelez-vous votre méthode getProducts avec un uuid différent à chaque fois? Vous écririez une nouvelle entrée dans localStorage avec chaque uuid unique avec lequel vous appelez cette méthode