I'm starting with Angular and trying to KISS as much as possible. I have a form in a template whose (ngSubmit)
component method calls an injected Service method that calls a synchronous back-end (not using HTTP, everything happens in memory in a domain layer) which can throw exceptions.
For example, if trying to start a game with a player's name that already exists, the domain layer will tell me with an AlreadyExistsError
on the method to start a new game.
The exception is caught just fine with try/catch as so:
onSubmit() {
try {
this.myService.methodCallingSyncBackEnd(this.model.name); // player name from form
this.submitted = true;
} catch (error) {
console.log(error);
if (error instanceof AlreadyExistsError)
this.errorMsg = error.message; // player already exists
else
this.errorMsg = 'Unknown error.';
}
}
The catch is working. However, the front-end seems to hang (the Submit button looks like it's not had a button-up - see the bold outline), and I also don't see the {{errorMsg}}
in the HTML template.
The console log for error shows:
Error: Player 'Marie Antoinette' already exists.
at Game.startGame (Game.ts:28)
at MyService.methodCallingSyncBackEnd (my-service.service.ts:20)
at PlayComponent.onSubmit (play.component.ts:27)
at PlayComponent_Template_form_ngSubmit_4_listener (play.component.html:5)
at executeListenerWithErrorHandling (core.mjs:14949)
at Object.wrapListenerIn_markDirtyAndPreventDefault [as next] (core.mjs:14987)
at Object.next (Subscriber.js:110)
at SafeSubscriber._next (Subscriber.js:60)
at SafeSubscriber.next (Subscriber.js:31)
at Subject.js:31
Game.ts
is the domain class that detects the error and throws the exception with a message. It's all in memory (nothing asynchronous).my-service.service.ts
is an Angular service injected into thePlayComponent
that has the form template defined in its.html
.
This looks like I need some kind of framework-friendly way to handle errors withfrom a call to an injected service, but I don't want to complicate my code with Observables/Observers if I don't have to, because (so far) I don't need to have much reactivity in my application.
Since there's no web back-end, and try/catch is such straightforward code (easier to understand), I would like to keep it simple. Is there something I can do to "unwind" properly the framework from the catch, or am I stuck using some more complicated code because of ngForm
/ngSubmit
?
CodePudding user response:
You can use catchError of rxjs
this.myService.methodCallingSyncBackEnd(this.model.name).pipe(catchError((error) => {
if (error instanceof AlreadyExistsError) {
this.errorMsg = error.message;
}
}),
);
https://www.learnrxjs.io/learn-rxjs/operators/error_handling/catch
CodePudding user response:
If you don't want to "complicate your code", this solution seems to be not bad. But if you want a "framework-friendly" solution, Angular essentially works with Observables/Observers.
The main advantage to use Observables/Observers is to respect a full asynchronous workflow in all your Angular components. If later a service method will required such delay (API call, processing...), everything will be clean for you.
On your service side, you can easily implement an Observable like this:
methodCallingBackend(name: string): Observable<void> {
return new Observable(observer => {
// ... your code here ...
// If an error occurred:
throw new AlreadyExistsError();
// If everything worked well:
observer.next();
});
}
On your component side, you can easily debounce input to provide user an immediate feedback:
this.form.get('name')?.valueChanges.pipe(debounceTime(400), distinctUntilChanged())
.subscribe((name) => {
this.myService.methodCallingBackend(name)
.pipe(take(1), catchError((error) => {
// ^ take(1): Unsubscribe automatically after response
if (error instanceof AlreadyExistsError) {
this.errorMsg = error.message;
}
}))
.subscribe(() => {
// Everything worked well
});
});
Like this, your service method will be triggered after each pause of 400ms in user input.