Угловой выходной с обратным вызовом - PullRequest
0 голосов
/ 17 июня 2019

Возможно ли иметь обратный вызов с @Output?

У меня есть FormComponent, который проверяет действительность и отключает кнопку отправки при отправке.Теперь я хотел бы снова включить кнопку отправки, когда отправка завершится.

@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
  }
}

Я знаю, я мог бы использовать @Input() в FormComponent и сделать что-то вроде этого:

@Input()
set submitted(val: boolean) {
  this.isSubmitted = val;
}

Но я хотел бы знать, есть ли более простое / лучшее решение, потому что isSubmitted должно быть внутренним свойством FormComponent, которое должно управляться самим компонентом, а не его родителем.

Ответы [ 5 ]

1 голос
/ 17 июня 2019
 onSubmit() {
    this.isSubmitting = true;
    this.submitHandler(this.form.value).subscribe(res => {
      this.isSubmitting = false;
      this.cdr.markForCheck();
    });
  }

В приведенном выше примере кода функция onSubmit() не является функцией без сохранения состояния и зависит от внешнего обработчика . Создание самой функции непредсказуемой с точки зрения тестирования. Когда это не удастся (если это произойдет), вы не будете знать, где, почему или как. Функция обратного вызова также выполняется после , компонент был уничтожен .

Проблема отключения является внешним состоянием потребителя компонента. Так что я бы просто сделал привязку ввода (как и другой ответ здесь). Это делает компонент более сухим и его легче тестировать.

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

Ключевым отличием здесь является то, что я использую enabled$ | async ниже для поддержки OnPush обнаружения изменений. Поскольку состояние компонента изменяется асинхронно.

@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));
  }
}
1 голос
/ 17 июня 2019

Я не знаю о таком обратном вызове.В лучшем случае вы можете сделать это при некоторой проводке @Input.

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

}

В child.component.ts

@Input() disableButton: boolean

В child.component.html

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

Таким образом, одним из способов является реализация этих строк.

0 голосов
/ 20 июня 2019

И еще одно решение с использованием переменной шаблона:

@Component({
  template: `
    <my-form (submitted)="onSubmit($event, form)" #form></my-form>
  `
})
class ParentComponent {
  constructor(private service: MyService) { }

  onSubmit(event: MyData, form: FormComponent) {
    // don't forget to unsubscribe
    this.service.doSomething(event).pipe(
      finalize(() => {
        form.setSubmitting(false);
      })
    ).subscribe();
  }
}
class FormComponent {
  form: FormGroup = ...;

  isSubmitting = false;

  @Output()
  submitted = new EventEmitter<MyData>()

  constructor(private cdr: ChangeDetectorRef) { }

  setSubmitting(val: boolean) {
    this.isSubmitting = val;
    this.cdr.markForCheck();
  }

  onSubmit() {
    if (!this.form.valid || this.isSubmitting) {
      return;
    }

    this.isSubmitting = true;

    this.submitted.emit(this.form.value);
  }
}
0 голосов
/ 17 июня 2019

Я нашел другой способ: передавая функцию-обработчик как @Input:

class FormComponent {
  form: FormGroup = ...;

  isSubmitting = false;

  @Input()
  submitHandler: (value: MyData) => Observable<any>;

  constructor(private cdr: ChangeDetectorRef) { }

  onSubmit() {
    if (!this.form.valid || this.isSubmitting) {
      return;
    }

    this.isSubmitting = true;

    // don't forget to unsubscribe on destroy
    this.submitHandler(this.form.value).subscribe(res => {
      this.isSubmitting = false;
      this.cdr.markForCheck();
    });
  }
}
@Component({
  template: `
    <my-form [submitHandler]="submitHandler"></my-form>
  `
})
class ParentComponent {
  constructor(private service: MyService) { }

  submitHandler = (formValue: MyData): Observable<any> => {
    return this.service.doSomething(event);
  };
}

Это просто в использовании и работает довольно хорошо.Единственная «плохая» вещь в том, что мне кажется, что я злоупотребляю @Input за то, что не было разработано.

0 голосов
/ 17 июня 2019

Вы можете установить isSubmiting в родительском компоненте и предоставить его как вход для дочернего компонента. В вашем случае решение будет, инициализируйте isSubmitting в родительском компоненте и установите для него значение false. Затем, когда из дочернего компонента вы передаете значение в первой строке родительского обратного вызова, устанавливается isSubmitting в true. Однажды, когда логика для onSubmitted сделана, вы можете снова установить isSubmitting в false. Все, что вам нужно сделать в дочернем компоненте, это получить isSubmitted в качестве входных данных и установить для него тип ввода submit как bind attr [disabled] = "isSubmitting"

...