我想知道我的代码是否会造成内存泄漏?
我有一个应显示“应用程序”对象的组件类。 它具有过滤和分页功能。
我创建了一个方法 loadAppsData(),在其中我订阅 向Web服务发出请求后返回的Observable。
在初始化时 ngOnInit(),或者在用户与过滤输入字段或分页器交互之后调用此方法(请参见方法 onUserInteractionsWithTree()))< / p>
为避免内存泄漏,我已经使用
.pipe(takeUntil(this.ngUnsubscribe))
和
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;
}
}
}
}
答案 0 :(得分:1)
内存泄漏:
可观察的模式易于发生内存泄漏,因为在组件(在这种情况下)失效之后将继续存在的预订将在应用程序的生命周期中持续存在。
例如:假设您有一个组件,该组件在创建时会订阅到formControl
,但每次创建时都不会关闭订阅您创建新订阅的组件。您有泄漏,可能会使内存超载。
关闭订阅:
可观察的完成或您手动取消订阅后,订阅结束。
您选择创建一个Subject
(您将其命名为ngUnsubscribe
-这是一个非常糟糕的名字)。当组件销毁时,您complete()
是主题。
这意味着在销毁组件时将关闭对该主题的所有订阅。
订阅时,实际上您使用takeUntil(ngUnsubscribe)
,创建的是原始可观察的镜像并在该镜像上进行订阅。
因此,销毁组件时,在镜像(ngUnsubscribe
)上进行的所有预订都会销毁。 不,您没有内存泄漏。
注释:
由于可观察的完成时订阅已关闭,因此您无需根据完成可观察的方法(例如角度HttpClient
(get
,{{ 1}},...)。
您可以在Alex Beugnet的评论中提供的链接中找到所有这些信息。
为了更好的理解,您可以检查:
答案 1 :(得分:1)
您的工作可以,但是可以,但是理想情况下,您应该使用模板内的| async
管道进行订阅,因为angular可以处理所有订阅并为您取消订阅。孤儿订阅将导致应用程序中的内存泄漏。
因此在您的组件中执行此操作
ngOnInit() {
this.dataObjects = this.applicationService.getDataObjects(this.buildAppDataRequestParams());
}
并在您的模板中
<ng-container *ngFor="let dataObject of dataObjects | async">
// Your html markup for each dataObject here
{{dataObject | json}}
</ng-container>
如果要进行复杂的映射或事件,则应考虑使用BehaviourSubjects
,然后使用
getDataObjects.toPromise().then(r =>
{
dataObjectSubject.next(r.map(i => // mapping here))
})
并使用|async
管道以相同的方式订阅模板中的BehaviourSubject。
希望有帮助
答案 2 :(得分:0)
大代码段,我注意到的很少:),但是很难实现整体和平。
1。
this.applicationService.getDataObjects(this.buildAppDataRequestParams())
.pipe(takeUntil(this.ngUnsubscribe))
这里看起来不需要pipe(takeUntil(this.ngUnsubscribe))
,因为getDataObjects看起来像xmlhttprequest。这样就立即完成了。
switchMap(term => of(term))
-对我来说很糟糕。您创建流并立即对其进行扁平化,我想主要目的是在新值出现时杀死流,但效果不好。
.subscribe((data: any) => { this.loadAppsData();});
-在订阅中,您调用了另一个函数,该函数也订阅了另一个流,因此很难遵循。
我会让您的组件更多地驱动事件。
将逻辑分解为何时需要触发数据,何时到达新数据需要发生什么。订阅一次。
想法:
this.data$ = merge(componentInit$, inputChanged$, nextPage$).pipe(
switchMap(() => loadData()/* should return stream, not subscription */)
)
//...
this.data$.pipe(takeUntil(/*bablba*/), map(/* transformations */)).subscribe(() => {
//actions with data
})
答案 3 :(得分:0)
首先
this.ngUnsubscribe.next(); // Unsubscribe from observables.
next() 不取消对可观察对象的订阅。用于通过可观察对象发送数据。
要接收此数据,您需要先订阅“ ngUnsuscribe”(坏名,因为它会产生误会),然后再调用函数。
因此,正确的主题流是
如果在发送数据的下一行中关闭主题,则可能会由于主题通信的异步性质而产生问题。