rxjs 操作符
Photo by Pablo Heimplatz on Unsplash — Emitting a large number of events Pablo Heimplatz在Unsplash上的照片 —发射了大量事件
RxJS操作员技巧— startWith (RxJS Operator Tips — startWith)
Creating awesome by combining
startWith
and
EventEmitter
通过结合
startWith
和
EventEmitter
创建超赞的
EventEmitter
It’s quite easy to get lost in RxJS. There are plenty of operators to choose from and on my developer journey, I’ve discovered that it’s really easy to overlook an operator that might save you a headache or 2. 在RxJS中迷路很容易。 在我的开发者旅程中,有很多运营商可供选择,我发现忽略运营商确实很容易,这可能会让您头疼或烦恼2。
Following on from my previous article discussing flatMap vs. switchMap, in this article I’m going to cover
startWith
. 在上一篇讨论flatMap与switchMap的文章之后 ,本文将介绍
startWith
。
The official definition from the RxJS documentation is: RxJS文档的正式定义是:
Returns an Observable that emits the items you specify as arguments before it begins to emit items emitted by the source Observable. 返回一个Observable,它在开始发出源Observable发出的项目之前,发出您指定为参数的项目。
问题 (The Problem)
As usual, with programming, it’s too easy to get consumed by the “what” and not really the “why”, so we’ll start with a problem definition. 像往常一样,通过编程,很容易被“什么”而不是真正的“为什么”所消耗,因此我们将从问题定义开始。
Given I have a seed value that queries for a list of sub values, how can I subscribe to a Refresh Event that updates the list even when the seed doesn’t change? 给定我有一个查询子值列表的种子值,即使种子没有变化,我如何订阅刷新该列表的刷新事件?
That might be confusing, so, broken down into another way: 这可能会造成混淆,因此分解为另一种方式:
-
My support system shows a list of open tickets
path/to/tickets
我的支持系统显示了未售票
path/to/tickets
-
An operator selects a ticket
/path/to/tickets/:id
操作员选择票证
/path/to/tickets/:id
- All of the comments inside that ticket are displayed
显示该票证中的所有评论
- When the Refresh Event is fired, how do I refresh the page?
刷新事件触发后,如何刷新页面?
Yes, the user can refresh the page, but if we’re running in a PWA then it isn’t readily apparent how to do that as the browser chrome isn’t visible. 是的,用户可以刷新页面,但是如果我们在PWA中运行,那么由于浏览器镶边不可见,如何做到这一点并不容易。
The use of the Refresh Event is useful because it may not just be a button that refreshes the content. If you’ve wired your page to some kind of Web Socket system, you may need to refresh the list automatically on some external signal. 使用刷新事件很有用,因为它可能不仅仅是刷新内容的按钮。 如果将页面连接到某种Web Socket系统,则可能需要根据某些外部信号自动刷新列表。
I’m going to show you how
startWith()
can save the day here and create a really elegant solution. 我将向您展示
startWith()
如何在这里节省一天的时间并创建一个非常优雅的解决方案。
TLDR (TLDR)
Add
startWith
to your
EventEmitter
subscription pipelines and they’ll emit an initial value without having to wait for one! 将
startWith
添加到您的
EventEmitter
订阅管道中,它们将发出初始值,而无需等待!
If you’d like a bit more detail, read on 如果您想了解更多细节,请继续阅读
设置 (The Setup)
Let’s start with a greenfield Angular project containing 2 routes, the Ticket List and the Ticket View. 让我们从一个绿地Angular项目开始,该项目包含2条路线,票证列表和票证视图。
门票清单 (Ticket List)
For this demo, the ticket list is very simple, it just contains a really simple array of ticket ids (1–9) and a series of
<a>
tags that navigate the user to
/tickets/:id
. 对于此演示,票证列表非常简单,它仅包含一个非常简单的票证ID数组(1–9)和一系列
<a>
标签,这些标签将用户导航到
/tickets/:id
。
The boilerplate code 样板代码
The UI for this looks a little like: UI看起来像这样:
票务视图 (Ticket View)
The Ticket View is where the “problem” is going to be solved. I will discuss 3 solutions for the Typescript code, however, the HTML will remain the same. 票务视图是解决“问题”的地方。 我将讨论TypeScript代码的3种解决方案,但是HTML保持不变。
The layout simply contains: 该布局仅包含:
- Ticket Title Heading
机票标题
- Previous Ticket Button
上一张票证按钮
- Refresh Comments Button
刷新评论按钮
- Next Ticket Button
下一票按钮
- List of all comments
所有评论清单
As the Ticket View is an Angular component, it will be possible to extract the ID using the
ActivatedRoute
class. An initial stubbed implementation of the Ticket View could look something like this: 由于票证视图是Angular组件,因此可以使用
ActivatedRoute
类提取ID。 票证视图的初始存根实现可能类似于以下内容:
The 2 UI Screens — Ignore the complete lack of design 2个UI屏幕-忽略完全缺乏设计的情况
示例—获取评论 (Sample — Fetching Comments)
In each of these samples, I will use the same method to fetch the actual ticket comments. Clearly this is just a sample as it doesn\’t actually do any fetching. However, by returning the result as an
Observable<string[]>
this will simulate an actual networking call as the Angular
HttpClient
will behave like an Observable. 在每个样本中,我将使用相同的方法来获取实际的票证注释。 显然,这只是一个示例,因为它实际上并未进行任何提取。 但是,通过将结果作为
Observable<string[]>
这将模拟实际的网络调用,因为Angular
HttpClient
行为类似于Observable。
丑陋的 (The Ugly)
Looking back at the problem statement “how do I refresh the list”, the easiest solution is to just refresh the page. 回顾问题陈述“如何刷新列表”,最简单的解决方案就是刷新页面。
优点 (Pros)
- The list will refresh, job done
列表将刷新,工作完成
缺点 (Cons)
- The entire page will reload, including everything in it
整个页面将重新加载,包括其中的所有内容
- Additional server load
额外的服务器负载
- No ability for in-page progress bars or UX
没有页面内进度条或UX功能
This means that all of the javascript, HTML and CSS needs to be recalculated for the entire website. And, if your users were in the middle of doing something else then their work could be lost. This is massive overkill for such a simple problem and defeats the whole purpose of a Single Page Application (SPA). This is the very definition of a sledgehammer to crack a nut. 这意味着需要为整个网站重新计算所有的javascript,HTML和CSS。 而且,如果您的用户正在做其他事情,那么他们的工作可能会丢失。 对于这样一个简单的问题,这太过分了,并且破坏了单页应用程序(SPA)的整个目的。 这就是敲击螺母的大锤的确切定义。
Additionally, by using the
snapshot
mechanism of
activatedRoute
we’re also unable to use the reusable components that Angular provides. 此外,通过使用
activatedRoute
的
snapshot
机制,我们也无法使用Angular提供的可重用组件。
坏人 (The Bad)
Next, we move to the slightly better, but not great implementation. In this example, we’ll use a
BehaviorSubject
to store the value of the ticket ID. 接下来,我们转到稍好一点但不是很好的实现。 在此示例中,我们将使用
BehaviorSubject
存储票证ID的值。
So, this is much better, but the “refreshing” logic hinges around resetting the value of the
currentTicketId
BehaviourSubject to the same value. This will make the pipeline think that the value has changed and will force the comments to load. 因此,这要好得多,但是“刷新”逻辑取决于将
currentTicketId
BehaviourSubject的值重置为相同的值。 这将使管道认为该值已更改,并将强制加载注释。
优点 (Pros)
- The page doesn’t reload
页面不会重新加载
- Components are reused when navigating to different tickets
导航到不同的票证时组件被重用
缺点 (Cons)
-
All elements using
currentTicketId
will recalculate on comment change
所有使用
currentTicketId
元素都会在评论更改时重新计算
- Need to filter out the initial null value on page load
需要在页面加载时过滤掉初始null值
- No way to tell the difference between a page load and a refresh event
无法分辨页面加载和刷新事件之间的区别
Personally, I use BehaviourSubjects in lots of my projects. They’re a great way of converting a value to an observable and easily change them. 就个人而言,我在很多项目中都使用BehaviourSubjects。 它们是将值转换为可观察值并轻松更改它们的好方法。
But, I think we can do 1 better. 但是,我认为我们可以做的更好1。
善良(或可能是伟大的) (The Good (or, possibly the Great))
I’ve taken a long time to get to here, but we’re finally going to use
startWith
. 我花了很长时间到达这里,但我们最终将使用
startWith
。
The Angular
EventEmitter
is a really simple observable. When the
emit
method is called (with an optional value), then all subscribers to that event can perform an action. Angular
EventEmitter
是一个非常简单的观察对象。 当调用
emit
方法(具有可选值)时,该事件的所有订阅者都可以执行操作。
So, my ideal goal would be to somehow combine the incoming Ticket ID observable and the Refresh Requested event. The result of this would be if either the Ticket Id or the Refresh Event are triggered, the comments list is updated. 因此,我的理想目标是以某种方式结合可观察到的传入票证ID和“刷新请求”事件。 结果将是, 如果票证ID或刷新事件被触发,则评论列表将被更新。
I’m going to bring together 2 additional RxJS concepts to solve this problem the right way: 我将汇集2个其他RxJS概念以正确的方式解决此问题:
-
Use the
combineLatest
operator to listen to both the ID and the Event
使用
combineLatest
运算符来侦听ID和事件
-
Add
startWith
to the event so it has an initial value
将
startWith
添加到事件中,使其具有初始值
优点 (Pros)
- The page doesn’t reload
页面不会重新加载
- Components are reused when navigating to different tickets
导航到不同的票证时组件被重用
-
Can separate out Refresh event from Ticket Id change
可以将刷新事件与票证ID更改分开
缺点 (Cons)
- ?
?
The benefit of using
combineLatest
is that the same pipeline can be used to retrieve the comments, no matter how this was triggered. This is a really useful function as it’ll take a number of Observable and return the output as a single array, but only when each observable has emitted something. 使用
combineLatest
的好处是,
combineLatest
,都可以使用同一管道来检索注释。 这是一个非常有用的函数,因为它将接受多个Observable并将输出作为单个数组返回, 但是仅当每个observable发出了一些东西时才返回。
This is where
startWith
comes in. It ensures that the event always emits something so the comments will load in at least once. 这是
startWith
进入的地方。它确保事件始终发出某些内容,以便注释至少加载一次。
The Output — I’m not a UI Designer 输出—我不是UI设计器
好处 (Benefits)
I think the major benefit of this is that it makes using Events as part of pipelines really accessible. 我认为这样做的主要好处是可以真正使用事件作为管道的一部分。
There are many scenarios where you wish to load in data and have it refresh. Without this
startWith
functionality you’ll need to write quite a bit of extra/quirky workarounds which will just make your code ugly. 在许多情况下,您希望加载数据并刷新数据。 没有
startWith
功能,您将需要编写大量额外的/古怪的解决方法,
startWith
会使您的代码难看。
结论 (Conclusion)
The
startWith
operator can be useful in many scenarios. For any observable that you have which doesn’t have an initial value (which is nearly all observables outside of
of() & from() & BehaviorSubject
then having an initial value can make a lot of sense.
startWith
运算符在许多情况下都非常有用。 对于任何没有初始值的可观察对象(几乎是
of() & from() & BehaviorSubject
之外的所有可观察对象,那么具有初始值可能很有意义。
Like most things, this is subjective. I’m a really great fan of “finding the right solution”, but, this doesn’t have to be your solution. Either way, the list of RxJS operators is large, feature-rich and extremely helpful with many scenarios. 像大多数事情一样,这是主观的。 我非常喜欢“找到正确的解决方案”,但是,这不一定是您的解决方案。 无论哪种方式,RxJS运算符的列表都很大,功能丰富,并且在许多情况下都非常有用。
What will you use
startWith
for? 您将使用
startWith
做什么?
翻译自: JavaScript/ 操作符