Adrian Moisa
Adrian Moisa

Reputation: 4363

How to trigger reactive form valueChanges subscription only on input blur and enter key in Angular 2

I'm working on a form that is supposed to update some ui parts only on input blur or enter key pressed. I'm a big fan of reactive forms and I'm trying to figure out what is the proper way. So far I haven't found too many answers for reactive forms. Most of them are focused on template driven forms. I got something like this so far:

Form component Dynamic form values change at runtime (No predictable filed names)

import { Component, Input, Output, EventEmitter, 
    ChangeDetectionStrategy } from '@angular/core'
import { FormBuilder, FormGroup, FormControl } from '@angular/forms'
import { Observable } from "rxjs/Observable"
import { DEBUG } from '../../../../config/config'
import * as Debug from 'debug'

// Interfaces
import { Foo } from '../../interfaces/foo'

// Services
import { FooService } from '../../services/foo.service'

// Debug
const debugOff = (...any) => { }, debug = Debug('app:FooCmp')

@Component({
    selector: 'foo-data-cmp',
    templateUrl: './foo.data.cmp.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
    styleUrls: ['./foo.data.cmp.css']
})
export class FooDataCmp {

    private _foo: Foo[] = null
    @Input('foo')
    set foo(foo: Foo[]) {
        this._foo = foo
        DEBUG.input && debug('Input foo:', this._foo)
    }
    get foo(): Foo[] {
        return this._foo
    }

    // Forms
    public fooForm: FormGroup
    public fooForm$: Observable<any>
    private updatedFoo: any[] // Updated form values

    // Subscriptions
    private subscriptions: any[] = []

    constructor(
        private formBuilder: FormBuilder,
        private fooService: FooService,
    ) {
        DEBUG.constr && debug('Construct FooDataCmp')
    }

    ngOnInit() {
        DEBUG.init && debug('Initialise FooDataCmp')

        // Form
        this.initFooForm()
        this.subcribeToFormChanges()
    }

    ngOnDestroy() {
        DEBUG.destroy && debug('Destroy FooDataCmp')
        this.subscriptions.forEach(sub => sub.unsubscribe())
    }

    private initFooForm() {
        DEBUG.cmp && debug('Initialise foo form')

        // Build the form
        this.fooForm = this.formBuilder.group(this._foo)

        // Prevent undefined error at first keystroke
        this.updatedFoo = this._foo
    }

    private subcribeToFormChanges() {
        this.fooForm$ = this.fooForm.valueChanges
        let sub = this.fooForm$.subscribe(fooForm => {
            DEBUG.cmp && debug('Form changes fooForm', fooForm)
            this.updatedFoo = fooForm
        })
        this.subscriptions.push(sub)
    }

    /**
     * <!> Important step in the data update process
     * Update state store. 
     * Other services / components are subscribed to the state store itself
     */
    public refreshAllFooRelatedData () {
        DEBUG.cmp && debug('Refresh all foo related data')
        DEBUG.cmp && debugOff('Updated foo', this.updatedFoo)
        this.fooService.refreshAllFooRelatedData(this.updatedFoo)
    }

    public refreshAllFooRelatedDataOnEnter (e: KeyboardEvent) {
        if (e.keyCode !== 13) {return}
        DEBUG.cmp && debug('Refresh all foo related data (on enter)')
        DEBUG.cmp && debugOff('Updated foo', this.updatedFoo)
        this.fooService.refreshAllFooRelatedData(this.updatedFoo)
    }

    public debugTemplate() {
        DEBUG.render && debug('Render FooDataCmp')
    }

}

Form template

<form [formGroup]="fooForm">
    <table class="list expiries">
            <td *ngFor="let value of foo; let i = index">
                <input type="text" [formControlName]="foo[i]" 
                    (blur)="refreshAllFooRelatedData()" 
                    (keydown)="refreshAllFooRelatedDataOnEnter($event)">
            </td>
    </table>
</form>

{{ debugTemplate() }}

Is this a decent solution or am I missing some trick? My question is if this is the proper way to do it. I've been expecting to find some form API options for handling this task. My approach was to build on top of the forms, but I would rather use the full form api if there is anything available for this task

Upvotes: 19

Views: 27388

Answers (3)

Mykola Riabchenko
Mykola Riabchenko

Reputation: 628

// component
public ctrl: FormControl = new FormControl(0, {updateOn: 'blur'});

//html
<input [formControl]="ctrl" (keydown.enter)="$event.target.blur()"/>```

Upvotes: 0

bryan60
bryan60

Reputation: 29345

if anyone in the future find this, there is actually something built into reactive forms for this, the updateOn option:

new FormControl('', {updateOn: 'blur'});

this will cause valueChanges and validation and whatnot to only trigger on blur

Upvotes: 65

Fan Cheung
Fan Cheung

Reputation: 11380

You can combine blur and enter event into a subject and merge it with the valuechange Observable, so that it only fires value when blur or enter. I haven't fully tested the code just an idea and I have come across similar scenario before

// component
this.updateField=new Subject()

this.form.get('yourfield').valueChanges.withLatestFrom(updateField).map(res=>res[0]).subscribe()

//html
<input (blur)="updateField($event)" (keydown.enter)="updateField($event)"/>

Upvotes: 8

Related Questions