Angular: building an account activation countdown timer.
One of the approaches generally used to validate a user’s email address during signup is to send a 6-digit code to this address. He is then asked to provide this code within a relatively short time in minutes. The problem we want to solve in this article is to implement such a system using Angular for the front side.
Assumptions
1- We will focus on the front-end part to make reading digestible assuming that your Spring, Jango, Express, etc backend is functional.
2- We will also assume that for a succesful signup operation, your backend replies with a JSON object looking like this :
{
"account_creation_date" : "2024-03-13T12:08:38.0295378",
"other_key" : "other_value"
}
3- You have Angular 2+ intalled on your system.
Our strategy
When the user submits their information to create an account, the request is sent to the backend authentication service API. This API creates the user in the database with the account marked as deactivated. The backend then sends an email to the user, containing the activation code, valid for 10 minutes in our scenario. Once the process is completed, the API responds by providing an object in the form described above. Subsequently, the front-end application retrieves the creation date of the account which is provided in the response, adds the required 10 minutes to it, and stores this in localStorage. A service calculates the remaining time in real-time, and returns an observable. Finally the component subscribes to this observable and displays the countdown. The advantage of this approach is that even if the page is reloaded, the countdown remains accurate and continues to decrement.
What will we create ?
We will create an angular application that has 2 views : a signup page, and an account activation page. We will create a countdown service that will be injected in the component. And we will also create a custom pipeline in order to format the time to the user.
In summary :
- signupComponent
- AccountActivationComponent
- countDownService
- formatTimePipeline
… Let’s go !!
1- Create an Angular project
Lets start by creating our angular projet with the following command line :
ng new accountActivationCountDown
This command creates a minimal Angular project, containing the necessary dependencies, and basic source code to get started. When the command completes, a new folder named accountActivationCountDown is created. If VsCode is installed, run the following command to open the project.
code accountActivationCountDown
Now move your terminal into the root of your project and run the following command to start your application.
ng serve
2- Create empty components
You can do it manually if you want to go deep, but for this article, we will use the following angular command :
ng generate component component/signup & ng generate component component/activateAccount
or the shortcut :
ng g c component/signup & ng g c component/activateAccount
When these commands complete, a new folder named component is created, containing our two components separeted in two folders.
Replace all the content of app.component.html file with the following and you are done for this step.
<div>
<app-signup></app-signup>
<router-outlet></router-outlet>
</div>
NB : Note the presence of router-outlet
, which enables the Angular engine to display components based on the selected route. Also, observe the presence of app-signup
, which corresponds to the selector of our signup component.
3- Create the authentication service
The following command will help us start quickly:
ng g s service/authService
Now, open the newly created service and write the following code:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthServiceService {
//replace with your own apiUrl
readonly apiUrl = `http://localhost:8088/api/v1/auth/signup`;
constructor(private http: HttpClient) { }
signup(firstname:string, lastname: string, email : string, password : string): Observable<any> {
return this.http.post(this.apiUrl, {firstname, lastname, email, password});
}
}
To use HttpClient
, it is necessary to add HttpClientModule
to the imports
array in the app.module.ts
file.
4- Create the countdown service
You will also create this service using the appropriate ng command.
ng g service service/countDown
Now write the following code into your CountDownService class:
getRemainingTimeObservable(endTime: string): Observable<any> {
return timer(0, 1000).pipe(
map(() => {
const total = Date.parse(endTime) - Date.parse(new Date().toString());
const seconds = Math.floor((total / 1000) % 60);
const minutes = Math.floor((total / 1000 / 60) % 60);
return {total, seconds, minutes}
}),
takeWhile(({ total }) => total >= 0, true)
)
}
and add this imports from rxjs:
import { Observable, map, takeWhile, timer } from 'rxjs';
This function takes as an argument the expiration date of the validation code. It returns an obervable. The timer(0, 1000) method creates an observable that starts emitting values immediately (as indicated by the first argument), and emits a new one every second (1000 milliseconds). This simulates the ticking of a clock.
The rxjs pipe method allows you to apply operations to the data stream produced by the observable. In our case it will allow us to use the map and takeWhile operators on our stream. We use map to produce the data format to return, and the takeWhile to set some kind of stop condition for the timer.
At each time t of the timer, the following line of code
const total = Date.parse(endTime) - Date.parse(new Date().toString());
will calculate the remaining time until expiration by subtracting the current time from endTime. Here is the heart of our countdown timer.
We are done with the countdown service.
5- Update the signup component
First update the html file with the following code.
<div>
<form >
<!--One way binding with [value]-->
<input type="text" placeholder="John" name="firstname" [value]="firstname" />
<input type="text" placeholder="Doe" name="lastname" [value]="lastname" />
<input type="email" placeholder="email@mail.com" name="email" [value]="email"/>
<input type="password" placeholder="*******" name="password" [value]="password" />
<button (click)="signup()">Submit</button>
</form>
</div>
or
<form >
<!--Two ways binding with ngModel requires to add FormsModule in the
imports array of app.module.ts.
Let's use this approach.
-->
<input type="text" placeholder="John" name="firstname" [(ngModel)]="firstname" />
<input type="text" placeholder="Doe" name="lastname" [(ngModel)]="lastname" />
<input type="email" placeholder="email@mail.com" name="email" [(ngModel)]="email" />
<input type="password" placeholder="*******" name="password" [(ngModel)]="password" />
<button (click)="signup()">Submit</button>
</form>
We will not focus here on the design, in which case we would have used Angular material or Bootstrap or created a css file.
Now update the .ts file like this:
email! : string;
password! : string;
firstname!: string;
lastname!: string;
authService = inject(AuthServiceService)
router = inject(Router)
signup() {
this.authService.signup(this.firstname, this.lastname, this.email,this.password).subscribe({
next : apiResponse => {
//some stuff here
localStorage.setItem("authentication_date", apiResponse.authentication_date);
this.router.navigate(['/activate']);
},
error: (err) => console.error(err),
complete: () => console.log('Completed')
})
}
Do not forget to import , @angular/router and AuthServiceService that are injected. You can also inject them through the constructor like this :
constructor(
private authService : AuthServiceService,
private router : Router) {
}
// it will produce the same result like
// authService = inject(AuthServiceService)
// router = inject(Router)
What are we doing here ? We invoke the service and then subscribe to the observable it provides. If the http call is successful, then we store the time value in localStorage, and then we redirect to the activate component.
Let’s now build the activation component.
6- Create the pipes
Pipes in Angular are classes that implement the PipeTransform interface to perform data transformations synchronously or asynchronously in templates. You can use built-in pipes such as currency or date, or create custom pipes that fit your specific requirements.
To create a pipe, you can do it manually for a deeper understanding, but also using angular CLI. Let us create two pipe that we will use later :
ng g pipe customPipe/addZeros
ng g pipe customPipe/setToZero
We have created two pipes. AddZeros and SetToZero pipes, they are located in a folder named customPipe. Each pipe should be included in the declarations array of the app.module.ts file, enabling its use across any component in the application.
Inside the AddZerosPipe class, replace the transform method with this one:
// This method add a zero when the number is less than 10.
// Example : 9 -> 09, 10 -> 10
transform(value: number): string {
if(value < 10) {
return '0' + value;
}
return String(value);
}
As said above, an Angular pipe implements the PipeTransform interface. The transform method of this interface must therefore be overridden.
Inside the SetToZeroPipe class, replace the transform method with this:
// To avoid to print negative value, set to zero when countdown completes
// example : -10 -> 0
transform(value: number): number {
if(value < 0) return 0;
return value;
}
7- Update the activation component
The best for the end…
Now that we have our signup component, our service, our pipes, let us set the activation component up.
Replace the containt of the .html file with the following :
<div>
<div>
<p>Enter the code that has been sent to your email adress</p>
<p>
remaining time : {{remainingTime?.minutes | setToZero | addZeros}}:{{remainingTime?.seconds | setToZero | addZeros}}</p>
</div>
<form >
<input type="number" name="code" class="form-control" [(ngModel)]="activationCode"
id="inputCode" placeholder="00000" />
<button (submit)="activate">Submit</button>
</form>
</div>
Note how we use a pipe with the ‘|’ symbol followed by its name. This name corresponds to the one present in the @Pipe directive.
Update the containt of the .ts file :
activationCode! : number;
activationDeadline: any;
remainingTime : any;
subscriptionToTimer!: Subscription;
timerService = inject(CountDownService);
ngOnInit() {
console.log(new Date().toString());
let endTime = localStorage.getItem('authentication_date') ?? new Date().toString();
this.activationDeadline = new Date(Date.parse(endTime) + 60 * 10 * 1000);
this.subscriptionToTimer = this.timerService.getRemainingTimeObservable(this.activationDeadline)
.subscribe({
next : time => {
this.remainingTime = time;
},
error: (err) => console.error(err),
complete: () => console.log('Observable completed')
})
}
activate () {
//implement the logic here
}
Now open the application on your browser. In terms of UI/UX, nothing too special. fill in your informations and click on the `submit` button. The countdown starts. And if you reload the page, it doesn’t reset. That was the aim of the game. But if it resets, this means that you have not retrieved the date from the backend (confirm this by checking if the localStorage is not null).
Conclusion
In this article, we implemented a countdown for activating an account after a signup operation. This improves the user experience. The key Angular concepts that were discussed:
- Rxjs Observable,
- angular pipes
- data binding
- Angular CLI
To go in-depth on these concepts, you can read the Angular documentation at the following address : https://angular.io/docs
You can find the source code on my Github repository at the following address: https://github.com/ntjoel19/countDownTime