Advertisement

#14 All About the Username

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.

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

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);
            ...
        }
    }
}
(a) We need to add a small timeout so that we can see how the transitions of our authenticator component are behaving.

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")
                ...
(b) We need to modify our getValidatorIconClass so that we know which control we are talking about. We also only want to show the unique validator if the user is trying to register and not login.

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
            })
        ];
    }
    ...
}
(c) Here we are adding a property setter for the _isUsernameUnique flag so that we automatically update the validity of the username control anytime the flag is changed. We also make a small change to the validator icon method and the unique validator itself.

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.

Using the *ngIf directive we are no longer displaying the unique validator if
            the user is attempting to login.
(d) Using the *ngIf directive we are no longer displaying the unique validator if the user is attempting to login.
If the user is trying to register we display a different icon if we have not asked the server
            if the username is available or not.
(e) If the user is trying to register we display a different icon if we have not asked the server if the username is available or not.

Adding finally to our requests

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator-http.service.ts
      • services
        • http.service.ts

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);
                }
            });
    }
}
(f) Adding the ability to specify a function that will be run after a request is made whether or not it was successful.

authenticator-http.service.ts

export class AuthenticatorHttpService extends HttpService {
    ...
    public checkUsernameAvailability = (..., $finally: (response: Response) => void) => {
        this.get(..., $finally);
    }
}
(g) We need to add the $finally function to the arguments of the checkUsernameAvailability method so that we can pass it to the base class.
Advertisement

Is the username unique or not?

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.ts

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;
            });
    }
    ...
}
(h) We need to modify our code so that we are actually responding to the response the server has sent us.

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 the interface looks like if we have not check for the availability of the
            username.
(i) What the interface looks like if we have not check for the availability of the username.
What the interface looks like if the username is unavailable.
(j) What the interface looks like if the username is unavailable.
What the interface looks like if the username is available.
(k) What the interface looks like if the username is available.

What if they change there minds?

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.pug
          • authenticator.component.ts

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}')`)
            ...
(l) In order to allow the user to change the value of the username input we need to bind an event handler to the key up event.

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;
        }
    }
}
(m) If the user changes the value of the username input and we have already checked for the availability of the previous name we need to set out component to allow for the new username to be checked.

No need to check again

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.ts

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] = {};
            },
            ...);
    }
    ...
}
(n) Updating our component to populate our username unavailable cache object and use it to determine whether we need to check on the current username after the user has changed it.
Exciton Interactive LLC
Advertisement