undefined
undefined

Reputation: 6864

Angular form control value changes doesn't notify

I have a basic form control which subscribed to the valueChanges observable.

@Component({
  selector: 'my-app',
  template: `
       <input [formControl]="control" />
       <div>{{ name$ | async | json }}</div>
   `
})
export class AppComponent implements OnInit {
  name$: Observable<string>;
  control;

  constructor(private builder: FormBuilder) {
    this.control = this.builder.control('');
  }

  ngOnInit() {
    this.name$ = this.control.valueChanges
      .pipe(
        map((name) => {
          console.log('fired', name)
          return name;
        })
      );


    this.control.setValue('2');
  }
}

When I call setValue the observer doesn't get any notification. It seems that the async pipe doesn't work. If I wrap the setValue in setTimeout, it's working. Why it behaves like that?

I also tried to add shareReplay() which also doesn't work.

Upvotes: 4

Views: 12297

Answers (4)

LucFreyermuth
LucFreyermuth

Reputation: 11

General solution if you want an observable of a from control value in Angular :

this.value$ = defer(() => {
  return this.control.valueChanges.pipe(startWith(this.control.value));
});

By using defer, you make sure that your startWith value will be the actual value of your form when your observable is subscribed. If you didn't use defer, your startWith value would just have been the value of the form when your observable is created (not subscribed).

You can put that logic in an utils function if you want to reuse it in your app :)

// utils.ts
export function observeControlValue$(formControl: AbstractControl): Observable<any> {
    return defer(() => {
        return formControl.valueChanges.pipe(startWith(formControl.value));
    });
}

// component.ts
this.value$ = observeControlValue$(this.control);

Upvotes: 1

Muhammed Albarmavi
Muhammed Albarmavi

Reputation: 24424

this one solution for the problem

this.control.setValue('2'); // I move any changes at the top for startWith operater 
this.name$ = this.control.valueChanges
  .pipe(
    startWith(this.control.value),
    map((name) => {
      console.log('fired', name)
      return name;
    })
  );

Another way to make it work without the async operator this mean I have subscribe to value changes before the control value get changes to '2' and I have catch all the changes later

this.control.valueChanges
  .pipe(
    map((name) => {
      console.log('fired', name)
      return name;
    })
  )
  .subscribe ( v => this.value = v )

template

{{value}}

Why the example in the question with async doesn't work , async operator will subscribe for the observable but at the time of async subscribe the current value of the control will be '2' so there no changes that why first value will not emit as change for the form control,another thing observable is lazy so when you do subscribe you got the value any changes before will not be seens

Upvotes: 3

Vincent GODIN
Vincent GODIN

Reputation: 537

If you want to use reactive form, you should use FormControl instead of FormBuilder.

Your control should be initialized like this :

export class AppComponent implements OnInit {
public control = new FormControl('');

Html is : <input type="text" [formControl]="control">

Then you don't need to have a Observable<string> just use

<div>{{ control.value | json }}</div>

See ReactiveForm doc for details : https://angular.io/guide/reactive-forms

Upvotes: -2

Hussein
Hussein

Reputation: 1153

The reason why it didn't work the view is not initialized yet (the html markup), when you call it from ngOnInit. You need to add it to ngAfterViewInit():

import AfterViewInit from '@angular/core';
export class AppComponent implements AfterViewInit{ 
   ngAfterViewInit() {
    this.name$ = this.control.valueChanges
      .pipe(
        map((name) => {
          console.log('fired', name)
          return name;
        })
      );


    this.control.setValue('2');
  }
}

you need to test it though, i didn't

Upvotes: 2

Related Questions