Kirti Kulkarni

Jun 28, 2022Beginner-12 min

5
12

Create a custom login page in ABP Commercial Angular app

In this short article and video, we will demonstrate how to work with the default login page of ABP commercial Angular application and create a new design for the same login page with a nice-looking background. Hope you enjoy creating the same! 😊

1. Run following command to create AccoutModule, LoginComponent, AccoutLayoutComponent

2. Open the generated src/account/account.module.ts file and replace the content with below:

import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { LoginComponent } from './login/login.component';
    import { CoreModule } from '@abp/ng.core';
    import { ThemeSharedModule } from '@abp/ng.theme.shared';
    import { AccountLayoutComponent } from './account-layout/account-layout.component';  
    @NgModule({
    declarations: [
    LoginComponent,
    AccountLayoutComponent
    ],
    imports: [
    CommonModule,
    CoreModule, 
    ThemeSharedModule
    ],
    exports: [LoginComponent, AccountLayoutComponent]
    })
    export class AccountModule { } 

3. Now open the generated account-layout.component.ts file and replace the content with the below code

import { Component, OnInit } from '@angular/core';
    import { AuthWrapperService } from '@volo/abp.ng.account.core';
    @Component({
    selector: 'app-account-layout',
    templateUrl: './account-layout.component.html',
    styleUrls: ['./account-layout.component.scss'],
    providers: [AuthWrapperService],
    })
    export class AccountLayoutComponent implements OnInit {
    constructor(
    public authWrapperService: AuthWrapperService,
    ) { }
    ngOnInit(): void {
    }
    }

4. Open the generated account-layout.component.scss file and replace the content with the below code

.main {
        min-height: 100vh;
        background: #f6fffd;
        }
        .styleCard {
        border: 3px solid #07ad90;
        }
        .card-header .styleCardTitle {
        color: #07ad90;
        font-size: 25px;
        font-weight: 600;
        }

5. Open the generated account-layout.component.html file and replace the content with the below code

<div className="d-flex align-items-center main" style="min-height: 100vh">
    <div className="container">
    <div className="row">
    <div className="col mx-auto" style="max-width: 400px">
    <div className="card styleCard">
    <div className="card-header">
    <h2 className="card-title d-inline-block styleCardTitle">
    Login
    </h2>
    </div>
    <div
    className="card-body"
    *ngIf="authWrapperService.enableLocalLogin$ | async; else disableLocalLoginTemplate">
    <router-outlet></router-outlet>
    </div>
    </div>
    </div>
    </div>      
    <ng-template #disableLocalLoginTemplate>
    <div className="alert alert-warning">
    <strong>{{ 'AbpAccount::InvalidLoginRequest' | abpLocalization }}</strong>
    {{ 'AbpAccount::ThereAreNoLoginSchemesConfiguredForThisClient' | abpLocalization }}
    </div>
    </ng-template>
    </div>
    </div>      

6. Open the generated login.component.ts file and replace the content with the below


    import { AuthService, ConfigStateService } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { AfterViewInit, Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import {
eAccountComponents,
getRedirectUrl,
RecaptchaService,
RECAPTCHA_STRATEGY,
SecurityCodeData,
SecurityCodeService,
} from '@volo/abp.ng.account/public';
import { IdentityLinkUserService, LinkUserInput } from '@volo/abp.ng.account/public/proxy';
import { of, pipe, throwError } from 'rxjs';
import { catchError, finalize, switchMap, tap } from 'rxjs/operators';
    
const { maxLength, required } = Validators;
    
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
providers: [RecaptchaService],
})
export class LoginComponent implements OnInit, AfterViewInit {
@ViewChild('recaptcha', { static: false })
recaptchaRef: ElementRef;
    
form: FormGroup;
    
inProgress: boolean;
    
isSelfRegistrationEnabled = true;
    
authWrapperKey = eAccountComponents.AuthWrapper;
    
linkUser: LinkUserInput;
    
protected fb: FormBuilder;
protected toasterService: ToasterService;
protected authService: AuthService;
protected configState: ConfigStateService;
protected route: ActivatedRoute;
protected router: Router;
protected identityLinkUserService: IdentityLinkUserService;
protected recaptchaService: RecaptchaService;
protected securityCodeService: SecurityCodeService;
    
constructor(protected injector: Injector) {
this.fb = injector.get(FormBuilder);
this.toasterService = injector.get(ToasterService);
this.authService = injector.get(AuthService);
this.configState = injector.get(ConfigStateService);
this.route = injector.get(ActivatedRoute);
this.router = injector.get(Router);
this.identityLinkUserService = injector.get(IdentityLinkUserService);
this.recaptchaService = injector.get(RecaptchaService);
this.securityCodeService = injector.get(SecurityCodeService);
}
    
ngOnInit() {
this.init();
this.buildForm();
this.setLinkUserParams();
}
    
ngAfterViewInit() {
this.recaptchaService.setStrategy(
RECAPTCHA_STRATEGY.Login(this.configState, this.recaptchaRef.nativeElement),
);
}
    
protected setLinkUserParams() {
const {
linkUserId: userId,
linkToken: token,
linkTenantId: tenantId,
} = this.route.snapshot.queryParams;
    
if (userId && token) {
this.identityLinkUserService.verifyLinkToken({ token, userId, tenantId }).subscribe(res => {
if (res) {
this.linkUser = { userId, token, tenantId };
}
});
}
}
    
protected init() {
this.isSelfRegistrationEnabled =
(
(this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') as string) || ''
).toLowerCase() !== 'false';
}
    
protected buildForm() {
this.form = this.fb.group({
username: ['', [required, maxLength(255)]],
password: ['', [required, maxLength(128)]],
rememberMe: [false],
});
}
    
onSubmit() {
if (this.form.invalid) return;
    
this.inProgress = true;
    
const { username, password, rememberMe } = this.form.value;
const redirectUrl = getRedirectUrl(this.injector) || (this.linkUser ? null : '/');
const loginParams = { username, password, rememberMe, redirectUrl };
    
(this.recaptchaService.isEnabled ? this.recaptchaService.validate() : of(true))
.pipe(
switchMap(isValid =>
isValid
? this.authService
.login(loginParams)
.pipe(this.handleLoginError(loginParams))
.pipe(this.linkUser ? this.switchToLinkUser() : tap())
: of(null),
),
finalize(() => (this.inProgress = false)),
)
.subscribe();
}
    
private switchToLinkUser() {
return pipe(
switchMap(() => this.identityLinkUserService.link(this.linkUser)),
tap(() => {
this.router.navigate(['/account/link-logged'], {
queryParams: this.route.snapshot.queryParams,
});
}),
);
}
    
private handleLoginError(loginParams?: Omit<SecurityCodeData, 'twoFactorToken' | 'userId'>) {
return catchError(err => {
if (err.error?.error_description === 'RequiresTwoFactor') {
this.securityCodeService.data = {
...loginParams,
twoFactorToken: err.error.twoFactorToken,
userId: err.error.userId,
};
this.router.navigate(['/account/send-security-code']);
return of();
}
    
this.recaptchaService.reset();
this.toasterService.error(
err.error?.error_description ||
err.error?.error?.message ||
'AbpAccount::DefaultErrorMessage',
null,
{ life: 7000 },
);
return throwError(err);
});
}
}  

7. Open the generated login.component.scss file and replace the content with the below


    .styleInput {
        border: none;
        border-bottom: 3px solid #07ad90;
        }
        input:focus {
        box-shadow: 0 0 0 0.1rem transparent !important;
        }
        .styleBtn {
        background-color: #07ad90;
        color: #fff;
        width: 100%;
        }
        .styleLabel {
        color: #666;
        }
        .form-check-input:checked {
        background-color: #42509e;
        border-color: #42509e;
        }  

8. Open the generated login.component.html file and replace the content with the below

 <section>
    <form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit>
    <div className="mb-3">
    <label for="username" className="form-label styleLabel">{{
    'AbpAccount::UserNameOrEmailAddress' | abpLocalization
    }}</label>
    <input
    className="form-control styleInput"
    type="text"
    id="username"
    formControlName="username"
    placeholder="Username"/>
    >/div>
    <div className="mb-3">
    <label for="password" className="form-label styleLabel">{{
    'AbpAccount::Password' | abpLocalization
    }}</label>
    <input
    className="form-control styleInput"
    type="password"
    id="password"
    formControlName="password"
    placeholder="Password"
    />
    </div>
    <div className="row">
    <div className="col">
    <div className="form-check mb-2">
    <input
    className="mb-4 form-check-input"
    type="checkbox"
    id="login-input-remember-me"
    formControlName="rememberMe"
    />
    <label className="form-check-label styleLabel" for="login-input-remember-me">
    {{ 'AbpAccount::RememberMe' | abpLocalization }}
    </label>
    </div>
    </div>
    <div className="text-end col">
    <a style="color: #42509e">{{
    'AbpAccount::ForgotPassword' | abpLocalization
    }}</a>
    </div>
    </div>
    <button className="d-grid btn styleBtn" type="submit">
    {{ 'AbpAccount::Login' | abpLocalization }}
    </button>
    </form>
    </section>

9. Open the generated app.component.ts file and replace the components within @volo/abp.ng.accout/public with your components:

import { ReplaceableComponentsService } from '@abp/ng.core';
    import { Component } from '@angular/core';
    import { eAccountComponents } from '@volo/abp.ng.account/public';
    import { eThemeLeptonComponents } from '@volo/abp.ng.theme.lepton';
    import { AccountLayoutComponent } from './account/account-layout/account-layout.component';
    import { LoginComponent } from './account/login/login.component';
        
    @Component({
    selector: 'app-root',
    template: 
    <abp-loader-bar></abp-loader-bar>
    <abp-dynamic-layout></abp-dynamic-layout>,
    })
    export class AppComponent {
    constructor(private replaceableComponentsService: ReplaceableComponentsService) {}
        
    ngOnInit() {
    this.replaceableComponentsService.add({
    key: eAccountComponents.Login,
    component: LoginComponent,
    });
    this.replaceableComponentsService.add({
    key: eThemeLeptonComponents.AccountLayout,
    component: AccountLayoutComponent,
    });
    }
    }

10. Change environment.ts

If you implemented the Angular UI account module to your project, you can switch the flow to resource owner password flow by changing the OAuth configuration in the environment.ts files as shown below:

import { Environment } from '@abp/ng.core';

    export const environment = {
    // other options removed for sake of brevity
        
    oAuthConfig: {
    issuer: 'https://localhost:44305',
    clientId: 'MyProjectName_App',
    dummyClientSecret: '1q2w3e*',
    scope: 'offline_access MyProjectName',
    },
        
    // other options removed for sake of brevity
    } as Environment;

This is how it will look finally after running the angular application!

Final Angular Application
Resources:

1.https://docs.abp.io/en/abp/5.1/UI/Angular/Authorization

2.https://docs.abp.io/en/abp/5.1/UI/Angular/Account-Module

3.https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement?&_ga=2.134919410.2079403767.1656487830-331411379.1655450101#how-to-replace-a-component

Please do let us know any specific scenario or user story that you would like us to demonstrate. HireTechTeam developers have implemented huge number of varied solutions for our customers with ABP commercial. Write to us at : info@waiin.com

Featured Comments
Joe ThomsonToday at 5:42PM

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor

Joe ThomsonToday at 5:42PM

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor

Joe ThomsonToday at 5:42PM

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor

Kirti Kulkarni

ABOUT THE AUTHOR

With over 20 years of experience in software development, Kirti heads Product R&D and Competency Management at WAi Technologies, leading the training and skills upgradation program at WAi. Kirti introduced the 'Women Back To Work' Initiative that encourages women to return back to mainstream software development after a career break or sabbatical.