This week, I was making a demo app for our internal company trade show at G-Research. The app mimicked our restricted simulation viewer to demo our team’s work without revealing sensitive information. To make it more interactive, we added a simulation game in which the user, upon completion, could enter their name and have the app route to the high score table.
However, I had a problem. Every time I tried to route from the game to the high score table, the whole app reloaded. This was incredibly frustrating and would be a terrible user experience!
My template had a form with a text box for the name and a button to save the score.
<form #scoreForm>
<input type="text" id="name" />
<button (click)="saveScore(scoreForm)" > Save Score </button>
</form>
In the component, I saved the score before triggering a router navigation to the high score page.
saveScore(scoreform) {
this.scoreService(this.score, scoreform.name.value);
this.router.navigate(['/highscore']);
}
Upon clicking the Save Score
button, the routing animation would begin, but it would be immediately followed by a full-page reload and end up on the high score page. I thought I must have made a routing error, but after many searches, I realized that the same routing logic worked in other parts of the app, just not when the button was placed inside the form.
Once I added the form into my Google search, I came across a vast number of posts describing my exact issue.
It all boils down to the submit behavior of HTML forms. By default, on submit, the form data is posted to the action
endpoint. If you have not set the action
attribute, the form data will be posted to the current page and cause a refresh.
FormsModule
? Not me…The most popular Angular answers on StackOverflow relate to a missing FormsModule
import. If you import this module, Angular will prevent the default form submit behavior on all your forms, which will stop the page from reloading.
However, even with the FormsModule
imported, my page was still reloading. What was going on?!
After some more digging, I discovered that the click handler was called before the submit action; so, if I commented out the router.navigate
call, the page would not reload. It also didn’t route, but clearly Angular was preventing the default form action in this case.
It appears that calling router.navigate
in a click handler stops Angular from preventing the default submit action. This is why the page reloads partway through the routing navigation. I found that if I wrapped router.navigate
in a setTimeout
, the form submit method would be called, and Angular could prevent the default action. We can do better though.
A better solution is to move the saveScore
handler to the submit action on the form. This simple change has two major benefits.
<form #scoreForm (submit)="saveScore(scoreForm)">
<input type="text" id="name" />
<button> Save Score </button>
</form>
router.navigate
, with no setTimeout
required.My takeaway is to use the form submit event instead of a button click handler. Hopefully, by recording the pitfalls of my initial approach, I will save someone/myself time in the future. If not, I have learned some more about forms by digging into this issue, so it has been worthwhile.