import { DOCUMENT } from '@angular/common' ; import { AfterViewInit , Directive , ElementRef , HostBinding , HostListener , Inject , Input , OnInit , Optional , Self , } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { ActionsSubject } from '@ngrx/store' ; import { Actions , FocusAction , MarkAsDirtyAction , MarkAsTouchedAction , SetValueAction , UnfocusAction } from '../actions' ; import { FormControlState , FormControlValueTypes } from '../state' ; import { selectViewAdapter } from '../view-adapter/util' ; import { FormViewAdapter , NGRX_FORM_VIEW_ADAPTER } from '../view-adapter/view-adapter' ; import { NgrxValueConverter , NgrxValueConverters } from './value-converter' ; export interface Document { activeElement : any ; } export enum NGRX_UPDATE_ON_TYPE { CHANGE = 'change' , BLUR = 'blur' , NEVER = 'never' } class ControlValueAccessorAdapter implements FormViewAdapter { constructor ( private valueAccessor ) { } setViewValue ( value ) { this . valueAccessor . writeValue ( value ) ; } setOnChangeCallback ( fn ) { this . valueAccessor . registerOnChange ( fn ) ; } setOnTouchedCallback ( fn ) { this . valueAccessor . registerOnTouched ( fn ) ; } setIsDisabled ( isDisabled ) { if ( this . valueAccessor . setDisabledState ) { this . valueAccessor . setDisabledState ( isDisabled ) ; } } } export type NgrxFormControlValueType < TStateValue > = TStateValue extends FormControlValueTypes ? TStateValue : never ; @ Directive ( { selector : ':not([ngrxFormsAction])[ngrxFormControlState]' , } ) export class NgrxFormControlDirective < TStateValue , TViewValue = TStateValue > implements AfterViewInit , OnInit { private isInitialized = false ; private focusTrackingIsEnabled = false ; @ Input ( ) set ngrxFormControlState ( newState ) { if ( ! newState ) { throw new Error ( 'The control state must not be undefined!' ) ; } const oldState = this . state ; this . state = newState ; if ( this . isInitialized ) { this . updateViewIfControlIdChanged ( newState , oldState ) ; this . updateViewIfValueChanged ( newState , oldState ) ; this . updateViewIfIsDisabledChanged ( newState , oldState ) ; this . updateViewIfIsFocusedChanged ( newState , oldState ) ; } } @ Input ( ) ngrxUpdateOn : NGRX_UPDATE_ON_TYPE = NGRX_UPDATE_ON_TYPE . CHANGE ; @ Input ( ) set ngrxEnableFocusTracking ( value ) { if ( value && ! this . dom ) { throw new Error ( 'focus tracking is only supported on the browser platform' ) ; } this . focusTrackingIsEnabled = value ; } @ Input ( ) ngrxValueConverter : NgrxValueConverter < TViewValue , TStateValue > = NgrxValueConverters . default < any > ( ) ; @ HostBinding ( 'attr.cdk-focus-region-start' ) get focusRegionStartAttr ( ) { return this . state && this . state . isFocused ? '' : null ; } state : FormControlState < NgrxFormControlValueType < TStateValue > > ; private viewAdapter : FormViewAdapter ; private viewValue : TViewValue ; private stateValue : TStateValue ; constructor ( private el , @ Optional ( ) @ Inject ( DOCUMENT ) private dom , @ Optional ( ) @ Inject ( ActionsSubject ) private actionsSubject , @ Self ( ) @ Optional ( ) @ Inject ( NGRX_FORM_VIEW_ADAPTER ) viewAdapters , @ Self ( ) @ Optional ( ) @ Inject ( NG_VALUE_ACCESSOR ) valueAccessors ) { viewAdapters = viewAdapters || [ ] ; valueAccessors = valueAccessors || [ ] ; if ( valueAccessors . length > 1 ) { throw new Error ( 'More than one custom control value accessor matches!' ) ; } this . viewAdapter = valueAccessors . length > 0 ? new ControlValueAccessorAdapter ( valueAccessors [ 0 ] ) : selectViewAdapter ( viewAdapters ) ; } updateViewIfControlIdChanged ( newState , oldState ) { if ( oldState && newState . id === oldState . id ) { return ; } this . stateValue = newState . value ; this . viewValue = this . ngrxValueConverter . convertStateToViewValue ( this . stateValue ) ; this . viewAdapter . setViewValue ( this . viewValue ) ; if ( this . viewAdapter . setIsDisabled ) { this . viewAdapter . setIsDisabled ( newState . isDisabled ) ; } } updateViewIfValueChanged ( newState , _ ) { if ( newState . value === this . stateValue ) { return ; } this . stateValue = newState . value ; this . viewValue = this . ngrxValueConverter . convertStateToViewValue ( newState . value ) ; this . viewAdapter . setViewValue ( this . viewValue ) ; } updateViewIfIsDisabledChanged ( newState , oldState ) { if ( ! this . viewAdapter . setIsDisabled ) { return ; } if ( oldState && newState . isDisabled === oldState . isDisabled ) { return ; } this . viewAdapter . setIsDisabled ( newState . isDisabled ) ; } updateViewIfIsFocusedChanged ( newState , oldState ) { if ( ! this . focusTrackingIsEnabled ) { return ; } if ( oldState && newState . isFocused === oldState . isFocused ) { return ; } if ( newState . isFocused ) { this . el . nativeElement . focus ( ) ; } else { this . el . nativeElement . blur ( ) ; } } protected dispatchAction ( action ) { if ( this . actionsSubject !== null ) { this . actionsSubject . next ( action ) ; } else { throw new Error ( 'ActionsSubject must be present in order to dispatch actions!' ) ; } } ngOnInit ( ) { if ( ! this . state ) { throw new Error ( 'The form state must not be undefined!' ) ; } this . isInitialized = true ; this . updateViewIfControlIdChanged ( this . state , undefined ) ; this . updateViewIfValueChanged ( this . state , undefined ) ; this . updateViewIfIsDisabledChanged ( this . state , undefined ) ; this . updateViewIfIsFocusedChanged ( this . state , undefined ) ; const dispatchMarkAsDirtyAction = ( ) => { if ( this . state . isPristine ) { this . dispatchAction ( new MarkAsDirtyAction ( this . state . id ) ) ; } } ; const dispatchSetValueAction = ( ) => { this . stateValue = this . ngrxValueConverter . convertViewToStateValue ( this . viewValue ) ; if ( this . stateValue !== this . state . value ) { this . dispatchAction ( new SetValueAction ( this . state . id , this . stateValue as NgrxFormControlValueType < TStateValue > ) ) ; dispatchMarkAsDirtyAction ( ) ; } } ; this . viewAdapter . setOnChangeCallback ( ( viewValue ) => { this . viewValue = viewValue ; if ( this . ngrxUpdateOn === NGRX_UPDATE_ON_TYPE . CHANGE ) { dispatchSetValueAction ( ) ; } } ) ; this . viewAdapter . setOnTouchedCallback ( ( ) => { if ( ! this . state . isTouched && this . ngrxUpdateOn !== NGRX_UPDATE_ON_TYPE . NEVER ) { this . dispatchAction ( new MarkAsTouchedAction ( this . state . id ) ) ; } if ( this . ngrxUpdateOn === NGRX_UPDATE_ON_TYPE . BLUR ) { dispatchSetValueAction ( ) ; } } ) ; } ngAfterViewInit ( ) { this . viewAdapter . setViewValue ( this . viewValue ) ; if ( this . viewAdapter . setIsDisabled ) { this . viewAdapter . setIsDisabled ( this . state . isDisabled ) ; } } @ HostListener ( 'focusin' ) @ HostListener ( 'focusout' ) onFocusChange ( ) { if ( ! this . focusTrackingIsEnabled ) { return ; } const isControlFocused = this . el . nativeElement === this . dom ! . activeElement ; if ( isControlFocused !== this . state . isFocused ) { this . dispatchAction ( isControlFocused ? new FocusAction ( this . state . id ) : new UnfocusAction ( this . state . id ) ) ; } } }