@@ -11,6 +11,7 @@ import {
11
11
scheduled ,
12
12
asapScheduler ,
13
13
EMPTY ,
14
+ ObservedValueOf ,
14
15
} from 'rxjs' ;
15
16
import {
16
17
takeUntil ,
@@ -225,44 +226,53 @@ export class ComponentStore<T extends object> implements OnDestroy {
225
226
projector : ( s : T ) => Result ,
226
227
config ?: SelectConfig
227
228
) : Observable < Result > ;
229
+ select < SelectorsObject extends Record < string , Observable < unknown > > > (
230
+ selectorsObject : SelectorsObject ,
231
+ config ?: SelectConfig
232
+ ) : Observable < {
233
+ [ K in keyof SelectorsObject ] : ObservedValueOf < SelectorsObject [ K ] > ;
234
+ } > ;
228
235
select < Selectors extends Observable < unknown > [ ] , Result > (
229
- ...args : [ ...selectors : Selectors , projector : Projector < Selectors , Result > ]
236
+ ...selectorsWithProjector : [
237
+ ...selectors : Selectors ,
238
+ projector : Projector < Selectors , Result >
239
+ ]
230
240
) : Observable < Result > ;
231
241
select < Selectors extends Observable < unknown > [ ] , Result > (
232
- ...args : [
242
+ ...selectorsWithProjectorAndConfig : [
233
243
...selectors : Selectors ,
234
244
projector : Projector < Selectors , Result > ,
235
245
config : SelectConfig
236
246
]
237
247
) : Observable < Result > ;
238
248
select <
239
- Selectors extends Array < Observable < unknown > | SelectConfig | ProjectorFn > ,
249
+ Selectors extends Array <
250
+ Observable < unknown > | SelectConfig | ProjectorFn | SelectorsObject
251
+ > ,
240
252
Result ,
241
- ProjectorFn extends ( ...a : unknown [ ] ) => Result
253
+ ProjectorFn extends ( ...a : unknown [ ] ) => Result ,
254
+ SelectorsObject extends Record < string , Observable < unknown > >
242
255
> ( ...args : Selectors ) : Observable < Result > {
243
- const { observables, projector, config } = processSelectorArgs <
244
- Selectors ,
245
- Result ,
246
- ProjectorFn
247
- > ( args ) ;
248
-
249
- let observable$ : Observable < Result > ;
250
- // If there are no Observables to combine, then we'll just map the value.
251
- if ( observables . length === 0 ) {
252
- observable$ = this . stateSubject$ . pipe (
253
- config . debounce ? debounceSync ( ) : ( source$ ) => source$ ,
254
- map ( ( state ) => projector ( state ) )
255
- ) ;
256
- } else {
257
- // If there are multiple arguments, then we're aggregating selectors, so we need
258
- // to take the combineLatest of them before calling the map function.
259
- observable$ = combineLatest ( observables ) . pipe (
260
- config . debounce ? debounceSync ( ) : ( source$ ) => source$ ,
261
- map ( ( projectorArgs ) => projector ( ...projectorArgs ) )
256
+ const { observablesOrSelectorsObject, projector, config } =
257
+ processSelectorArgs < Selectors , Result , ProjectorFn , SelectorsObject > (
258
+ args
262
259
) ;
263
- }
264
260
265
- return observable$ . pipe (
261
+ const source$ = hasProjectFnOnly ( observablesOrSelectorsObject , projector )
262
+ ? this . stateSubject$
263
+ : combineLatest ( observablesOrSelectorsObject as any ) ;
264
+
265
+ return source$ . pipe (
266
+ config . debounce ? debounceSync ( ) : noopOperator ( ) ,
267
+ ( projector
268
+ ? map ( ( projectorArgs ) =>
269
+ // projectorArgs could be an Array in case where the entire state is an Array, so adding this check
270
+ observablesOrSelectorsObject . length > 0 &&
271
+ Array . isArray ( projectorArgs )
272
+ ? projector ( ...projectorArgs )
273
+ : projector ( projectorArgs )
274
+ )
275
+ : noopOperator ( ) ) as ( ) => Observable < Result > ,
266
276
distinctUntilChanged ( ) ,
267
277
shareReplay ( {
268
278
refCount : true ,
@@ -357,36 +367,70 @@ export class ComponentStore<T extends object> implements OnDestroy {
357
367
}
358
368
359
369
function processSelectorArgs <
360
- Selectors extends Array < Observable < unknown > | SelectConfig | ProjectorFn > ,
370
+ Selectors extends Array <
371
+ Observable < unknown > | SelectConfig | ProjectorFn | SelectorsObject
372
+ > ,
361
373
Result ,
362
- ProjectorFn extends ( ...a : unknown [ ] ) => Result
374
+ ProjectorFn extends ( ...a : unknown [ ] ) => Result ,
375
+ SelectorsObject extends Record < string , Observable < unknown > >
363
376
> (
364
377
args : Selectors
365
- ) : {
366
- observables : Observable < unknown > [ ] ;
367
- projector : ProjectorFn ;
368
- config : Required < SelectConfig > ;
369
- } {
378
+ ) :
379
+ | {
380
+ observablesOrSelectorsObject : Observable < unknown > [ ] ;
381
+ projector : ProjectorFn ;
382
+ config : Required < SelectConfig > ;
383
+ }
384
+ | {
385
+ observablesOrSelectorsObject : SelectorsObject ;
386
+ projector : undefined ;
387
+ config : Required < SelectConfig > ;
388
+ } {
370
389
const selectorArgs = Array . from ( args ) ;
371
390
// Assign default values.
372
391
let config : Required < SelectConfig > = { debounce : false } ;
373
- let projector : ProjectorFn ;
374
- // Last argument is either projector or config
375
- const projectorOrConfig = selectorArgs . pop ( ) as ProjectorFn | SelectConfig ;
376
-
377
- if ( typeof projectorOrConfig !== 'function' ) {
378
- // We got the config as the last argument, replace any default values with it.
379
- config = { ...config , ...projectorOrConfig } ;
380
- // Pop the next args, which would be the projector fn.
381
- projector = selectorArgs . pop ( ) as ProjectorFn ;
382
- } else {
383
- projector = projectorOrConfig ;
392
+
393
+ // Last argument is either config or projector or selectorsObject
394
+ if ( isSelectConfig ( selectorArgs [ selectorArgs . length - 1 ] ) ) {
395
+ config = { ...config , ...selectorArgs . pop ( ) } ;
384
396
}
385
- // The Observables to combine, if there are any.
397
+
398
+ // At this point selectorArgs is either projector, selectors with projector or selectorsObject
399
+ if ( selectorArgs . length === 1 && typeof selectorArgs [ 0 ] !== 'function' ) {
400
+ // this is a selectorsObject
401
+ return {
402
+ observablesOrSelectorsObject : selectorArgs [ 0 ] as SelectorsObject ,
403
+ projector : undefined ,
404
+ config,
405
+ } ;
406
+ }
407
+
408
+ const projector = selectorArgs . pop ( ) as ProjectorFn ;
409
+
410
+ // The Observables to combine, if there are any left.
386
411
const observables = selectorArgs as Observable < unknown > [ ] ;
387
412
return {
388
- observables,
413
+ observablesOrSelectorsObject : observables ,
389
414
projector,
390
415
config,
391
416
} ;
392
417
}
418
+
419
+ function isSelectConfig ( arg : SelectConfig | unknown ) : arg is SelectConfig {
420
+ return typeof ( arg as SelectConfig ) . debounce !== 'undefined' ;
421
+ }
422
+
423
+ function hasProjectFnOnly (
424
+ observablesOrSelectorsObject : unknown [ ] | Record < string , unknown > ,
425
+ projector : unknown
426
+ ) {
427
+ return (
428
+ Array . isArray ( observablesOrSelectorsObject ) &&
429
+ observablesOrSelectorsObject . length === 0 &&
430
+ projector
431
+ ) ;
432
+ }
433
+
434
+ function noopOperator ( ) : < T > ( source$ : Observable < T > ) => typeof source$ {
435
+ return ( source$ ) => source$ ;
436
+ }
0 commit comments