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.
5 Réponses :
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"
Pourriez-vous fournir un exemple?
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 } )); }
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é.
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.
@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)); } }
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.
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(); } }
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 leParentComponent
(car le même formulaire peut être affiché plusieurs fois sur le même écran). Et puisFormComponent
l'injecte et observe le fluxisSubmitted $
. 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, puisform.isSubmitting = false
. Fait effectivement le même travail.