Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular @Output with callback

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.

like image 963
Benjamin M Avatar asked Sep 24 '26 12:09

Benjamin M


2 Answers

 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));
  }
}

I am not aware of such callback. At best you can do is do some @Input wiring.

In parent.component.html

<my-form (submitted)="formSubmit($event)" [disableButton]="disableButton"></my-form>

In parent.component.ts

disableButton: boolean = false;

formSubmit(myForm) {
 this.disableButton = true; --> disable it here as soon as form submitted.

 this.service.doSomething(event).pipe(
  tap(res => {
  console.log("service res", res);
  this.disableButton = false; // --> enable it here when form submission complete
   }
 ));

}

In child.component.ts

@Input() disableButton: boolean

In child.component.html

<button [disabled]="disableButton?'':null">Submit</button>

So one way is to implement on these lines.

like image 22
emkay Avatar answered Sep 27 '26 02:09

emkay