Calling registerOnChange and registerOnTouched methods after the component's ngOnDestroy hook has been invoked is a consequence of calling FormControlName.ngOnDestroy().
When a view is destroyed, my understanding from this function is that all of the onDestroy hooks from the destroyed view will be invoked. So, that's why the form directives of that view will have their onDestroy hook called too.
This is what happens on FormControlName.ngOnDestroy():
// `formDirective` refers to the parent `FormGroup` directive
this.formDirective.removeControl(this);
This is what happens on FormGroupDirective.removeControl():
:
removeControl(dir: FormControlName): void {
cleanUpControl(dir.control || null, dir, /* validateControlPresenceOnChange */ false);
removeListItem(this.directives, dir);
}
And finally, cleanUpControl is where the registerOnChange and registerOnTouched methods are invoked from:
// Reverts configuration performed by the `setUpControl` control function.
// Effectively disconnects form control with a given form directive.
// This function is typically invoked when corresponding form directive is being destroyed.
export function cleanUpControl(/* ... */) {
/* ... */
const noop = () => {
if (validateControlPresenceOnChange && (typeof ngDevMode === 'undefined' || ngDevMode)) {
_noControlError(dir);
}
};
if (dir.valueAccessor) {
dir.valueAccessor.registerOnChange(noop);
dir.valueAccessor.registerOnTouched(noop);
}
/* .... */
}
So, based on my understanding, it is part of the cleanup mechanism. It is indeed a bit inconvenient because those methods are called when the view is set up and also when the view is destroyed. I guess the solution is to first check for null/undefined values.
If you want to explore further, you can just use the debugger keyword:
registerOnChange(fn: any): void {
debugger;
this.con.log(`registerOnChange (${this.id})`);
}
and then open the DevTools in the StackBlitz project.