import { FormGroup } from '@angular/forms' ; import { merge , Subject } from 'rxjs' ; import { Router } from '@angular/router' ; import { coerceArray , get , resolveParams } from './utils' ; import { auditTime , map , takeUntil } from 'rxjs/operators' ; import { BindQueryParamsOptions , QueryDefOptions , ResolveParamsOption , SyncDefsOptions } from './types' ; import { QueryParamDef } from './QueryParamDef' ; import set from 'lodash.set' ; export class BindQueryParamsManager < T = any > { private defs : QueryParamDef < T > [ ] ; private group ! : FormGroup ; private $destroy = new Subject ( ) ; private defsSynced : Record < keyof T , boolean > = { } as Record < keyof T , boolean > ; connect ( group ) { this . group = group ; this . onInit ( ) ; return this ; } constructor ( private router , defs , private options , private createOptions ? ) { this . defs = coerceArray ( defs ) . map ( ( def ) => new QueryParamDef ( def ) ) ; } onInit ( ) { this . handleInitialURLSync ( ) ; this . updateControl ( this . defs , { emitEvent : true } , ( def ) => ! ! ( def . syncInitialQueryParamValue ?? this . createOptions ?. syncInitialQueryParamValue ) ) ; const controls = this . defs . map ( ( def ) => { return this . group . get ( def . path ) ! . valueChanges . pipe ( map ( ( value ) => ( { def , value , } ) ) ) ; } ) ; let buffer = [ ] ; merge ( ... controls ) . pipe ( map ( ( result ) => buffer . push ( result ) ) , auditTime ( 0 ) , takeUntil ( this . $destroy ) ) . subscribe ( ( ) => { this . updateQueryParams ( resolveParams ( buffer ) ) ; buffer = [ ] ; } ) ; } destroy ( ) { this . $destroy . next ( ) ; } getDef ( queryKey ) { return this . defs . find ( ( def ) => def . queryKey === queryKey ) ; } parse ( queryParams ) { const result = { } ; for ( const [ key , value ] of Object . entries ( queryParams ) ) { const def = this . getDef ( key as keyof T ) ; if ( def ) { result [ key ] = def . parse ( value as string ) ; } } return result ; } syncAllDefs ( options = { emitEvent : true } ) { const allKeys = this . defs . map ( ( def ) => def . queryKey ) ; this . syncDefs ( allKeys , options ) ; } syncDefs ( queryKeys , options = { emitEvent : true } ) { const defs = [ ] ; coerceArray ( queryKeys ) . forEach ( ( key ) => { if ( ! this . defsSynced [ key ] ) { this . defsSynced [ key ] = true ; const def = this . getDef ( key as keyof T ) ; if ( def ) { defs . push ( def ) ; } } } ) ; if ( defs . length ) { this . updateControl ( defs , options ) ; } } paramExists ( queryKey ) { return this . search . has ( queryKey as string ) ; } someParamExists ( ) { return this . defs . some ( ( def ) => { return this . search . has ( def . queryKey ) ; } ) ; } get search ( ) { return new URLSearchParams ( this . options . windowRef . location . search ) ; } private handleInitialURLSync ( ) { const initialSyncDefs = [ ] ; for ( const def of this . defs ) { const syncInitialControlValue = def . syncInitialControlValue ?? this . createOptions ?. syncInitialControlValue ; if ( syncInitialControlValue && ! this . paramExists ( def . queryKey ) ) { initialSyncDefs . push ( { def , value : get ( this . group . value , def . path ) } ) ; } } if ( initialSyncDefs . length ) { this . updateQueryParams ( { ... Object . fromEntries ( this . search as any ) , ... resolveParams ( initialSyncDefs ) , } ) ; } } private updateQueryParams ( queryParams ) { this . router . navigate ( [ ] , { queryParams , queryParamsHandling : 'merge' , replaceUrl : true , } ) ; } private updateControl ( defs , options , updatePredicate = ( _ ) => true ) { const queryParams = new URLSearchParams ( this . options . windowRef . location . search ) ; let value = { } ; for ( const def of defs ) { if ( updatePredicate ( def ) ) { const { queryKey } = def ; const queryParamValue = queryParams . get ( queryKey ) ; if ( ! queryParamValue ) continue ; set ( value , def . path . split ( '.' ) , def . parse ( queryParamValue ) ) ; } } if ( Object . keys ( value ) . length ) { this . group . patchValue ( value , options ) ; } } }