#angular #debugging #memory-leaks #google-chrome-devtools
#angular #отладка #утечки памяти #google-chrome-devtools
Вопрос:
как вы можете видеть на прикрепленном изображении, я выполняю отладку, чтобы обнаружить утечки памяти в моем приложении.
Под записью «array» очень высокая дельта! «система» тоже, и куча памяти увеличивается с 6 МБ до 11 МБ, переключаясь между домашней страницей (где ничего нет) и галереей 20 раз.
В галерее используются отправки и селекторы ngrx, я передаю данные в компонент галереи с помощью асинхронного канала. Я говорю о компоненте gallery, потому что, исключив его, прокомментировав его, утечки памяти больше нет.
Прежде всего, я хотел бы знать, действительно ли это утечка памяти, и если это так, я хотел бы знать, как ее устранить. Поскольку это массивы, разве они не должны быть освобождены при уничтожении компонента? а как насчет «системной» дельты? Спасибо! github
компонент conteiner
@Component({
selector: 'app-conteiner-gallery',
template:`
<app-gallery [allCars]="allCars$ | async"
[brands]="brands$ | async"
[types]="types$ | async"
[usersRatings]="usersRatings$ | async"
[recentSeenCars]="recentSeenCars$ | async"
[observedCars]="observedCars$ | async"
(setObservedCarAction)="onSetObservedCar($event)"
(setNotObservedCarAction)="onSetNotObservedCar($event)"
(setObservedCarsAction)="onSetObservedCars($event)"
(setBrandChecked)="onSetBrandChecked($event)"
(setBrandsChecked)="onSetBrandsChecked($event)"
(setTypeChecked)="onSetTypeChecked($event)"
(setTypesChecked)="onSetTypesChecked($event)">
</app-gallery>
`
})
export class ConteinerGalleryComponent implements OnInit, OnDestroy{
allCars$:Observable<Car[]>
brands$:Observable<BrandCB[]>
types$:Observable<TypeCB[]>
usersRatings$:Observable<UserRating[]>
recentSeenCars$:Observable<RecentSeenCar[]>
observedCars$:Observable<ObservedCar[]>
unsubscription$:Subject<boolean> = new Subject
isStoreLoaded:boolean = false
isStoreRecentSeenCarsLoaded:boolean = false
constructor( private store:Store<StoreState>, private authService:AuthenticationService){}
ngOnDestroy():void{
this.unsubscription$.next(true)
this.unsubscription$.complete()
}
ngOnInit(): void {
this.store.pipe(select(statusSelectors.isStoreLoaded))
.pipe( takeUntil(this.unsubscription$))
.subscribe( loaded => this.isStoreLoaded = loaded)
this.store.pipe(select(statusSelectors.isRecentSeenCarsLoaded))
.pipe(takeUntil(this.unsubscription$))
.subscribe( loaded => this.isStoreRecentSeenCarsLoaded = loaded)
!this.isStoreLoaded ? this.store.dispatch(carsActions.getAllCars()) : null
!this.isStoreLoaded ? this.store.dispatch(brandsActions.getBrands()) : null
!this.isStoreLoaded ? this.store.dispatch(typesActions.getTypes()) : null
!this.isStoreLoaded ? this.store.dispatch(userRatingsActions.getUsersRatings()) : null
if(!this.isStoreRecentSeenCarsLoaded amp;amp; this.authService.currentUserValue){
this.store.dispatch(recentSeenCarsActions.getRecentSeenCars())
this.store.dispatch(statusActions.setRecentSeenCarsLoaded({loaded:true}))
}
if(!this.isStoreLoaded)
this.authService.currentUserValue ? this.store.dispatch(observedCarsActions.getObservedCars()) : null
this.store.dispatch(statusActions.setStoreLoaded({loaded:true}))
this.allCars$ = this.store.pipe(select(carsSelectors.getAllCars))
this.brands$ = this.store.pipe(select(brandsSelectors.getBrands))
this.types$ = this.store.pipe(select(typeSelectors.getTypes))
this.usersRatings$ = this.store.pipe(select(userRatingsSelectors.getUsersRatings))
this.recentSeenCars$ = this.store.pipe(select(userRecentSeenCarsSelectors.getUsers))
this.observedCars$ = this.store.pipe(select(userObservedCarsSelectors.getUsers))
}
onSetObservedCar(carId:number):void{
let observed:ObservedCar = {id:null, carId:carId, userId:null}
this.store.dispatch(observedCarsActions.setObservedCar({carId}))
}
onSetNotObservedCar(carId:number):void{
this.store.dispatch(observedCarsActions.setNotObservedCar({carId}))
}
onSetObservedCars(cars:Car[]):void{
this.store.dispatch(carsActions.setCarsWithObserved({cars}))
}
onSetBrandChecked(brand:BrandCB):void{
this.store.dispatch(brandsActions.setBrandChecked({brand}))
}
onSetBrandsChecked(brands:BrandCB[]):void{
this.store.dispatch(brandsActions.setBrandsChecked({brands}))
}
onSetTypeChecked(typee:TypeCB):void{
this.store.dispatch(typesActions.setTypeChecked({typee}))
}
onSetTypesChecked(types:TypeCB[]):void{
this.store.dispatch(typesActions.setTypesChecked({types}))
}
public logout():void{
this.store.dispatch(statusActions.setRecentSeenCarsLoaded({loaded:false}))
}
}
компонент галереи
export class GalleryComponent implements OnInit, DoCheck, OnChanges{
@Input() allCars:Car[]
@Input() brands:BrandCB[]
@Input() types:TypeCB[]
@Input() usersRatings:UserRating[]
@Input() recentSeenCars:Car[]
@Input() observedCars:ObservedCar[]
@Output() setObservedCarAction = new EventEmitter<any>()
@Output() setNotObservedCarAction = new EventEmitter<any>()
@Output() setObservedCarsAction = new EventEmitter<any>()
@Output() setBrandCount = new EventEmitter<any>()
@Output() setTypeCount = new EventEmitter<any>()
@Output() setBrandChecked = new EventEmitter<any>()
@Output() setTypeChecked = new EventEmitter<any>()
@Output() setBrandsChecked = new EventEmitter<any>()
@Output() setTypesChecked = new EventEmitter<any>()
filteredCars:Car[] = []
/* brandsCB:BrandCB[] = []
typesCB:TypeCB[] = [] */
user:User
iterableDiffer:IterableDiffer<any>
checkboxBrandAll:boolean = true
checkboxTypeAll:boolean = true
leftConteinerClass:string
faBars = faBars
constructor( private authService:AuthenticationService, private iterableDiffers: IterableDiffers){
this.iterableDiffer = iterableDiffers.find([]).create(null);
}
// evocata quando ci sono cambiamenti nelle proprietà di input
ngOnChanges(changes: SimpleChanges): void {
// c'è bisogno di chiamare filterCars perchè oltre a filtrare le auto aggiorna l'array in visualizzazione
changes.allCars ? this.filterCars(this.brands, this.types) : null
}
ngDoCheck(): void {
// used when observed buttons are pressed
let changes = this.iterableDiffer.diff(this.observedCars);
if (changes) {
this.user ? this.setObservedCars() : null
}
}
ngOnInit():void{
this.user = this.authService.currentUserValue
this.user ? this.setObservedCars() : null
this.initFilters()
this.filterCars(this.brands, this.types)
this.showNormalLeftConteiner()
window.addEventListener('resize', () => this.showNormalLeftConteiner())
}
// setta le checkbox degli "all" dei filtri a false se altre checkbox risultano selezionate
initFilters(){
let filtersBrands:boolean = false
let filtersTypes:boolean = false
this.brands.some( brand => brand.checked) ? filtersBrands = true : null
this.types.some( type => type.checked) ? filtersTypes = true : null
filtersBrands ? this.checkboxBrandAll = false : null
filtersTypes ? this.checkboxTypeAll = false : null
}
// operazioni da effettuara al cambiamento di una checkbox dei filtri.
checkboxChange(event, brand, type):void{
let name = event.target.name;
let checked = event.target.checked;
let brands:BrandCB[] = JSON.parse(JSON.stringify(this.brands))
let types:TypeCB[] = JSON.parse(JSON.stringify(this.types))
//BRANDS
if(name =='allBrands'){
if(checked == true){
brands.map(brand => brand.checked = false);
this.checkboxBrandAll = true
this.filterCars(brands, types)
brands = this.countBrands(brands)
types = this.countTypes(types)
this.setBrandsChecked.emit(brands)
this.setTypesChecked.emit(types)
}else{
this.checkboxBrandAll = true
}
} else if(name == 'otherBrands'){
brands = brands.map( b => {
if(b.id == brand.id){
return ({...brand, checked:checked})
}else{
return b
}
})
if(checked == true){
this.checkboxBrandAll = false;
}else
brands.some( b => b.checked) ? null : this.checkboxBrandAll = true
this.filterCars(brands, types)
brands = this.countBrands(brands)
types = this.countTypes(types)
this.setBrandsChecked.emit(brands)
this.setTypesChecked.emit(types)
}
// TYPES
if(name =='allTypes'){
if(checked == true){
types.map(type => type.checked = false)
this.checkboxTypeAll = true
this.filterCars(brands, types)
brands = this.countBrands(brands)
types = this.countTypes(types)
this.setBrandsChecked.emit(brands)
this.setTypesChecked.emit(types)
}else{
this.checkboxTypeAll = true
}
} else if(name == 'otherTypes'){
types = types.map( t => {
if(t.id == type.id){
return ({...type, checked:checked})
}else{
return t
}
})
if(checked == true){
this.checkboxTypeAll = false;
}else
types.some( t => t.checked) ? null : this.checkboxTypeAll = true
this.filterCars(brands, types)
brands = this.countBrands(brands)
types = this.countTypes(types)
this.setBrandsChecked.emit(brands)
this.setTypesChecked.emit(types)
}
}
// aggiorna la proprietà "observed" di allCars
setObservedCars():void{
let allCars:Car[] = JSON.parse(JSON.stringify(this.allCars))
let observedCars:ObservedCar[] = [...this.observedCars]
allCars.map( car => {
// se la car è osservata, setta "observed" a true
observedCars.some( obs => obs.carId == car.id) ? car.observed = true : car.observed = false
})
this.setObservedCarsAction.emit(allCars)
}
// aggiorna l array per la gallery (this.filteredCars) con l'array modificato eventualmente nei filtri o nella proprietà observed (this.allCars)
filterCars(brands:BrandCB[], types:TypeCB[]):void{
let allCars = [...this.allCars];
const checkedBrands = brands.filter(brand => brand.checked);
const checkedTypes = types.filter(type => type.checked);
let filteredCars:Car[];
if(!this.checkboxBrandAll amp;amp; !this.checkboxTypeAll){
filteredCars = allCars.filter(car => checkedBrands.find(brand => brand.name === car.brand) amp;amp; checkedTypes.find(type => type.name === car.type));
} else if(!this.checkboxBrandAll amp;amp; this.checkboxTypeAll){
filteredCars = allCars.filter(car => checkedBrands.find(brand => brand.name === car.brand));
}else if(this.checkboxBrandAll amp;amp; !this.checkboxTypeAll){
filteredCars = allCars.filter(car => checkedTypes.find(type => type.name ===car.type));
}else{
filteredCars = [...allCars];
}
this.filteredCars = [...filteredCars]
}
// conta le occorrenze di ogni brand, ad esempio ferrari(2)
countBrands(brands:BrandCB[]):BrandCB[]{
let br:BrandCB[] = brands.map( brand => {
let count = 0;
this.filteredCars.forEach( car => {
brand.name == car.brand ? count : null
})
brand.count = count;
return brand
})
return br
}
// conta le occorrenze di ogni type, ad esempio jeep(2)
countTypes(types:TypeCB[]):TypeCB[]{
let ty:TypeCB[] = types.map( type => {
let count = 0;
this.filteredCars.forEach( car => {
type.name == car.type ? count : null
})
type.count = count;
return type
})
return ty
}
showMobileLeftConteiner():void{
this.leftConteinerClass = 'show-mobile-left-conteiner'
}
showNormalLeftConteiner():void{
if(window.innerWidth >= 1051){
this.leftConteinerClass = 'show-normal-left-conteiner'
}else{
this.hideLeftContenier()
}
}
hideLeftContenier():void{
this.leftConteinerClass = 'hide-left-conteiner'
}
onSetObservedCar(carId:number):void{
this.setObservedCarAction.emit(carId)
}
onSetNotObservedCar(carId:number):void{
this.setNotObservedCarAction.emit(carId)
}
}