#14 All About the Username
Thursday, January 4, 2018
In this article we will use the response from the server, that we setup in a previous article, telling us whether a username is available or not to style the username input and button as well show the correct icon for the unique validator.
Parts
- Part 30: User Database
- Part 29: Canonical Url
- Part 28: Razor Page Handlers
- Part 27: Send Screen Finished
- Part 26: Stroke Dashoffset
- Part 25: Send Screen
- Part 24: Forgot Link Behaviour
- Part 23: Animating Controls (cont.)
- Part 22: Animating Controls
- Part 21: Hiding Control Groups
- Part 20: ModelState Errors (cont.)
- Part 19: ModelState Errors
- Part 18: Animating Info Pages (cont.)
- Part 17: Animating Info Pages
- Part 16: Keyboard Navigation
- Part 15: Accessing the DOM
- Part 14: All About the Username
- Part 13: CSRF Attacks
- Part 12: Http Requests
- Part 11: HMR
- Part 10: Color Inputs And Buttons
- Part 9: Animating Sub-Actions
- Part 8: Form Validation (cont.)
- Part 7: Form Validation
- Part 6: Form Group
- Part 5: Authenticator Validators
- Part 4: Authenticator Inputs
- Part 3: First Angular Component
- Part 2: Webpack
- Part 1: Beginning
You can view a completed example of the authenticator component here.
Unique Validator Icon
-
WebUi
-
Controllers
- AccountController.cs
-
Source
-
components
-
authenticator
- authenticator.component.pug
- authenticator.component.ts
-
authenticator
-
components
-
Controllers
To start with we will add a small timeout to our check username availability action so that we can see the transitions
of our authenticator component (a). Next we are going to modify our template so that
we pass in the key for the form control to our getValidatorIconClass
(b). We need to do
this because the validators for the username control are slightly more complicated than just whether the control has
the error in question. In addition to this we do not want to show the unique validator if the user is just trying to
login so we include the call to the *ngIf
directive on a div that we have added
that contains the unique validator. The last things that we need to do for this section is to modify our component's
typescript file. We start by adding a new property setter for setting the _isUsernameUnique
flag. We are doing this because we want to update the validity of the username control anytime the flag is changed.
Next we modify the getValidatorIconClass
so that we can use a different icon for
the unique validator. Finally we need to update the username control's unique validator.
AccountController.cs
...
using System.Threading;
namespace WebUi.Controllers
{
...
public class AccountController : Controller
{
...
public IActionResult CheckUsernameAvailability(string username)
{
Thread.Sleep(2000);
...
}
}
}
authenticator.component.pug (1)
...
mixin validator(message, key, error)
div.info-validator
...
i.validator-icon.fa([ngClass]=`getValidatorIconClass('${key}', ...)`)
div.authenticator
div.info-container
div.info-pages
...
+info("Username")
div(*ngIf="isRegister")
+validator("Unique", controlUsernameKey, "unique")
...
authenticator.component.ts (1)
...
export class AuthenticatorComponent implements OnInit {
...
private set isUsernameUnique(value: boolean) {
this._isUsernameUnique = value;
this.controlUsername.updateValueAndValidity();
}
...
public getValidatorIconClass = (key: string, ...): string => {
if (key === this.controlUsernameKey && error === "unique" && this._isUsernameUnique === null) {
return "fa-question";
}
const hasError = control.hasError(error);
return hasError ? "fa-times" : "fa-check";
}
...
private createFormGroup = () => {
...
controls[this.controlUsernameKey] = ["",
XcValidators.compose({
...,
"unique": {
validator: XcValidators.unique,
config: {
pass: () => {
if (this.isLogin) {
return true;
}
return this._isUsernameUnique === null
? true
: this._isUsernameUnique;
}
}
} as IUniqueValidatorAndConfig
})
];
}
...
}
With these changes made we can see in (d) that the unique validator no longer appears if the user is attempting to login. And in (e) we see that we are displaying the icon for the unique validator under certain conditions and the usual and where appropriate.
Adding finally to our requests
-
WebUi
-
Source
-
components
-
authenticator
- authenticator-http.service.ts
-
authenticator
-
services
- http.service.ts
-
components
-
Source
When we create the HttpService
and the AuthenticatorHttpService
we provided a way of executing code depending on whether a request was successful by passing in a couple of functions. There will also be plenty
of cases where we will want to execute additional code whether or not the request was successful. We will do this by allowing for a $finally, since
finally is a reserved word we need to add the '$', function to be specified. This function is of course optional and we will see use of it in
the next section.
http.service.ts
...
export abstract class HttpService {
...
protected get = (..., $finally?: (response: Response) => void) => {
this.doPromise(..., $finally);
}
private doPromise = (..., $finally?: (response: Response) => void) => {
observable
.toPromise()
.then((response: Response) => {
...
if (typeof $finally !== "undefined" && $finally !== null) {
$finally(response);
}
})
.catch((response: Response) => {
...
if (typeof $finally !== "undefined" && $finally !== null) {
$finally(response);
}
});
}
}
authenticator-http.service.ts
export class AuthenticatorHttpService extends HttpService {
...
public checkUsernameAvailability = (..., $finally: (response: Response) => void) => {
this.get(..., $finally);
}
}
Is the username unique or not?
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.ts
-
authenticator
-
components
-
Source
Next we need to actually use the response from the server to indicate whether or not the username that the user is trying to use is unique or not. We also want to prevent checking with the server if the user is trying to login or the username control is currently invalid. Once we have the response to the server we only need to use the property setter that we previously created to set whether or not the username is unique based on whether the server responded with a success or error code. Lastly we see our use of the finally method so that we can tell our component that we are no longer checking for the availability of the username (h).
authenticator.component.ts (2)
...
export class AuthenticatorComponent implements OnInit {
...
private checkUsernameAvailability = () => {
if (this.controlUsername.invalid || this.isLogin) {
return;
}
this._isCheckingUsernameAvailability = true;
this._authenticatorHttpService.checkUsernameAvailability(this.controlUsername.value,
() => {
this.isUsernameUnique = true;
},
() => {
this.isUsernameUnique = false;
},
() => {
this._isCheckingUsernameAvailability = false;
});
}
...
}
With those changes our component will display to the user whether we have not checked that the username is available (i), or that the username is unavailable (j), or lastly if the username is available (k).
What if they change there minds?
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.pug
- authenticator.component.ts
-
authenticator
-
components
-
Source
As it stands now if the user returns to the username input after having checked whether the username is available the
component would not check again for a new username. We of course need to correct this. In order do it though we will
need to bind the onKeyUp
event of our inputs (l).
For now we will only create a default branch of a switch that uses the key property of the event that is being
passed into our event handler (m). We will return to this handler
in the future for handling specific keys in the future. As you can see we are simply setting the is username unique
flag to null if the user is typing into the username input. And just to remind you since we are using the property to
set the flag the validity, and therefore the appearance of the input, are being updated automatically. Lastly we are using
the previous username cache to prevent the is username unique being set null by the user just pressing a modifer key such
as shift or alt.
authenticator.component.pug (2)
mixin formControlGroup(label, inputType, key)
div.form-control-group
...
div.input-group(...)
input(..., (keyup)=`onKeyUp($event, '${key}')`)
...
authenticator.component.ts (3)
...
export class AuthenticatorComponent implements OnInit {
...
private _previousUsernameCache: string = null;
...
public onKeyUp = (event: KeyboardEvent, key: string) => {
switch (event.key) {
default:
if (key === this.controlUsernameKey && this.controlUsername.value !== this._previousUsernameCache) {
this._previousUsernameCache = this.controlUsername.value;
if (this._isUsernameUnique !== null) {
this.isUsernameUnique = null;
}
}
break;
}
}
}
No need to check again
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.ts
-
authenticator
-
components
-
Source
If we have previously checked on the availability of a username and the server has responded telling us that it is not available the
likelihood that it will be available if we check again is probably negligible. For this reason it makes sense for us to remember
the unavailable usernames and just show the user that they are unavailable without checking with the server first. This is probably
also true for available names but we will not cache them as we are about to do with the unavailable ones. To remember the names that
we have checked and found to be unavailable we will use a private cache object, _invalidUsernameCache
(n). Now we just need to modify our checkUsernameAvailability
method to add the username, as a property to our object, if the server responds with an error. Once we have done that we can modify
our onKeyUp
method to check whether the cache object contains a property with the current
username. If it does we will set the username unique property to false otherwise null.
authenticator.component.ts (4)
...
export class AuthenticatorComponent implements OnInit {
...
private readonly _unavailableUsernameCache: { [key: string]: { } } = {};
...
public onKeyUp = (event: KeyboardEvent, key: string) => {
switch(event.key) {
default:
if (key === this.controlUsernameKey && this.controlUsername.value !== this._previousUsernameCache) {
this._previousUsernameCache = this.controlUsername.value;
const cached = this._unavailableUsernameCache[this.controlUsername.value];
if (typeof cached !== "undefined") {
this.isUsernameUnique = false;
} else if (this._isUsernameUnique !== null) {
this.isUsernameUnique = null;
}
}
break;
}
}
...
private checkUsernameAvailability = () => {
...
const username = this.controlUsername.value;
...
this._authenticatorHttpService.checkUsernameAvailability(username,
...,
() => {
...
this._unavailableUsernameCache[username] = {};
},
...);
}
...
}