Why routing from a form button broke my app

Stephen Cooper - Oct 14 '19 - - Dev Community

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, which after completing, users could enter their name and then have the app route to the high score table.

Page reloading 😖

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>
Enter fullscreen mode Exit fullscreen mode

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']);
    }
Enter fullscreen mode Exit fullscreen mode

On clicking the Save Score button the routing animation would begin but be immediately followed by a full page reload and then end up on the high score page. I thought I must have made a routing error but after many searches I realised that the same routing logic worked in other parts of the app but just not when the button was placed inside the form.

Once I added the form into my Google search I came across vast numbers of posts describing exactly my issue.

Alt Text

It all boils down to the submit behaviour 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, causing a refresh.

Missing 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 behaviour on all your forms, which in turn stops the page reloading.

However, I had the FormsModule imported but my page was still reloading. What was going on!?

Routing interference

After some more digging I discovered that the click handler was called before the submit action and if I commented out the router.navigate call the page did 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 part way through the routing navigation the page reloads. I found if I wrapped router.navigate in a setTimeout then the form submit method is called and Angular can prevent the default action. We can do better though.

The better solution 😃

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>       
Enter fullscreen mode Exit fullscreen mode
  1. Angular now prevents the default action whilst still calling router.navigate with no setTimeout required.
  2. Pressing the Enter key will now also submit the form and trigger the navigation to the high scores.

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 worth while for me. ☺️

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .