A custom RxJS operator packages application-specific observable behavior into a reusable function. Like built-in operators, it is placed in pipe() and transforms a source observable; unlike them, it can carry business rules that would otherwise be repeated across streams. section 5.4 section 5.2
The basic shape is a function that accepts configuration and returns a function over an Observable. A simple version can wrap source.pipe(...) directly; a more concise typed version returns pipe(...) as a MonoTypeOperatorFunction. Parameters make one operator reusable across observables with different settings. section 5.4.3 section 5.4.3
Use a custom operator when it should:
- Apply the same transformation or side effect consistently, such as logging every emitted value with an optional label. section 5.4.3
- Encapsulate a business decision before a stream proceeds, rather than scattering that decision across call sites. section 5.4 section 5.4.3
- Compose existing operators such as withLatestFrom, filter, map, and tap into a named operation. section 5.4.3
Gate HTTP requests by permission
A hasPermissions operator receives required permissions, reads permission state through PermissionsService, pairs each source value with the latest permission result, filters denied values, and maps allowed values back to the original value. An Angular HTTP interceptor can then apply it to employee requests, preventing unauthorized calls from proceeding. section 5.4.3 section 5.4.3
Custom operators can inject Angular dependencies directly, avoiding an explicit service parameter. Call inject() only within an injection context; otherwise, pass or arrange dependencies differently. This makes DI-backed operators practical, but ties their execution to Angular’s DI rules. section 3.2.3 section 5.4.3
Listing 5.13 Custom RxJS operator that uses dependency injection
export function hasPermissions<T>(
permissions: Permissions[], #1
permissionsService = inject(PermissionsService), #2
): MonoTypeOperatorFunction<T> {
return pipe(
withLatestFrom(permissionsService.hasPermissions(permissions)), #3
filter(([, hasPermissions]) => hasPermissions), #4
map(([value]) => value) #5
);
}
Inspect a DI-backed operator that combines permission state with a source stream, filters unauthorized emissions, and preserves the source value. section 5.4.3
