如何避免RxJs可观察对象的内存泄漏?

在大型Angular应用程序中,最容易陷入麻烦的方法是不取消订阅可观察对象会造成内存泄漏.虽然有不同的技术自动取消订阅可观察对象,一种方法更简洁、优雅,而且总体上是最防错的。

这个技巧就是使用异步从Angular框架。

为什么异步管这么厉害的工具?

  1. 首先,它自动订阅可观察对象,因此我们不再需要调用.subscribe ()
  2. 它返回来自该可观察对象的数据,在我们的组件需要显示最新数据时触发Angular的更改检测。
  3. 最后,当我们的组件被销毁时,它会自动从可观察对象中取消订阅。

所有这一切都只需要6个字符!(好吧,如果你想计算空白,可能会多一点):

              
              
<<跨度class="hljs-name">div>{{myObservable | async}}<跨度class="hljs-tag">div>
代码语言:<跨度class="shcb-language__name">HTML、XML<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">xml<跨度class="shcb-language__paren">)

异步如果我们不使用Pipe,它的工作方式与我们编写的代码完全相同(因此会反复重复,使代码库更大,从而使代码更慢)。

检查它的源代码在这里,您将看到它所做的一切都是实现ngOnDestroy取消订阅我们的可观察对象。

没有任何好的理由不使用异步管道,如果您认为您有一些理由,请继续关注我们的下一条消息,因为我们将介绍一些关于使用该管道的很好的提示和技巧。

如何为Angular应用程序生成文档?

编写软件文档是很困难的。不仅如此,维护软件文档更加复杂,而且常常被人遗忘。

那么,解决方案是什么呢?使用自动化的解决方案如何:

  1. 从代码注释生成文档,不需要Wiki或任何第三方软件。
  2. 自动生成上述文档,并且可以作为构建过程的一部分。
  3. 向开发人员提供指标和反馈,鼓励他们编写更多的文档。

这样的解决方案是存在的。它被称为Compodoc.Compodoc可以从应用程序中编写的所有注释中生成一个类似javadoc的网站(您可以看到这里是此类文档的示例):

Compodoc可以通过npm全局安装:

              
npm<跨度class="hljs-selector-tag">安装<跨度class="hljs-selector-tag">- g<跨度class="hljs-keyword">@compodoc/ compodoc
代码语言:<跨度class="shcb-language__name">CSS<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">css<跨度class="shcb-language__paren">)

或者你也可以在本地将它添加到单个Angular项目中ng添加项目文件夹中的原理图:

              
ng<跨度class="hljs-selector-tag">添加<跨度class="hljs-keyword">@compodoc/ compodoc
代码语言:<跨度class="shcb-language__name">CSS<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">css<跨度class="shcb-language__paren">)

然后你就可以创建一个配置文件来决定包含哪些文件(例如,你可能想要排除test,并且Compodoc已经准备好只用一个命令就可以运行:

              
npx<跨度class="hljs-selector-tag">compodoc<跨度class="hljs-selector-tag">- p<跨度class="hljs-selector-tag">tsconfig<跨度class="hljs-selector-class">. doc<跨度class="hljs-selector-class">. json
代码语言:<跨度class="shcb-language__name">CSS<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">css<跨度class="shcb-language__paren">)

上面的命令创建了一个静态HTML网站,记录了整个应用程序的所有模块/组件/管道/指令/服务。

我最喜欢的功能是文档覆盖统计使用一个类似于开发人员所熟悉的测试覆盖率报告的报告,显示应用程序的哪些部分是良好记录的,哪些部分不是。

RxJs组合最新运算符

combineLatest有一个说明一切的名字:操作符将两个或多个可观察对象的最新值组合成一个可观察对象。

这张大理石图完美地说明了这种行为(点击图片可以访问交互式版本):

该操作符有几种可能的用例。最常见的用法是通过组合不同的源来过滤信息,如下图所示RxJs和Angular表单的动态过滤教程,它实现了相当于自动补全文本输入的功能,每当用户输入新字符时,就会显示新的建议。

我们得到了什么combineLatest由组合的可观察对象发出的所有最新值组成的数组。例如,如果我们订阅组合最新(obs1, obs2, obs3),我们得到的数据是一个数组,包含[lastValueFromObs1, lastValueFromObs2, lastValueFromObs3].数组中数据的顺序与传递可观察对象的顺序相匹配combineLatest,该数组的值将再次发射,每当其中一个可观察对象再次发射。

一个常见的陷阱combineLatest操作符是否等待所有可观察的源至少发出一个值才返回某个值,因此将它与startWith操作符,如这个例子来自我们之前的教程

注意:操作符将被重命名为combineLatestWith在RxJs 8+

如何进行高级日期格式化?

本周,我们进行了报道的基本日期格式设置日期而且dateFormat函数.虽然这些工具在大多数基本场景下工作得很好,但有时我们想做更复杂的日期操作,例如:

  • 在日期上加/减天数
  • 显示一个持续时间,例如:"最近更新:2天前
  • 比较日期

大多数人在这种情况下使用Moment.js,但是js现在正式完成了并且处于维护模式。

最好的选择之一是date-fns.它使用现代Javascript,并尽可能依赖于本机Date对象。如果你以前用过Moment.js,date-fns看起来很熟悉:

              
进口{格式,格式Distance, formatRelative, subDays}<跨度class="hljs-keyword">从<跨度class="hljs-string">“date-fns”格式(<跨度class="hljs-keyword">新<跨度class="hljs-built_in">日期(),<跨度class="hljs-string">“‘今天是‘eeee’”)<跨度class="hljs-comment">//=> "今天是星期五"格式Distance (subDays (<跨度class="hljs-keyword">新<跨度class="hljs-built_in">日期(),<跨度class="hljs-number">3.),<跨度class="hljs-keyword">新<跨度class="hljs-built_in">日期(){<跨度class="hljs-attr">addSuffix:<跨度class="hljs-literal">真正的})<跨度class="hljs-comment">//=> "3 days ago"格式Relative (subDays (<跨度class="hljs-keyword">新<跨度class="hljs-built_in">日期(),<跨度class="hljs-number">3.),<跨度class="hljs-keyword">新<跨度class="hljs-built_in">日期())<跨度class="hljs-comment">//=> "last Friday at 7:26 p.m. ."
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

该库包含一些有趣的帮助函数,例如closestTo获取数组中最接近给定日期的日期,或formatDistanceToNow,它返回有意义的、可读的距离,比如“不到一分钟”或“大约一个月”。

这些特性都是独立的函数,这对于Angular应用来说非常棒,因为它允许在构建时摇树(只有我们需要的函数包含在构建输出中),而Moment只有一个时刻()函数中包含了所有东西,防止了正确的摇树。

Angular中的格式化函数

我们提到过的默认配置选项日期在Angular中。

你知道我们也可以在Typescript代码中使用日期格式吗formatDate函数?

此函数具有与日期管道相同的签名,这非常有意义,因为管道本身使用的函数(源代码在这里):

              
formatDate(值:字符串|数字| .<跨度class="hljs-built_in">日期,<跨度class="hljs-attr">格式:字符串,<跨度class="hljs-attr">语言环境:字符串,时区?:字符串):字符串
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

使用该函数所需要的只是将其导入@angular /常见

              
进口{formatDate}<跨度class="hljs-keyword">从<跨度class="hljs-string">“@angular /普通”
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

该函数的唯一缺点是没有默认格式或区域设置,因此必须将这两个值作为参数传递,而对于日期管道。

顺便说一下,类似的功能可用于数字、货币和百分比:

关于这些函数的更多信息,我有这个教程用Angular格式化日期和数字

日期管道默认格式和时区

日期管道是用Angular格式化日期的最方便的方法。然而,通常情况下,我们需要在整个应用程序中使用一致的日期格式,这意味着每次使用日期管道时都必须传递自定义格式:

              
<<跨度class="hljs-name">跨度>今天是{{今天|日期:'月/日/日'}}<跨度class="hljs-tag">跨度>
代码语言:<跨度class="shcb-language__name">HTML、XML<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">xml<跨度class="shcb-language__paren">)

当然,我们可以将该格式存储在一个常量中,并在每次使用管道时重用该常量,但这不是很方便。

幸运的是,从Angular 15开始,我们现在可以通过配置一个名为DATE_PIPE_DEFAULT_OPTIONS

它的工作原理是将以下代码添加到依赖注入配置(数组的供应商)app.modules.ts

              
提供者:[{<跨度class="hljs-attr">提供: DATE_PIPE_DEFAULT_OPTIONS,<跨度class="hljs-attr">useValue:{<跨度class="hljs-attr">dateFormat:<跨度class="hljs-string">“MM / dd / yy”}}]
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

有了这样的配置,我们就可以在没有任何参数的情况下使用管道,并在整个应用程序中自动应用默认格式:

              
<<跨度class="hljs-name">跨度>今天是{{今天|日期}}<跨度class="hljs-tag">跨度>
代码语言:<跨度class="shcb-language__name">HTML、XML<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">xml<跨度class="shcb-language__paren">)

属性也可以自定义时区时区相同的属性DatePipeConfig对象。

调试器关键字

昨天,我们介绍了如何使用带有

标记的JSON管道在网页上调试JSON数据。

今天,我想介绍另一种可以节省时间的调试技术调试器关键字。

当你想在你的web应用程序中添加断点时,添加Javascript指令:

              
调试器
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

然后在浏览器中打开开发工具并导航到应用程序URL。只要Javascript碰到调试器语句,运行时就会停止在那个断点上,允许你调试代码:

一旦浏览器在该断点上暂停,就可以通过单击浏览器开发工具中的行号添加其他断点,就像常规调试器一样。

有两件重要的事情需要注意:

  1. 断点仅在开发工具打开时才有效
  2. 别忘了去掉调试器语句。您可能不希望将其发布到生产环境中。

使用JSON管道进行调试

我们都用过console.log调试我们的代码。在Angular中,有一个有趣的替代方案可以临时在我们的web页面上显示调试信息JSON管

主要用途如下:

              
<<跨度class="hljs-name">跨度>myData | json<跨度class="hljs-tag">跨度>
代码语言:<跨度class="shcb-language__name">HTML、XML<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">xml<跨度class="shcb-language__paren">)

中以JSON字符串的形式输出数据跨度元素,但它不会被格式化,所以JSON字符串可能很难阅读:

              
{<跨度class="hljs-string">“关系”:<跨度class="hljs-string">“友好的”,<跨度class="hljs-string">“象征”:<跨度class="hljs-string">“默认土地单位”,<跨度class="hljs-string">“雁行”:<跨度class="hljs-string">“没有”,<跨度class="hljs-string">“mod1”:<跨度class="hljs-string">“没有”,<跨度class="hljs-string">“mod2”:<跨度class="hljs-string">“没有”,<跨度class="hljs-string">“uniqueDesignation”:<跨度class="hljs-string">"",<跨度class="hljs-string">“higherFormation”:<跨度class="hljs-string">"",<跨度class="hljs-string">“reinforcedReduced”:<跨度class="hljs-string">"",<跨度class="hljs-string">“飞行”:<跨度class="hljs-literal">假,<跨度class="hljs-string">“活动”:<跨度class="hljs-literal">假,<跨度class="hljs-string">“安装”:<跨度class="hljs-literal">假,<跨度class="hljs-string">“工作组”:<跨度class="hljs-literal">假,<跨度class="hljs-string">“commandPost”:<跨度class="hljs-string">“没有”,<跨度class="hljs-string">“tacticalMissionTasks”:<跨度class="hljs-string">“没有”,<跨度class="hljs-string">“类型”:<跨度class="hljs-string">“土地单位”
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

相反,下面的语法工作得更好:

              
<<跨度class="hljs-name">精准医疗>myData | json<跨度class="hljs-tag">精准医疗>
代码语言:<跨度class="shcb-language__name">HTML、XML<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">xml<跨度class="shcb-language__paren">)

精准医疗HTML标签代表“精准医疗格式化”内容。因此,该标记将保留任何空白、新行和制表符,这使得读取JSON数据变得更容易:

              
{<跨度class="hljs-string">“关系”:<跨度class="hljs-string">“友好的”,<跨度class="hljs-string">“象征”:<跨度class="hljs-string">“默认土地单位”,<跨度class="hljs-string">“雁行”:<跨度class="hljs-string">“没有”,<跨度class="hljs-string">“mod1”:<跨度class="hljs-string">“没有”,<跨度class="hljs-string">“mod2”:<跨度class="hljs-string">“没有”
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

这是一个简单的技巧,但在调试使用复杂JSON结构的代码时节省了大量时间。

RxJs的switchMap操作符

Angular应用中常见的一个错误是用以下方式嵌套可观察订阅:

              
observable1.subscribe (<跨度class="hljs-function">数据=>{observable2.subscribe (<跨度class="hljs-function">otherData=>{<跨度class="hljs-comment">//对otherData执行一些操作});});
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

不推荐使用上述语法,因为它很难阅读,并且可能导致微妙的错误和意想不到的副作用。例如,这种语法很难正确地取消所有这些可观察对象的订阅。

同样,如果observable1如果在短时间内触发多次,则可能需要取消以前的订阅observable2并根据接收到的新数据开始一个新的observable1.上面的代码没有做任何这些工作。

解决方案:使用RxJsswitchMap这样的运算符:

              
observable1.pipe (<跨度class="hljs-function">数据=>{switchMap(<跨度class="hljs-function">数据=>observable2).subscribe (<跨度class="hljs-function">otherData=>{<跨度class="hljs-comment">//对otherData执行一些操作});
代码语言:<跨度class="shcb-language__name">JavaScript<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">javascript<跨度class="shcb-language__paren">)

switchMapOperator执行以下所有操作:

  • 自动取消和取消订阅observable2如果observable1产生一个新值。
  • 自动取消订阅observable2如果我们取消订阅observable1
  • 确保observable1而且observable2依次发生,一个接一个。

下面的大理石图很好地说明了什么switchMap:

如果你不吸毒的话switchMap但是,请在代码中查找对的嵌套调用.subscribe (),然后开始用switchMap操作符,以防止内存泄漏和错误,并使您的代码更具可读性。

这里有一个链接一个使用定时器每30秒发出一次HTTP请求的例子获取更新的数据。使用将计时器数据转换为API调用switchMap在示例的第37行。

出口指示

昨天,我们讨论了模板引用变量.今天,我想向你展示如何使用模板引用变量访问指令。

你以前可能见过这种语法:

              
<<跨度class="hljs-name">形式#<跨度class="hljs-attr">myForm=<跨度class="hljs-string">“ngForm”(<跨度class="hljs-attr">ngSubmit)=<跨度class="hljs-string">“onSubmit (myForm)”>
代码语言:<跨度class="shcb-language__name">HTML、XML<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">xml<跨度class="shcb-language__paren">)

这种语法是可能的,因为NgForm指令是出口使用以下语法(实际的Angular源代码在这里):

              
@Directive({exportAs:<跨度class="hljs-string">“ngForm”})
代码语言:<跨度class="shcb-language__name">打印稿<跨度class="shcb-language__paren">(<跨度class="shcb-language__slug">打印稿<跨度class="shcb-language__paren">)

上面的代码允许使用模板引用变量,例如# myForm = " ngForm ".这种技术在Angular的表单和组件库中被广泛使用,用于将公共指令属性(和方法)公开给组件的模板。

例如,我们可以访问myForm.valuemyForm.valid在表达式中。

ngModel也是这样输出的。