|
| 1 | +import { Observable } from 'rxjs'; |
| 2 | + |
| 3 | +/** |
| 4 | + * Utility type that allows stricter type checking when creating a method with output that is intended to be used |
| 5 | + * in a 'combineLatest' or similar call. |
| 6 | + */ |
| 7 | +export type ObservablesDictionary<T extends { [key: string]: any }> = { |
| 8 | + [key in keyof T]: Observable<T[key]> |
| 9 | +}; |
| 10 | + |
| 11 | +/* |
| 12 | + How to use an ObservablesDictionary<T>: |
| 13 | +
|
| 14 | + Suppose that you require multiple observables to fire before you can start with a task such as creating a form. The |
| 15 | + usual way to implement this is to create all the necessary observables, combine them in an array, pass the array as |
| 16 | + argument in a 'combineLatest' call, and subscribe to the resulting observable to handle the result. |
| 17 | +
|
| 18 | + Having to deconstruct the array into its components can be tedious and error-prone. RxJS supports dictionaries of |
| 19 | + observables as input argument in 'combineLatest', so it would be nice to be able to use this while maximally making |
| 20 | + use of TypeScript's type safety. That is where the ObservablesDictionary type comes in. |
| 21 | +
|
| 22 | + You start by defining the interface that should be the output of the 'combineLatest' method. |
| 23 | + e.g.: |
| 24 | +
|
| 25 | + interface MyData { |
| 26 | + collection: Collection; |
| 27 | + bitstreams: PaginatedList<Bitstream>; |
| 28 | + title: string; |
| 29 | + } |
| 30 | +
|
| 31 | + Now the input for the 'combineLatest' should be of type ObservablesDictionary<MyData>. |
| 32 | + In essence ObservablesDictionary<T> creates a copy of the defined interface T, while making observables of all T's |
| 33 | + fields. ObservablesDictionary<T> also applies the additional constraint that all the keys of T must be strings, which |
| 34 | + is required for objects used in 'combineLatest'. |
| 35 | +
|
| 36 | + ObservablesDictionary<MyData> is equivalent to the following: |
| 37 | +
|
| 38 | + interface ObservablesDictionaryMyData { |
| 39 | + collection: Observable<Collection>; |
| 40 | + bitstreams: Observable<PaginatedList<Bitstream>>; |
| 41 | + title: Observable<string>; |
| 42 | + } |
| 43 | +
|
| 44 | + This does not follow the convention of appending fieldNames of observables with the dollar sign ($). This is because |
| 45 | + RxJS maps the input names one-to-one to the output names, so they must be exactly the same. |
| 46 | +
|
| 47 | +
|
| 48 | + By using these types it becomes much easier to separate the process into multiple parts while maximally making use of |
| 49 | + the type system: The first function creates all the necessary observables and returns an object of type |
| 50 | + ObservablesDictionary<MyData>. The second function takes as argument an object of type MyData and performs whatever |
| 51 | + action you want to with the retrieved data. The final function then simply handles the necessary plumbing by calling |
| 52 | + the first method, placing the result as argument in a 'combineLatest' method, and in the subscription simply passing |
| 53 | + the result through to the second function. |
| 54 | +
|
| 55 | +
|
| 56 | +
|
| 57 | + An example of this type in action can be found in the edit-bitstream-page component (as of writing this explainer). |
| 58 | + The edit-bitstream-page has the following interface with that contains the required data: |
| 59 | +
|
| 60 | + interface DataObjects { |
| 61 | + bitstream: Bitstream, |
| 62 | + bitstreamFormat: BitstreamFormat, |
| 63 | + bitstreamFormatOptions: PaginatedList<BitstreamFormat>, |
| 64 | + bundle: Bundle, |
| 65 | + primaryBitstream: Bitstream, |
| 66 | + item: Item, |
| 67 | + } |
| 68 | +
|
| 69 | + The getDataObservables provides all the observables in an ObservablesDictionary<DataObjects> object |
| 70 | + which is used in the ngOnInit method to retrieve all the data necessary to create the edit form. |
| 71 | +*/ |
| 72 | + |
0 commit comments