Skip to content

Commit 6195b8d

Browse files
committed
115491: Make better use of the type system
1 parent 1c93e4f commit 6195b8d

2 files changed

Lines changed: 76 additions & 21 deletions

File tree

src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,12 @@ import { Item } from '../../core/shared/item.model';
3737
import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
3838
import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
3939
import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service';
40+
import { ObservablesDictionary } from 'src/app/shared/utils/observables-dictionary';
4041

4142
/**
42-
* All observables that have to return their data before the form can be created and filled. Objects of
43-
* the {@link DataObservables} type can directly be used in a 'combineLatest' method to get their values
44-
* once all the observables have fired, and the resulting object will be of the {@link DataObjects} type.
45-
* This interface does not follow the usual convention of appending the dollar ($) symbol to variables
46-
* representing observables, as rxjs will map those names 1-to-1 to the resulting object when used in a
47-
* 'combineLatest' method.
43+
* All data that is required before the form can be created and filled.
4844
*/
49-
interface DataObservables {
50-
[key: string]: Observable<any>;
51-
52-
bitstream: Observable<Bitstream>,
53-
bitstreamFormat: Observable<BitstreamFormat>,
54-
bitstreamFormatOptions: Observable<PaginatedList<BitstreamFormat>>,
55-
bundle: Observable<Bundle>,
56-
primaryBitstream: Observable<Bitstream>,
57-
item: Observable<Item>,
58-
}
59-
6045
interface DataObjects {
61-
[key: string]: any;
62-
6346
bitstream: Bitstream,
6447
bitstreamFormat: BitstreamFormat,
6548
bitstreamFormatOptions: PaginatedList<BitstreamFormat>,
@@ -454,9 +437,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
454437

455438
/**
456439
* Create all the observables necessary to create and fill the bitstream form,
457-
* and collect them in a {@link DataObservables} object.
440+
* and collect them in a {@link ObservablesDictionary} object.
458441
*/
459-
protected getDataObservables(): DataObservables {
442+
protected getDataObservables(): ObservablesDictionary<DataObjects> {
460443
const bitstreamFormatOptionsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions);
461444

462445
const bitstream$ = this.bitstreamRD$.pipe(
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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

Comments
 (0)