A common problem I’ve noticed in some systems is the difficulty in defining a password.
Nowadays, there are many requirements when creating a password. I remember that in the past, passwords like ‘123’ or ‘admin’ were common.
But now it’s so hard to define a password that sometimes I almost cry.
If we, as developers, have trouble defining a ‘simple password,’ just imagine the challenges our parents and grandparents face.
The truth is, we need significant improvements in UX (User Experience), particularly within banking systems!
On this post I will share a good approach to improve our UX.
You will learn how to create this
Start project
- First of all I created the repository input-validator-tooltip-angular on GitHub
- Second I created the angular project
ng new input-validator-tooltip-angular --no-standalone
- I chose SCSS for stylesheet
- Server-Side Rendering I Chose: NO
- Note: Starting from Angular v17, the module won’t be created.”. But I want module so I add: –no-standalone
- To understand a little more about Angular CLI (ng), you can see more details here
- Now, let’s add Angular Material
ng add @angular/material
Creating the directive
- Creating the module
ng g m validator-password-tooltip
- Creating the directive
ng g d validator-password-tooltip/validator-password-tooltip
- In ValidatorPasswordTooltipModule we will add MatTooltipModule in imports and export ValidatorPasswordTooltipDirective. Like that:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatTooltipModule } from '@angular/material/tooltip'; import { ValidatorPasswordTooltipDirective } from './validator-password-tooltip.directive'; @NgModule({ declarations: [ ValidatorPasswordTooltipDirective ], imports: [ CommonModule, MatTooltipModule ], exports:[ ValidatorPasswordTooltipDirective ] }) export class ValidatorPasswordTooltipModule {}
- Now we are opening the ValidatorPasswordTooltipDirective and extends MatTooltip
import { Directive } from '@angular/core'; import { MatTooltip } from '@angular/material/tooltip'; @Directive({ selector: '[validatorTooltip]', exportAs: 'validatorTooltip' }) export class ValidatorPasswordTooltipDirective extends MatTooltip {}
- Maybe you’re asking: Why do we extend MatTooltip? Because we will use it!
- In this example I want to utilize, but if you prefer, you can create a custom tooltip. In this post you can see how.
- Essentially we will change the behavior of MatTooltip. To understand how I opened the class and analyzed it’s structure.
- Just add these lines inside class
@Input() get validatorTooltip() { return this.message; } set validatorTooltip(value: string) { this.message = value + "- step1"; this.show(); }
- In the code above we are overriding the default behavior.
- When the value changes, the function ‘setvalidatorTooltip’ is called, so I added ‘this.show()’ to show tooltip.
- Now we can use the created directive but first we need to import some modules. Go to app.module.ts and add ReactiveFormsModule, MatInputModule and ValidatorPasswordTooltipModule:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatInputModule } from '@angular/material/input'; import { ReactiveFormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { ValidatorPasswordTooltipModule } from './validator-password-tooltip/validator-password-tooltip.module'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, BrowserAnimationsModule, ReactiveFormsModule, // add to use FormControl MatInputModule, // add to use input from material ValidatorPasswordTooltipModule // directive created ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
- In app.component.ts put this code:
import { Component } from '@angular/core'; import { FormControl } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { public password = new FormControl(); }
- In app.component.html remove all html and put this:
<div style="display: flex; justify-content: center; align-items: center; padding-top: 50px;"> <mat-form-field style="width: 50%;"> <mat-label>Password</mat-label> <input matInput [formControl]="password" [validatorTooltip]="password.value"> </mat-form-field> </div>
- The result:
- Now it’s the time for magic!
- Put this code in validator-password-tooltip.directive.ts:
import { Directive, Input, OnInit } from '@angular/core'; import { MatTooltip } from '@angular/material/tooltip'; @Directive({ selector: '[validatorTooltip]', exportAs: 'validatorTooltip' }) export class ValidatorPasswordTooltipDirective extends MatTooltip implements OnInit { ngOnInit(): void { super.tooltipClass = 'validator-password-tooltip'; } @Input() get validatorTooltip() { return this.message; } set validatorTooltip(value: string) { this.message = this.getMessage(value); this.show(); } private getMessage(value: string): string { const text = `${this.getTextRoleNumber(this.hasAnyNumber(value))} ${this.getTextRoleUpper(this.hasAnyUpper(value))} ${this.getTextRoleLower(this.hasAnyLower(value))} ${this.getTextRoleSpecial(this.hasAnySpecial(value))} ${this.getTextRoleLimit(this.hasLimit(value))}`; return text; } private getTextRoleNumber(showIcon: boolean): string { return `${this.getIcon(showIcon)} - Password must contain at least 1 number (0-9)\n` } private getIcon(showIcon: boolean): string { return `${showIcon ? '✅' : '❌'}` } private hasAnyNumber(value: string): boolean { return /[0-9]/.test(value) } private getTextRoleUpper(showIcon: boolean): string { return `${this.getIcon(showIcon) } - Password must contain at least 1 uppercase letter\n` } private hasAnyUpper(value: string): boolean { return /[A-Z]/.test(value) } private getTextRoleLower(showIcon: boolean): string { return `${this.getIcon(showIcon) } - Password must contain at least 1 lowercase letter\n` } private hasAnyLower(value: string): boolean { return /[a-z]/.test(value) } private getTextRoleSpecial(showIcon: boolean): string { return `${this.getIcon(showIcon) } - Password must contain at least 1 special character\n` } private hasAnySpecial(value: string): boolean { return /[^\w\d\s:]/.test(value) } private getTextRoleLimit(showIcon: boolean): string { return `${this.getIcon(showIcon)} - Password must have 7 to 20 characters without spaces\n` } private hasLimit(value: string): boolean { return /^([^\s]){7,20}$/gm.test(value) } }
Understanding the code
set validatorTooltip(value: string) { this.message = this.getMessage(value); this.show(); } private getMessage(value: string): string { const text = `${this.getTextRoleNumber(this.hasAnyNumber(value))} ${this.getTextRoleUpper(this.hasAnyUpper(value))} ${this.getTextRoleLower(this.hasAnyLower(value))} ${this.getTextRoleSpecial(this.hasAnySpecial(value))} ${this.getTextRoleLimit(this.hasLimit(value))}`; return text; }
- Here, we simply call the function ‘getMessage(value)’
- This function will return the text that will be shown inside the tooltip
private getIcon(showIcon: boolean): string { return `${showIcon ? '✅' : '❌'}` }
- The function above just show a simple icon (✅ or ❌)
private hasAnyNumber(value: string): boolean { return /[0-9]/.test(value) } private hasAnyUpper(value: string): boolean { return /[A-Z]/.test(value) } private hasAnyLower(value: string): boolean { return /[a-z]/.test(value) } private hasAnySpecial(value: string): boolean { return /[^\w\d\s:]/.test(value) } private hasLimit(value: string): boolean { return /^([^\s]){7,20}$/gm.test(value) }
- This part is probably the most complex.
- We have a function for each rule using Regex.
- Regex is an important area in programming mainly for data validation
- Basically in this code I validate if the value has (number, upper…)
return /^([^\s]){7,20}$/gm.test(value)
- Calm down. It may seem difficult to understand, but we have tools that help us with this creation
- This site is amazing 🙂
- And you will probably create a function that someone has already created, so it’s simple to find.
private getTextRoleNumber(showIcon: boolean): string { return `${this.getIcon(showIcon)} - Password must contain at least 1 number (0-9)\n` } private getTextRoleUpper(showIcon: boolean): string { return `${this.getIcon(showIcon) } - Password must contain at least 1 uppercase letter\n` } private getTextRoleLower(showIcon: boolean): string { return `${this.getIcon(showIcon) } - Password must contain at least 1 lowercase letter\n` } private getTextRoleSpecial(showIcon: boolean): string { return `${this.getIcon(showIcon) } - Password must contain at least 1 special character\n` } private getTextRoleLimit(showIcon: boolean): string { return `${this.getIcon(showIcon)} - Password must have 7 to 20 characters without spaces\n` }
- Here we just put the message that will be presented and call the function ‘getIcon(showIcon)’
- That’s the result:
- Now we just need adjust the css
- Open styles.scss and put this:
.validator-password-tooltip { white-space: pre-line; .mdc-tooltip__surface { max-width: unset; } }
- In ValidatorPasswordTooltipDirective add this:
import { Directive, Input, OnInit } from '@angular/core'; import { MatTooltip } from '@angular/material/tooltip'; @Directive({ selector: '[validatorTooltip]', exportAs: 'validatorTooltip' }) export class ValidatorPasswordTooltipDirective extends MatTooltip implements OnInit { ngOnInit(): void { super.tooltipClass = 'validator-password-tooltip'; } }
- Simple right? We just change the css from MatTooltip and now we have the final result:
Conclusion
- This approach is simple but I have been seeing a lot of system confusing the final user to do that simple task.
- If you have ever faced any problem in setting password for any system, tell me below.
- I hope you enjoyed it and see you next time 😀
Code: https://github.com/devinvestidor/input-validator-tooltip-angular