我想知道我的代码是否会造成内存泄漏?
Context
我有一个应该显示“应用程序”对象的组件类。
它具有过滤和分页功能。
我创建了一个方法加载应用程序数据()其中我订阅到
向 Web 服务发出请求后返回的 Observable。
该方法在初始化时被调用,ngOnInit(),或者在用户与过滤输入字段或分页器交互之后(请参阅方法onUserInteractionsWithTree() )
我的问题
为了避免内存泄漏我已经使用
.pipe(takeUntil(this.ngUnsubscribe))
and
ngOnDestroy(): void {
this.ngUnsubscribe.next(); // Unsubscribe from observables.
this.ngUnsubscribe.complete(); // Unsubscribe from ngUnsubscribe.
}
但在我看来,每次调用 subscribe() 方法时,我都会创建新的 Subscription 对象。这会造成内存泄漏吗?
我应该尝试重用订阅对象吗?
在此先感谢您的帮助,
在我的组件的 Typescript 代码下方
import {Component, OnInit, ViewChild, ElementRef, OnDestroy} from '@angular/core';
import {FlatTreeControl} from '@angular/cdk/tree';
import {MatPaginator} from '@angular/material';
import {DynamicFlatNode} from './dynamic-flat-node';
import {ApplicationService} from '../shared/service/application-service';
import {DataRequestOptions} from '../../shared/data/data-request-options';
import {MetaDescriptor} from '../../shared/data/meta/meta-descriptor';
import {TableDataRequestParamsService} from '../../shared/data/table-data-request-params.service';
import {ApplicationTreeDatabase} from './application-tree-database';
import {ApplicationTreeDatasource} from './application-tree-datasource';
// Observable classes and extensions.
import {BehaviorSubject, Subject, fromEvent, of, merge} from 'rxjs';
// Observable operators.
import {debounceTime, distinctUntilChanged, switchMap, takeUntil} from 'rxjs/operators';
@Component({
selector: 'app-application-tree',
templateUrl: './application-tree.component.html',
styleUrls: ['./application-tree.component.css'],
providers: [ApplicationTreeDatabase]
})
export class ApplicationTreeComponent implements OnInit, OnDestroy {
@ViewChild('appfilter') inputfilter: ElementRef;
@ViewChild(MatPaginator) paginator: MatPaginator;
readonly defaultPaginatorPageIndex = 0;
readonly defaultPaginatorPageSize = 2;
readonly defaultPaginatorPageRange = this.defaultPaginatorPageIndex + '-' + (this.defaultPaginatorPageSize - 1);
private ngUnsubscribe: Subject<void> = new Subject<void>();
// Application name filter. START
_inputFilterChange = new BehaviorSubject('');
get inputFilterValue(): string {
return this._inputFilterChange.value;
}
set inputFilterValue(inputFilterValue: string) {
this._inputFilterChange.next(inputFilterValue);
}
// Application name filter. END
treeControl: FlatTreeControl<DynamicFlatNode>;
dataSource: ApplicationTreeDatasource;
getLevel = (node: DynamicFlatNode) => node.level;
isExpandable = (node: DynamicFlatNode) => node.expandable;
hasChild = (_: number, _nodeData: DynamicFlatNode) => _nodeData.expandable;
constructor(
private applicationService: ApplicationService,
private dataRequestHelper: TableDataRequestParamsService,
private database: ApplicationTreeDatabase) {
this.treeControl = new FlatTreeControl<DynamicFlatNode>(this.getLevel, this.isExpandable);
this.dataSource = new ApplicationTreeDatasource(this.treeControl, this.paginator, database);
}
ngOnInit(): void {
fromEvent(this.inputfilter.nativeElement, 'keyup').pipe(
debounceTime(150)
, distinctUntilChanged()
, switchMap(term => of(term))
, takeUntil(this.ngUnsubscribe)
)
.subscribe(() => {
if (!this.dataSource) {
return;
}
// this.resetPaginator();
this.inputFilterValue = this.inputfilter.nativeElement.value;
});
this.loadAppsData();
this.onUserInteractionsWithTree();
}
ngOnDestroy(): void {
this.ngUnsubscribe.next(); // Unsubscribe from observables.
this.ngUnsubscribe.complete(); // Unsubscribe from ngUnsubscribe.
}
resetFilterAndTriggerChange() {
// Clear HTML filter content.
this.inputfilter.nativeElement.value = '';
// Clear filter data stream. => This will trigger database.load()
// because of Event emmited by inputFilterValueChange.
this.inputFilterValue = '';
}
buildAppDataRequestParams(): DataRequestOptions {
let range = this.dataRequestHelper.buildRequestRangeValue(this.paginator);
if (!range) { // paginator not initialized.
range = this.defaultPaginatorPageRange;
}
return new DataRequestOptions(this.inputFilterValue, 'name', range);
}
private loadAppsData() {
this.applicationService.getDataObjects(this.buildAppDataRequestParams())
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(dataAndMeta => {
// Update local Apps database.
this.database.updateApplicationData(dataAndMeta.data);
this.updatePaginator(dataAndMeta.meta);
// Inform datasource that data has changed.
this.dataSource.data = this.database.getAppsAsRootLevelNodes();
},
error => {
const errMsg = 'Echec d\'acces aux données';
throw new Error(errMsg);
}
);
}
private onUserInteractionsWithTree() {
const treeUserActionsListener = [
this._inputFilterChange,
this.paginator.page
];
// Merge the array of Observable inputs of treeUserActionsListener
// and put into the source property of a newly created Observable.
const mergeOfObservables = merge(...treeUserActionsListener);
// Create new Observable<RoleMemberClient[]> by calling the function defined below.
mergeOfObservables
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((data: any) => {
this.loadAppsData();
});
}
private updatePaginator(meta: MetaDescriptor) {
if ((meta) && (meta.isPaginatedData)) {
const contentRange = meta.contentRange;
const rangeStart = contentRange.rangeStart;
this.paginator.pageIndex = Math.floor(rangeStart / this.paginator.pageSize);
this.paginator.length = contentRange.size;
} else if (meta) {
// All data can be contained within the first table page.
this.paginator.length = meta.count;
if (this.paginator.pageIndex * this.paginator.pageSize < meta.count) {
// If last requested page do not contain data, do not reset table page index.
// The user will do it by itself.
// Otherwise reset the table page index to zero.
this.paginator.pageIndex = 0;
}
}
}
}