Reputation: 1999
I have a component with FormControl and subscription on it's value change:
@Component(...)
export class FooComponent implements OnInit, OnDestroy {
fooFormControl: FormControl;
...
ngOnInit() {
this.fooFormControl.valueChanges.subscribe(
() => {...},
() => {},
() => {
// never happens
},
);
}
ngOnDestroy() {
//happens
}
}
But when component is destroyed FormControl element isn't destroyed and onComplete callback never happens.
What is the right way to destroy FormControl element on component destroy?
Upvotes: 4
Views: 14542
Reputation: 21359
Using takeUntilDestroyed
export class FooComponent implements OnInit {
destroyRef: DestroyRef = inject(DestroyRef);
fooFormControl: FormControl; //assuming you do have a valid instance
ngOnInit() {
this.initOnFooControlChange();
}
private initOnFooControlChange(){
this.fooFormControl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(
() => {},
() => {},
() => {},
);
}
}
Upvotes: 0
Reputation: 5988
Here are two ideas that may help you. Not sure if they are the "right way" but it has worked pretty well for me so far. Besides this I am unsubscribing in ngOnDestroy like usual.
#1 Force completion by introducing your own subject via takeUntil
In TypeScript the valueChanges property is of type Observable. If you don't want to work around that and get to the Subject then you can introduce your own Subject by using the takeUntil operator. This will allow you to force the propagation of completion at a level above the valueChanges observable. It is important to not put the takeUntil operator before a switchmap or the switched to observable will continue and your subscription will not be canceled! For that reason I put it right before the subscribe operator.
Here is an example:
const stop = new Subject();
// simulate ngOnDestroy
window.setTimeout(() => {
stop.next();
stop.complete();
}, 3500);
Observable
.interval(1000)
.takeUntil(stop)
.subscribe(
() => { console.log('next'); },
() => { console.log('error'); },
() => { console.log('complete'); }
);
Here is a working example: https://rxviz.com/v/RoQNBnOM
#2 add the FormBuilder to your component's provider list
This is what I usually do. Actually I usually encapsulate my form inside a service that uses the FormBuilder but the effect is the same. Providing the service at this level will destroy and recreate the service each time the component is created. I started doing this because I kept having weird bugs caused by observable streams that were created off of valueChanges persisting past the lifespan of the component. They would get resubscribed to when the component was recreated and stuff like that.
Here is an example:
@Component({
selector: 'my-form',
templateUrl: './my-form.component.html',
styleUrls: ['./my-form.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [ FormBuilder ] // <-- THIS PART
})
export class MyFormComponent implements OnInit, OnDestroy {
}
Note that this doesn't actually propagate completion. But it does give you a fresh source subject each time.
Upvotes: 2
Reputation: 223318
FormControl
observables aren't supposed to be completed, although it is possible to do this manually if there is some logic that belongs to complete callback.
Since valueChanges
is event emitter and inherits from RxJS Subject
, it can be unsubscribed or completed:
ngOnDestroy() {
this.fooFormControl.valueChanges.complete();
// and/or
this.fooFormControl.valueChanges.unsubscribe();
}
It is possible that EventEmitter
won't be a subject in future. This may cause breaking changes, but currently it solely relies on RxJS.
Upvotes: 2
Reputation: 68685
You can't destroy your form control. Everything inside the component will be destroyed with it, but the events can holds in your app. So you can unsubscribe it's events.
@Component(...)
export class FooComponent implements OnInit, OnDestroy {
fooFormControl: FormControl;
var subscriber;
...
ngOnInit() {
this.subscriber = this.fooFormControl.valueChanges.subscribe(
() => {...},
() => {},
() => {
// never happens
},
);
}
ngOnDestroy() {
this.subscriber.unsubscribe();
}
}
Upvotes: 0