1
votes

Angular @Output avec rappel

Est-il possible d'avoir un rappel avec @Output ?

J'ai un FormComponent qui vérifie la validité et désactive le bouton d'envoi lors de la soumission. Maintenant, je voudrais réactiver le bouton d'envoi, une fois la soumission terminée.

@Input()
set submitted(val: boolean) {
  this.isSubmitted = val;
}
@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
  }
}

Je sais, je pourrais utiliser un @ Entrez () dans FormComponent et faites quelque chose comme ceci:

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

Mais j'aimerais savoir s'il y a un plus simple / meilleur solution, car isSubmitted doit être une propriété interne de FormComponent , qui doit être gérée par le composant lui-même et non par son parent.


3 commentaires

Je pense que c'est plus élégant de le faire dans un service. Vous pouvez injecter le service où vous le souhaitez. J'irais avec isSubmitted $: Subject dans un service.


Vous voulez dire une sorte de FormService ? Ensuite, il doit être fourni par le ParentComponent (car le même formulaire peut être affiché plusieurs fois sur le même écran). Et puis FormComponent l'injecte et observe le flux isSubmitted $ . Ouais, ça pourrait marcher aussi. Mais j'espérais quelque chose de plus simple ...


Une version plus courte consisterait à utiliser @ViewChild (FormComponent) form dans le parent, puis form.isSubmitting = false . Fait effectivement le même travail.


5 Réponses :


0
votes

Vous pouvez définir isSubmiting dans le composant Parent et le fournir comme entrée du composant enfant. Dans votre cas, la solution sera, initialisez isSubmitting dans le composant parent et définissez-le sur false. Ensuite, quand à partir du composant enfant u émettre une valeur sur la première ligne du jeu de rappel parent, il est soumis à true. Une fois que la logique pour onSubmitted est terminée, vous pouvez redéfinir isSubmitting sur false. Tout ce que vous avez à faire dans le composant enfant est de recevoir isSubmitted comme entrée et de le définir sur le type d'entrée submit as bind attr [disabled] = "isSubmitting"


1 commentaires

Pourriez-vous fournir un exemple?



1
votes

Je n'ai pas connaissance d'un tel rappel. Au mieux, vous pouvez faire du câblage @Input.

Dans parent.component.html

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

}


2 commentaires

Oui, c'est la seule solution que j'ai déjà indiquée dans la question, en utilisant @Input () set soumis . Mais vous devez également utiliser this.cdr.markForCheck () après this.disableButton = false lorsque vous utilisez OnPush .


Oops! J'ai manqué ça. Eh bien oui, c'est une solution. Je ne pense pas qu'il y ait une évasion du composant parent dans aucune autre solution à laquelle je pourrais penser. Comme une solution peut l'être, en utilisant un sujet et avec les données du formulaire, envoyez le sujet également au parent. Lorsque la soumission est terminée, activez le bouton désactivé via le sujet reçu. Une autre solution est de voir l'approche de l'enfant à laquelle vous avez vous-même pensé.



0
votes

J'ai trouvé un autre moyen: en passant une fonction de gestionnaire comme @Input:

@Component({
  template: `
    <my-form [submitHandler]="submitHandler"></my-form>
  `
})
class ParentComponent {
  constructor(private service: MyService) { }

  submitHandler = (formValue: MyData): Observable<any> => {
    return this.service.doSomething(event);
  };
}
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();
    });
  }
}

C'est facile à utiliser et fonctionne assez bien. La seule "mauvaise" chose est que j'ai l'impression d'abuser de @Input pour quelque chose qui n'a pas été conçu.


0 commentaires

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

2 commentaires

Dans mon implémentation actuelle, j'utiliserais this.submitHandler (this.form.value) .pipe (takeUntil (this.dest‌ royed $), finalize (thi‌ s.submitting = false; this.cdr.markForCheck ( );}). subscribe (); . (C'est pourquoi j'ai mis le commentaire "n'oubliez pas de vous désinscrire lors de la destruction" ;-) ). Bien qu'il ne soit pas exécuté après la destruction. Je ne comprends pas votre point de vue avec les tests. Le FormComponent a un contrat spécial, et il ne doit fonctionner que si ce contrat est respecté. Donc, si vous ne fournissez pas un submitHandler ou un submitHandler qui ne se termine jamais, vous n'avez pas rempli le contrat du composant. Ou ai-je raté quelque chose?


@BenjaminM ce que je veux dire par test, c'est que l'état interne de la fonction est contrôlé par un rappel externe. Ainsi, bien que les tests unitaires puissent réussir, le code peut toujours échouer dans la nature car la fonction n'est pas pure.



0
votes

Et une autre solution utilisant une variable de modèle:

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


0 commentaires