Is it possible to have a callback with @Output
?
I have a FormComponent
which checks validity, and disables the submit button while submitting. Now I'd like to reenable the submit button, when submitting has finished.
@Component({
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
...
</form>
`
})
class FormComponent {
form: FormGroup = ...;
isSubmitting = false;
@Output()
submitted = new EventEmitter<MyData>()
onSubmit() {
if(this.form.invalid || this.isSubmitting) {
return;
}
this.isSubmitting = true;
this.submitted.emit(this.form.value);
// Here I'd like to listen for the result of the parent component
// something like this...
// this.submitted.emit(...).subscribe(res => this.isSubmitting = false);
}
}
@Component({
template: `
<my-form (submitted)="onSubmitted($event)"></my-form>
`
})
class ParentComponent {
constructor(private service: MyService) { }
onSubmitted(event: MyData) {
this.service.doSomething(event).pipe(
tap(res => console.log("service res", res)
);
// basically I'd like to `return` this `Observable`,
// so the `FormComponent` can listen for the completion
}
}
I know, I could use an @Input()
within FormComponent
and do something like this:
@Input()
set submitted(val: boolean) {
this.isSubmitted = val;
}
But I'd like to know if there's a simpler / better solution, because isSubmitted
should be an internal property of FormComponent
, which should be managed by the component itself and not its parent.
onSubmit() {
this.isSubmitting = true;
this.submitHandler(this.form.value).subscribe(res => {
this.isSubmitting = false;
this.cdr.markForCheck();
});
}
In the above example code, the function onSubmit()
is not a stateless function and depends upon an external handler. Making the function itself unpredictable from a testing perspective. When this fails (if it does) you won't know where, why or how. The callback also ricks being executed after the component has been destroyed.
The problem of being disabled is an external state by the consumer of the component. So I would just make it an input binding (like the other answer here). This makes the component more dry and easier to test.
@Component({
template: `<form [formGroup]="form" (ngSubmit)="form.valid && enabled && onSubmit()"</form>`
})
class FormComponent {
form: FormGroup = ...;
@Input()
enabled = true;
@Output()
submitted = new EventEmitter<MyData>()
onSubmit() {
// I prefer to do my blocking in the template
this.submitted.emit(this.form.value);
}
}
The key difference here is that I use enabled$ | async
below to support OnPush
change detection. Since the state of the component changes asynchronously.
@Component({
template: `<my-form [enabled]="enabled$ | async" (submitted)="onSubmitted($event)"></my-form>`
})
class ParentComponent {
public enabled$: BehaviorSubject<boolean> = new BehaviorSubject(true);
constructor(private service: MyService) { }
onSubmitted(event: MyData) {
this.enabled$.next(false);
this.service.doSomething(event).pipe(
tap(res => this.enabled$.next(true)
).subscribe(res => console.log(res));
}
}