Reputation: 6864
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
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
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
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
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