10 RxJS operators tôi sử dụng hàng ngày trong dự án thực tế
Trong bài này, mình sẽ giới thiệu cho các bạn một số toán tử quan trọng mà theo mình, mọi developer Angular nên biết.
Để chọn ra chúng, mình đã dựa vào mã nguồn của một dự án mà mình và các đồng nghiệp đã làm trong hơn một năm, và ở đó mình đã đếm số lần xuất hiện của các toán tử.
1. Toán tử tap
Dù phân loại này có thể chủ quan, nhưng đây có lẽ là toán tử được sử dụng nhiều nhất đối với mọi lập trình viên RxJS.
Hãy xem tài liệu chính thức nói gì về nó:
Được sử dụng để thực hiện các hiệu ứng phụ cho các thông báo từ observable nguồn.
Nói cách khác, chúng ta có thể thực hiện một số hoạt động ngay khi Observable phát ra một sự kiện next, error hoặc complete, và chúng ta sẽ làm điều đó mà không làm thay đổi kết quả của Observable mà nó được kết nối.
Hãy xem ví dụ:
this.getDataFromServer()
.pipe(
tap((response) => this.originalResponse = response),
map(response => this.doSomethingWithResponse(response)),
tap(responseAfterMap => console.debug("transformed response", responseAfterMap))
)
.subscribe();
Ở đây, chúng ta đang sử dụng tap để lưu trữ phản hồi gốc trước (bởi vì chúng ta cần nó vì một lý do nào đó trong component của mình), và để xem cách nó đã được biến đổi bởi map sau này. Cái này làm rõ vì sao toán tử tap được sử dụng nhiều cho mục đích debugging; dòng code này, thật ra, sẽ có thể bị loại bỏ bởi developer ngay khi bạn hoàn thành việc debug.
2. Toán tử map
Mục đích của toán tử này là để biến đổi kết quả phát ra bởi Observable mà nó được gắn vào. Nó rất giống với hàm map của Array.
Một lần nữa, hãy xem ví dụ:
getVehicleInfo(vehiclePlate: string): Observable<CarInfo\> {
return this.http.get<CarInfo\>(\`https://someexampledomain.com/details/${vehiclePlate}\`)
.pipe(
map(vehicleData => {
return { model: vehicleData.model, color: vehicleData.color, isCar: vehicleData.type === 'CAR' };
})
);
}
Dịch vụ web tuyệt vời của chúng ta trả về rất nhiều thuộc tính và metadata về chiếc xe có biển số vehiclePlate; tuy nhiên, frontend của chúng ta không cần những thông tin chi tiết đến như vậy vì nó chỉ hiển thị mô hình, màu sắc và xem liệu chiếc xe có phải là ô tô hay không.
3. Toán tử of
Chúng ta sử dụng toán tử này để tạo ra một Observable mà ngay lập tức phát ra giá trị (hoặc list các giá trị) mà chúng ta đã truyền vào.
Nó có thể được sử dụng cho nhiều mục đích; có lẽ mục đích phổ biến nhất là để mô phỏng một phản hồi dịch vụ web. Hãy để mình cho bạn xem:
getVehicleInfo(vehiclePlate: string): Observable<CarInfo\> {
return of({ model: "Ford Puma" , color: "Desert Island Blue", isCar: true });
}
4. Toán tử switchMap
Hãy nói về một trong những toán tử yêu thích của mình! Mục đích của nó là để "chuyển đổi" một Observable với một Observable khác.
Một trường hợp sử dụng tiêu biểu là thanh tìm kiếm, cần thực hiện một lời gọi API mỗi khi người dùng nhập vào đó.
Hãy xem một chút code:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
switchMap(term => this.searchService.search(term))
)
.subscribe(list => this.carsList = list);
}
}
Có thêm 2 toán tử khác cũng làm công việc tương tự: concatMap và mergeMap; tuy nhiên, switchMap khác với chúng do hiệu ứng hủy của nó, tránh race conditions.
Trong trường hợp của chúng ta, điều này có nghĩa là nếu người dùng tiếp tục nhập vào thanh tìm kiếm (sẽ thực hiện thêm các lời gọi API), chỉ kết quả cuối cùng sẽ được hiển thị.
5. Toán tử catchError
Toán tử này rất quan trọng bất cứ khi nào chúng ta muốn xử lý các lỗi Observable.
Thông thường, mỗi khi một lỗi xảy ra, kết nối giữa Observable và Subscriber sẽ bị đóng. Chúng ta có thể tránh đóng kết nối bằng cách sử dụng catchError! Cách nó hoạt động thực sự rất đơn giản: nó nhận đầu vào là một lỗi và nó trả về một Observable khác.
Hãy chỉnh sửa ví dụ trước để hiểu rõ hơn:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
Từ giờ trở đi, nếu một lỗi xảy ra (tức là một lỗi API), nó sẽ được hiển thị cho người dùng và Subscription sẽ không bị đóng; điều này có nghĩa là thanh tìm kiếm sẽ tiếp tục hoạt động như thể không có gì xảy ra, thực hiện một lời gọi API mỗi khi người dùng nhập vào đó.
6. Toán tử startWith
Toán tử này có thể được đánh giá cao đặc biệt với Hot Observables.
Nó phát ra một giá trị nhất định ngay sau khi Subscription.
Hãy chỉnh sửa (lại) ví dụ trước để xem nó làm gì:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
Tại sao một dòng code nhỏ như vậy lại quan trọng?
Nếu người đọc đã chú ý, họ sẽ nhận thấy rằng có một vấn đề nhỏ với thanh tìm kiếm của chúng ta: ngay cả khi component đã được khởi tạo, người dùng sẽ không thấy bất kỳ chiếc xe nào trong danh sách cho đến khi họ nhập vào thanh tìm kiếm.
Sử dụng dòng code này, chúng ta vượt qua vấn đề này vì ngay khi component được init, nó sẽ thực hiện một lời gọi dịch vụ web cho phép chúng ta có được danh sách không lọc của các xe ngay từ đầu.
7. Toán tử debounceTime
Đây là một trong những toán tử yêu thích khác của tôi. Hãy đọc xem tài liệu chính thức nói gì về nó:
Loại bỏ các giá trị phát ra mà thời gian phát ra tính từ giá trị trước đó nhỏ hơn giá trị thời gian đã thiết lập
Nó có nghĩa là gì? Hãy xem xét ví dụ về thanh tìm kiếm một lần nữa.
Có một vấn đề khác là: mỗi khi người dùng nhập vào đầu vào, ngay cả chỉ một ký tự, API sẽ được gọi. Như bạn có thể tưởng tượng, máy chủ không hài lòng về điều đó. Hãy tưởng tượng mọi người đều làm như vậy cùng một lúc... (nếu không phải là một cuộc tấn công DDoS thì chúng khá giống rồi DDos rồi đấy ).
Sử dụng debouceTime(n), giá trị của trường tìm kiếm sẽ chỉ được phát ra sau n mili giây; hơn nữa, nếu trong khi đó người dùng đã nhập thêm một ký tự, thêm n mili giây sẽ trôi qua trước khi phát ra giá trị.
Điều này cho phép chúng ta giảm số lượng các lời gọi API đến máy chủ của chúng ta một cách đáng kể.
Hãy chỉnh sửa ví dụ cuối cùng của chúng ta một lần nữa để thấy sự khác biệt:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
debounceTime(300),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
8. Toán tử delay
Đây là một trong những toán tử đơn giản nhất: như tên gọi, nó chỉ trì hoãn việc phát ra một giá trị.
Một trường hợp sử dụng điển hình là, cùng với toán tử of, để làm cho việc gọi API mô phỏng trở nên "thực tế" hơn.
getVehicleInfo(vehiclePlate: string): Observable<CarInfo> {
return of({ model: "Ford Puma" , color: "Desert Island Blue", isCar: true })
.pipe(delay(500));
}
9. Toán tử distinctUntilChanged
Toán tử này chỉ phát ra một giá trị nếu nó khác với giá trị đã phát ra cuối cùng.
Đừng ghét mình vì điều này nhé, nhưng mình cần chỉnh sửa lại thanh tìm kiếm tuyệt vời của chúng ta một lần nữa để cho bạn thấy toán tử này có thể giúp ích cho chúng ta như thế nào. (Lần cuối mình hứa )
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
distinctUntilChanged(),
debounceTime(300),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
Và bây giờ... người dùng đã gõ điều gì đó vào thanh tìm kiếm; vì 300ms đã trôi qua nên component làm mới danh sách xe. Sau đó, người dùng vô tình gõ thêm một chữ cái, vì vậy anh ấy nhấn backspace để xóa nó. Thêm 300ms nữa trôi qua; tuy nhiên, API chưa được gọi vì distinctUntilChanged nhận ra rằng giá trị là giống với lần phát ra cuối cùng. Một lần nữa chúng ta đã tiết kiệm cho máy chủ của chúng ta khỏi một lần gọi API không cần thiết!
Bây giờ câu hỏi đặt ra là: Làm sao mà toán tử này nhận biết được không có sự thay đổi nào xảy ra?
À, theo mặc định distinctUntilChanged sử dụng toán tử so sánh === vậy nên nó ok cho các chuỗi.
Nhưng mà, cũng có thể tạo ra một hàm so sánh tùy chỉnh và truyền nó như một tham số đầu vào cho toán tử:
distinctUntilChanged((prev, curr) => prev.toLowerCase() === curr.toLowerCase())
10. Toán tử filter
Toán tử cuối cùng (nhưng không kém phần quan trọng) chúng ta sẽ xem ở đây là toán tử filter. Giống như trường hợp của toán tử map, toán tử này cơ bản làm cùng một việc mà hàm filter của Array thực hiện cho các Array. Nói cách khác:
Nó phát ra các giá trị thoả mãn điều kiện đã cung cấp
Cùng xem cách nó hoạt động qua một ví dụ:
export class CarComponent {
vehicles = [
{ model: "Ford Puma" , color: "Desert Island Blue", isCar: true },
{ model: "Iveco S-WAY" , color: "Polar White", isCar: false},
{ model: "Fiat 500" , color: "Gelato White", isCar: true }
]
// returns only the vehicles which are cars.
getCarsList(): Observable<CarListItem[]> {
from(vehicles).pipe(
filter(vehicle => vehicle.isCar)
);
}
}
English Version
In this article, I will introduce you to some important operators that, in my opinion, every Angular developer should know.
To select them, I relied on the source code of a project that I and my colleagues have been working on for over a year, where I counted the occurrences of the operators.
1. The tap
Operator
Although this classification may be subjective, this is probably the most widely used operator for every RxJS programmer.
Let's see what the official documentation says about it:
Used to perform side effects for every notification of the source observable.
In other words, we can perform some operations right when the Observable emits a next
, error
, or complete
event, and we will do that without modifying the result of the Observable it's connected to.
Let's see an example:
this.getDataFromServer()
.pipe(
tap((response) => this.originalResponse = response),
map(response => this.doSomethingWithResponse(response)),
tap(responseAfterMap => console.debug("transformed response", responseAfterMap))
)
.subscribe();
Here, we are using tap
to store the original response beforehand (because we need it for some reason in our component), and to see how it has been transformed by map
later on. This makes it clear why the tap
operator is used a lot for debugging purposes; this line of code, in fact, could be removed by the developer as soon as you finish debugging.
2. The map
Operator
The purpose of this operator is to transform the result emitted by the Observable it is chained to. It is very similar to the map
function of an Array.
Once again, let's see an example:
getVehicleInfo(vehiclePlate: string): Observable<CarInfo\> {
return this.http.get<CarInfo\>(`https://someexampledomain.com/details/${vehiclePlate}`)
.pipe(
map(vehicleData => {
return { model: vehicleData.model, color: vehicleData.color, isCar: vehicleData.type === 'CAR' };
})
);
}
Our great web service returns a lot of attributes and metadata about the vehicle with the license plate vehiclePlate; however, our frontend doesn't need such detailed information because it only displays the model, color, and whether the vehicle is a car or not.
3. The of
Operator
We use this operator to create an Observable that immediately emits the value (or a list of values) we passed in.
It can be used for various purposes; perhaps the most common one is to simulate a web service response. Let me show you:
getVehicleInfo(vehiclePlate: string): Observable<CarInfo\> {
return of({ model: "Ford Puma" , color: "Desert Island Blue", isCar: true });
}
4. The switchMap
Operator
Let's talk about one of my favorite operators! Its purpose is to "switch" one Observable with another Observable.
A typical use case is a search bar, which needs to make an API call every time the user types into it.
Let's see some code:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
switchMap(term => this.searchService.search(term))
)
.subscribe(list => this.carsList = list);
}
}
There are 2 other operators that do a similar job: concatMap and mergeMap; however, switchMap stands out from them due to its cancellation effect, avoiding race conditions.
In our case, this means that if the user continues typing into the search bar (triggering additional API calls), only the latest result will be displayed.
5. The catchError
Operator
This operator is very important whenever we want to handle errors in Observables.
Normally, when an error occurs, the connection between the Observable and the Subscriber is closed. We can prevent that by using catchError! It works very simply: it takes an error as input and returns another Observable.
Let's modify the previous example to understand it better:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
From now on, if an error occurs (i.e., an API error), it will be displayed to the user, and the Subscription will not be closed; this means the search bar will continue to work as if nothing happened, making an API call every time the user types into it.
6. The startWith
Operator
This operator can be particularly appreciated with Hot Observables.
It emits a certain value immediately after the Subscription.
Let's (re)modify our previous example to see what it does:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
Why is such a small line of code important?
If the reader paid attention, they would notice that there is a small issue with our great search bar: even if the component has been initialized, the user won't see any vehicles in the list until they type into the search bar.
Using this line of code, we solve this issue because as soon as the component is initialized, it makes a web service call that allows us to have an unfiltered list of cars right from the start.
7. The debounceTime
Operator
This is another one of my favorite operators. Let's read what the official documentation says about it:
Discard emitted values that take less than the specified time between output.
What does that mean? Let's consider an example with the search bar once again.
Another issue is that whenever the user inputs into the search bar, even just one character, an API call is made. As you can imagine, the server is not happy about that. Imagine everyone doing that at the same time... (if not a DDoS attack, they are quite similar to one already ).
By using debouceTime(n), the value of the search field will only be emitted after n milliseconds; moreover, if during that time the user inputs another character, another n milliseconds will pass before it gets emitted.
This allows us to significantly reduce the number of API calls to our server.
Let's (re)modify our last example once again to see the difference:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
debounceTime(300),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
8. The delay
Operator
This is one of the simplest operators: as the name suggests, it simply delays the emission of a value.
A typical use case is, along with the of operator, to make the API call simulation more "realistic."
getVehicleInfo(vehiclePlate: string): Observable<CarInfo> {
return of({ model: "Ford Puma" , color: "Desert Island Blue", isCar: true })
.pipe(delay(500));
}
9. The distinctUntilChanged
Operator
This operator only emits a value if it's different from the last emitted value.
Don't hate me for this, but I need to (once again) modify our great search bar to show you how this operator can help us. (Last time, I promise )
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList:CarInfo[] = [];
constructor(private searchService:SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
distinctUntilChanged(),
debounceTime(300),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
And now... the user has typed something into the search bar; 300ms have passed, so the component refreshes the list of cars. Then, the user accidentally types one more character, and then presses backspace to delete it. Another 300ms pass; however, the API is not called because distinctUntilChanged
recognizes that the value is the same as the last emitted value. Once again, we saved our server from an unnecessary API call!
Now the question arises: How does this operator know that there was no change?
Well, by default, distinctUntilChanged
uses the ===
comparison operator, so it's ok for strings.
However, you can also create a custom comparison function and pass it as a parameter to the operator:
distinctUntilChanged((prev, curr) => prev.toLowerCase() === curr.toLowerCase())
10. The filter
Operator
The last (but not least) operator we'll see here is the filter
operator. Like in the case of the map
operator, this operator basically does the same job that the filter
function of an Array does. In other words:
It emits only the values that satisfy the provided condition.
Let's see how it works through an example:
export class CarComponent {
vehicles = [
{ model: "Ford Puma" , color: "Desert Island Blue", isCar: true },
{ model: "Iveco S-WAY" , color: "Polar White", isCar: false},
{ model: "Fiat 500" , color: "Gelato White", isCar: true }
]
// returns only the vehicles which are cars.
getCarsList(): Observable<CarListItem[]> {
from(vehicles).pipe(
filter(vehicle => vehicle.isCar)
);
}
}
日本語版
この記事では、私の意見では、すべてのAngular開発者が知っているべきいくつかの重要なオペレーターを紹介します。
これらを選ぶために、私と同僚が1年以上取り組んでいるプロジェクトのソースコードを頼りに、オペレーターの出現回数を数えました。
1. tap
オペレーター
この分類は主観的かもしれませんが、これはおそらくすべてのRxJSプログラマーが最もよく使うオペレーターです。
公式のドキュメントでは次のように説明されています:
ソースObservableの通知ごとに副作用を実行するために使用されます。
言い換えると、Observable が next
、error
、または complete
イベントを発行したときに、そのObservableの結果を変更せずに、いくつかの操作を実行できます。
例を見てみましょう:
this.getDataFromServer()
.pipe(
tap((response) => this.originalResponse = response),
map(response => this.doSomethingWithResponse(response)),
tap(responseAfterMap => console.debug("transformed response", responseAfterMap))
)
.subscribe();
ここでは、tap
を使用して、元のレスポンスを事前に保存しています(コンポーネントで何かの理由で必要なため)、そして map
でどのように変換されたかを後で確認しています。実際、このコード行はデバッグが終了したら開発者によって削除される可能性があるため、tap
オペレーターはデバッグ目的でよく使用されることがわかります。
2. map
オペレーター
このオペレーターの目的は、それに連結されたObservableから発行される結果を変換することです。これは、Arrayの map
関数に非常に似ています。
再び、例を見てみましょう:
getVehicleInfo(vehiclePlate: string): Observable<CarInfo> {
return this.http.get<CarInfo>(`https://someexampledomain.com/details/${vehiclePlate}`)
.pipe(
map(vehicleData => {
return { model: vehicleData.model, color: vehicleData.color, isCar: vehicleData.type === 'CAR' };
})
);
}
私たちの素晴らしいWebサービスは、車両のライセンスプレート vehiclePlate の属性やメタデータを多く返します。しかし、フロントエンドではモデル、色、および車両が車かどうかだけを表示する必要があります。
3. of
オペレーター
このオペレーターは、すぐに値(または値のリスト)を発行するObservableを作成するために使用します。
さまざまな目的で使用できますが、おそらく最も一般的なのはWebサービスの応答をシミュレートすることでしょう。以下に示します:
getVehicleInfo(vehiclePlate: string): Observable<CarInfo> {
return of({ model: "Ford Puma" , color: "Desert Island Blue", isCar: true });
}
4. switchMap
オペレーター
私のお気に入りのオペレーターの1つについて話しましょう!その目的は、「別のObservableで切り替える」というものです。
典型的な使用例は、ユーザーが入力するたびにAPIコールを行う検索バーです。
いくつかのコードを見てみましょう:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList: CarInfo[] = [];
constructor(private searchService: SearchService) {
this.form.controls['search'].valueChanges.pipe(
switchMap(term => this.searchService.search(term))
)
.subscribe(list => this.carsList = list);
}
}
同じような役割を果たす他の2つのオペレーターもあります: concatMap と mergeMap ですが、switchMap は race conditions を回避するためのキャンセル効果があるため、それらとは異なります。
私たちの場合、これはユーザーが検索バーに入力し続ける場合(追加のAPIコールをトリガーする場合)に、最新の結果のみが表示されることを意味します。
5. catchError
オペレーター
このオペレーターは、Observable内のエラーを処理する際に非常に重要です。
通常、エラーが発生すると、ObservableとSubscriberの間の接続が閉じられます。これを catchError を使用して防ぐことができます!これは非常にシンプルに動作します: エラーを入力として受け取り、別のObservableを返します。
より良く理解するために、前の例を変更してみましょう:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList: CarInfo[] = [];
constructor(private searchService: SearchService) {
this.form.controls['search'].valueChanges.pipe(
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
これにより、エラーが発生した場合(つまり、APIエラー)、それがユーザーに表示され、Subscriptionは閉じられません。これは、ユーザーが入力するたびにAPIコールを続けることができるため、検索バーが何も起こらなかったかのように動作することを意味します。
6. startWith
オペレーター
このオペレーターは、Hot Observables と特に相性が良いです。
サブスクリプション直後に特定の値を即座に発行します。
これを確認するために、前の例を(再)変更してみましょう:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList: CarInfo[] = [];
constructor(private searchService: SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
なぜこのわずかなコード行が重要なのでしょうか?
もし読者が注意していたならば、素晴らしい検索バーには少し問題があることに気づいたでしょう: コンポーネントが初期化されても、ユーザーは検索バーに何も入力しない限り車両のリストを見ることができません。
このコード行を使用することで、コンポーネントが初期化されるとすぐにWebサービス呼び出しが行われ、最初からフィルタリングされていない車両のリストを持つことができます。
7. debounceTime
オペレーター
これは私のお気に入りのオペレーターのもう1つです。公式ドキュメントには次のように説明されています:
出力間の指定された時間よりも短い時間を取る値は破棄されます。
これはどういう意味でしょうか?再び、検索バーの例を考えてみましょう。
もう1つの問題は、ユーザーが検索バーに入力するたびに、たとえ1文字だけでもAPIコールが行われることです。想像できるように、サーバーはそれに対して喜んではいません。同時にそれを行うすべての人を想像してみて
ください...(DDoS攻撃ではない場合、それにかなり似ています )。
debouceTime(n)
を使用することで、検索フィールドの値は n ミリ秒後にのみ発行されます。さらに、その間にユーザーが別の文字を入力した場合は、次の n ミリ秒が経過するまで値が発行されません。
これにより、サーバーへのAPIコールの数を大幅に減らすことができます。
違いを確認するために、前の例を(再)変更してみましょう:
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList: CarInfo[] = [];
constructor(private searchService: SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
debounceTime(300),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
8. delay
オペレーター
これは最もシンプルなオペレーターの1つです。その名前が示すように、値の発行を遅延させます。
典型的な使用例は、of オペレーターと一緒に使用してAPI呼び出しのシミュレーションを「リアルに」することです。
getVehicleInfo(vehiclePlate: string): Observable<CarInfo> {
return of({ model: "Ford Puma" , color: "Desert Island Blue", isCar: true })
.pipe(delay(500));
}
9. distinctUntilChanged
オペレーター
このオペレーターは、前回の発行値と異なる場合にのみ値を発行します。
これを説明するために、(もう一度)素晴らしい検索バーを修正して、このオペレーターがどのように役立つかを見てみましょう。(最後の変更、お約束します )
export class CarComponent {
form = new FormGroup({
search: new FormControls('')
});
carsList: CarInfo[] = [];
constructor(private searchService: SearchService) {
this.form.controls['search'].valueChanges.pipe(
startWith(''),
distinctUntilChanged(),
debounceTime(300),
switchMap(term => this.searchService.search(term)),
catchError(err => {
this.showErrorMessage(err);
return of([]);
})
)
.subscribe(list => this.carsList = list);
}
}
そして... ユーザーが検索バーに何かを入力しました。300msが経過したので、コンポーネントは車両のリストを更新します。その後、ユーザーが誤って1文字入力し、それを削除するために バックスペース を押しました。さらに300msが経過しますが、値が前回の発行値と同じであることを distinctUntilChanged
が認識するため、APIは呼び出されません。再び、不必要なAPIコールからサーバーを救いました!
では、このオペレーターはどのように変更を検知しているのでしょうか?
デフォルトでは、distinctUntilChanged
は ===
比較演算子を使用しますので、文字列には問題ありません。
ただし、カスタムの比較関数を作成し、オペレーターのパラメータとして渡すこともできます:
distinctUntilChanged((prev, curr) => prev.toLowerCase() === curr.toLowerCase())
10. filter
オペレーター
最後(でも最も重要ではない)に、ここで見る filter オペレーターについて説明します。map
オペレーターの場合と同様に、このオペレーターは基本的にはArrayの filter
関数と同じ役割を果たします。言い換えると:
指定された条件を満たす値のみを発行します。
例を通じてその動作を見てみましょう:
export class CarComponent {
vehicles = [
{ model: "Ford Puma" , color: "Desert Island Blue", isCar: true },
{ model: "Iveco S-WAY" , color: "Polar White", isCar: false},
{ model: "Fiat 500" , color: "Gelato White", isCar: true }
]
// 車両のみを返します。
getCarsList(): Observable<CarListItem[]> {
from(vehicles).pipe(
filter(vehicle => vehicle.isCar)
);
}
}
Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.
Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.
Momo: NGUYỄN ANH TUẤN - 0374226770
TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)
All rights reserved