0
votes

I am trying to capture user inputs and perform some business logic depending upon the inputs and bind the resulting data back to the form element. I hook into form valuechanges observable and perform my custom logic and bind the result to template.

I am trying to avoid subscribing from component and do it from template using async pipe as shown below. But if I am not subscribing from the component, logic is not triggering.

From my understanding since async pipe will subscribe to the observable, from component the pipe operation logic should work fine but is is not getting called unless I do another subscribe as shown below, can any one help me why it is not triggering the pipe operator logic since I already subscribed using async pipe in the template, please ? Thanks.

I tried moving the logic to ngAfterViewChecked hook still not working, if I subscribe from component it is working but I want to avoid multiple subscription.

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Observable } from 'rxjs';
import { tap, startWith } from 'rxjs/operators';

@Component({
    selector: 'app-check-value-changes',
    templateUrl: './check-value-changes.component.html',
    styleUrls: ['./check-value-changes.component.scss']
})
export class CheckValueChangesComponent implements AfterViewInit {
    @ViewChild('form') form: NgForm;
    valueChanges$: Observable<any>;

    ngAfterViewInit() {
        this.valueChanges$ = this.form.valueChanges;
        // Not Working
        this.valueChanges$.pipe(
            tap((c) => console.log(`Async pipe Subscribe ? - ${JSON.stringify(c)}`))
            // other business logic here
        );

        // Working fine
        this.valueChanges$.pipe(
            tap((c) => console.log(`Explicit Subscribe - ${JSON.stringify(c)}`))
            // other business logic here
        ).subscribe();
    }
}
<span *ngIf="valueChanges$ | async as value">
    {{ value | json }}
</span>
<form #form="ngForm">
    <input type="text" name="txtName" ngModel>
</form>
1
The template subscribes to the valueChanges$ observable. It doesn't subscribe to the observable created in ngAfterViewInit (by this.valueChanges$.pipe(...)).JB Nizet
Thanks for the update, I don't think pipe will create another observable and its the same single observable for both template and component I believe.Karthikeyan
Well, that's where you're wrong.JB Nizet

1 Answers

1
votes

Turns out to be a simple fix, when I combined the observable initialization and pipe operation statements together in a single line everything works fine.

In my original code even though I did my pipe operation in valueChanges$ observable I didn't mutate the original observable and eventually created another observable which of-course needs another subscription to emit the values.

Fixed component code :

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { tap } from 'rxjs/operators';

@Component({
    selector: 'app-check-value-changes',
    templateUrl: './check-value-changes.component.html',
    styleUrls: ['./check-value-changes.component.scss']
})
export class CheckValueChangesComponent implements AfterViewInit {
    @ViewChild('form') form: NgForm;
    valueChanges$: Observable<any>;

    ngAfterViewInit() {
        this.valueChanges$ = this.form.valueChanges.pipe(
            tap((c) => console.log(`Async pipe Subscribe ? - ${JSON.stringify(c)}`))
        );
    }
}

Dev Tools