Rohit
Rohit

Reputation: 3136

valueChanges observable seems to be subscribing to data before subscription

I'm creating a reactive form which is dynamically created. I get data from a service in the form of an observable (that comes from a Subject), and want to subscribe to the form's valueChanges after it's created. When I first set it up, I subscribed to each of the service's observables and to valueChanges, but I saw data from every step of the form's creation, which makes sense, as the two service observables could emit after.

So I refactored the setup to use Observable.zip, which as far as I can understand, makes the code linear, so the form should be setup before I get to my valueChanges subscription, but I still see every step of the form creation happening.

this.filterForm = this.formBuilder.group({
    'search': '',
    'types': this.formBuilder.group({}),
    'dates': this.formBuilder.array([]),
});

let formData = Observable.zip(
    this.eventService.getTypes(),
    this.eventService.getDates()
);
formData.forEach(data => {
    let types = data[0],
        dates = data[1];
    types.forEach(type => {
        this.types.push(type);
        (<FormGroup>(this.filterForm.get('types'))).addControl(type, new FormControl())
    });
    dates.forEach(date => {
        this.dates.push(date.format('dddd'));
        (<FormArray>(this.filterForm.get('dates'))).push(new FormControl(false))
    });
    this.filterForm.valueChanges.subscribe(data => console.log(data));
});

When I say I see the values as the form is building, I mean this:

{search: "", types: {…}, dates: Array(0)}
{search: "", types: {…}, dates: Array(1)}
{search: "", types: {…}, dates: Array(2)}
{search: "", types: {…}, dates: Array(3)}
{search: "", types: {…}, dates: Array(4)}
{search: "", types: {…}, dates: Array(5)}

There are a dozen entries before it as the types field builds.

Is this how this should be working? My goal is to subscribe AFTER the form is built, so I only get the values when the form is changed by the user.

Upvotes: 3

Views: 3805

Answers (2)

martin
martin

Reputation: 96891

I have a little different suggestion. In RxJS apart from the subscribe() method that subscribes to the chain there are also toPromise() and forEach() that internally subscribe as well. See https://github.com/ReactiveX/rxjs/blob/master/src/internal/Observable.ts#L223

And I think that's what's happening to you. The forEach method subscribes to the formData Observable and invokes its callback for every value emitted. But you're also using this.filterForm.valueChanges.subscribe inside the callback which means you're creating a new subscription every time formData emits a value.

I don't know what your code should do but I think you could restructure it to something like the following:

Observable.zip(
    this.eventService.getTypes(),
    this.eventService.getDates()
  )
  .do(data => {
    let types = data[0],
        dates = data[1];
    types.forEach(type => {
        this.types.push(type);
        (<FormGroup>(this.filterForm.get('types'))).addControl(type, new FormControl())
    });
    dates.forEach(date => {
        this.dates.push(date.format('dddd'));
        (<FormArray>(this.filterForm.get('dates'))).push(new FormControl(false))
    });
  })
  .switchMap(() => this.filterForm.valueChanges))
  .subscribe(data => console.log(data));

Upvotes: 1

Nikola Jankovic
Nikola Jankovic

Reputation: 987

Don't subscribe in the OnInit lifecycle hook. Create a method that handles the subscription and call that method after declaring the form model in OnInit.

More generally though you can skip a specific number of emissions using the skip() operator, or use filter/mapping to only process emits after a specific condition / structure has been met.

Upvotes: 0

Related Questions