Angular 7 - 我是否创建了太多订阅?

2024-01-12

我想知道我的代码是否会造成内存泄漏?

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;
      }

    }
  }

}

内存泄漏 :

可观察模式很容易出现内存泄漏,因为订阅在组件之后仍然存在(在这种情况下)死亡,将在应用程序的生命周期内持续存在。

例如 :假设您有一个组件订阅 to a formControl当创建组件但从未关闭订阅时,每次创建组件时都会创建一个新的订阅。你有泄漏,你可能会超载内存。

截止认购:

当可观察到的时候订阅结束完成或者您手动取消订阅。
您已选择创建一个Subject(你称它为ngUnsubscribe- 这是一个非常糟糕的名字)。你complete()组件被销毁时的主题。
这意味着当组件被销毁时,对该主题的每个订阅都将被关闭。
当您订阅时,您使用takeUntil(ngUnsubscribe),事实上,您创建了原始可观察到的镜像并订阅了该镜像。
因此,当您的组件被销毁时,在您的镜像上进行的每个订阅(ngUnsubscribe),被破坏。所以不,你没有内存泄漏。

Notes :
因为订阅在 observable 完成时关闭,所以您不需要创建可观察的镜像/取消订阅来自完成您的可观察的方法,例如角度HttpClient(get, post,...)。

您可以在 Alex Beugnet 评论中提供的链接上找到所有这些信息。
为了更好地理解,您可以检查:

接收弹珠 http://rxmarbles.com/
学习rxjs https://www.learnrxjs.io/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Angular 7 - 我是否创建了太多订阅? 的相关文章

随机推荐

  • 带 rollup 和 redux 的 lit-element:未定义流程

    我正在尝试将基于 lit element 的应用程序中的状态管理从简单的全局变量切换为 redux 按照 redux 教程 我安装了 redux 工具包并创建了一个简单的减速器和存储 使用汇总构建应用程序成功 但当我在 Chrome 中加载
  • Java机器人鼠标移动:设置速度?

    Java Robot 类允许人们移动鼠标 就像移动实际的物理鼠标一样 然而 如何以一种人性化 而非即时 的方式将鼠标从 Point1 移动到 Point2 又名 如何设置移动速度 如果Robot类不可能达到这样的速度 那么如果鼠标只能瞬时移
  • 如何解决 FATAL:超出非超级用户的连接限制

    我写了一个用于批量插入的java代码 我使用复制命令为不同的表导入和创建不同的连接对象 但在执行时 程序抛出以下错误 FATAL connection limit exceeded for non superusers 您已超出 Postg
  • 如何防止弹出基本身份验证表单

    我有一个 Java 应用程序 JSF 它使用 javascript 连接到需要基本身份验证的网站 我想要配合的事情与我在弹出表单中输入用户名和密码时发生的事情完全相同 我已经尝试了许多关于该主题的不同方法 但没有一个有效 奇怪的是 ajax
  • 以编程方式单击 jetpack compose 中的文本字段

    有没有一种方法可以以编程方式单击文本字段 以便当我的搜索屏幕弹出时 它会自动单击文本字段并弹出键盘 或者 有没有办法知道文本字段的触摸事件 With 1 0 x您可以将焦点放在该组件上 就像是 var text by remember mu
  • 使列等高 - 通过嵌套

    我的设计使用两个外部列 并在标题部分的外部列之一和下面的另外两列中 如下所示 header out1 out2 footer
  • MockMVC 对异步服务执行后期测试

    我需要测试调用异步服务的控制器 控制器代码 RequestMapping value path method RequestMethod POST produces MediaType APPLICATION JSON VALUE Resp
  • 如何在 Spark 中以小块形式迭代大型 Cassandra 表

    在我的测试环境中 我有 1 个 Cassandra 节点和 3 个 Spark 节点 我想迭代大约有 200k 行的明显大表 每行大约占用 20 50KB CREATE TABLE foo uid timeuuid events blob
  • 如何将 MIDAS.DLL 嵌入客户端可执行文件中

    据博士说 鲍勃 这是可以做到的 有人可以提供分步示例或教程吗 你没有嵌入MIDAS DLL 你添加MidasLib到你的项目的USES条款 这会将基本功能嵌入到您的程序中 而无需依赖单独的 DLL
  • 服务器控制行为异常

    我有一个我编写的服务器控件 通常工作正常 但是 当我添加突出显示的行时 它添加的不是一个而是两个 br 元素 这不是我所追求的 mounting new DropDownLabel mounting ID mountTypeList mou
  • 是否可以在 Adob​​e Flex 中执行#define?

    我正在寻找一种方法来执行类似于 adobe flex 中的 c c define 的操作 我希望项目构建可以采用许多不同的路径 具体取决于是否定义了某些内容 Flex 中存在这样的东西吗 我知道有一些方法可以设置全局变量 但这并不真正适合我
  • 反序列化Bson文件

    我有一个用 mongodump 工具生成的 Bson 文件 我想在 C 代码中反序列化 为此 我似乎可以使用 mongodb C 驱动程序或 Json net 库 我尝试了它们 但我无法让它们工作 使用 Json net 库 input 是
  • MySQL Sum() 多列

    我有一张学生记分卡表 这是桌子 subject mark1 mark2 mark3 markn stud1 99 87 92 46 stud2 studn 现在 我需要对每个学生的总分进行求和 我通过使用得到它sum mark1 mark2
  • MySQL 数据透视表列数据作为行

    我正在努力寻找解决这个 MySQL 问题的方法 我似乎不知道该怎么做 我有以下表格 Question table id question 1 Is it this 2 Or this 3 Or that Results Table id u
  • android 使用外部 java 库时出现 java lang verifyerror

    我在我的 android 项目中使用外部库 在调用导入该库的类时出现 javalang verify 错误 它是 java jxl 库 请提供帮助我在库项目中创建了一个名为 lib 的目录 然后在库中引用它 这个库与原始的 Android
  • .NET 中的全局变量(或替代方案)最佳实践

    在 VB NET WinForms 应用程序中存储全局变量的最佳实践是什么 例如 当用户登录应用程序时 您可能希望存储一个可以在整个应用程序中访问的 CurrentUser 对象 您可以将其存储为模块中的对象 或者创建一个包含所有所需全局变
  • 如何使用 Javascript 滚动到底部时附加更多行

    It s in 共享点2010年 但我认为它可能会正常运行 1 默认情况下 每页的项目限制为 30 因此 我已经完成了在页面加载时显示 30 行的列表 然后我将鼠标向下滚动到底部 它隐藏了最后 10 行 Summary 页面加载 30 行
  • ToList 方法不适用于 TrackableCollection

    我们正在 EF 4 0 之上与可跟踪实体合作 为了删除实体及其所有依赖实体 我正在编写一个通用的DeleteDependentEntities 以便从EntityManager 中的Delete 方法调用 我们不 或者不想 依赖于在数据库中
  • 从数据库字段中删除特殊字符

    我有一个包含数千条记录的数据库 我需要删除其中一个字段以确保它只包含某些字符 字母数字 空格和单引号 我可以使用什么 SQL 从整个数据库的该字段中删除任何其他字符 例如斜杠等 update mytable set FieldName RE
  • Angular 7 - 我是否创建了太多订阅?

    我想知道我的代码是否会造成内存泄漏 Context 我有一个应该显示 应用程序 对象的组件类 它具有过滤和分页功能 我创建了一个方法加载应用程序数据 其中我订阅到 向 Web 服务发出请求后返回的 Observable 该方法在初始化时被调