это утечка памяти массива в angular?

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

}