Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to build a login page in Angular

A login page in Angular requires a reactive form with validation, an auth service that calls your backend and stores the JWT, and a route guard that redirects unauthenticated users. As the creator of CoreUI with Angular development experience since 2014, I’ve built the authentication flows in CoreUI Angular templates used by thousands of enterprise developers. The key is separating form logic, HTTP calls, and token storage into distinct layers so each piece is independently testable. A login page that looks professional and handles errors gracefully significantly impacts first impressions of your application.

Build the login form using Angular Reactive Forms and CoreUI components.

// login.component.ts
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms'
import { Router } from '@angular/router'
import {
  CardModule,
  FormModule,
  ButtonModule,
  GridModule,
  AlertComponent
} from '@coreui/angular'
import { AuthService } from '../auth/auth.service'

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, CardModule, FormModule, ButtonModule, GridModule, AlertComponent],
  template: `
    <div class="min-vh-100 d-flex align-items-center">
      <c-container>
        <c-row class="justify-content-center">
          <c-col md="6" lg="5">
            <c-card class="p-4">
              <c-card-body>
                <h1 class="h4 mb-4">Login</h1>

                <c-alert color="danger" *ngIf="error">{{ error }}</c-alert>

                <form [formGroup]="form" (ngSubmit)="submit()">
                  <div class="mb-3">
                    <label cLabel for="email">Email</label>
                    <input
                      cFormControl
                      id="email"
                      type="email"
                      formControlName="email"
                      placeholder="[email protected]"
                      [class.is-invalid]="isInvalid('email')"
                    />
                    <c-form-feedback *ngIf="isInvalid('email')">
                      Valid email is required
                    </c-form-feedback>
                  </div>

                  <div class="mb-4">
                    <label cLabel for="password">Password</label>
                    <input
                      cFormControl
                      id="password"
                      type="password"
                      formControlName="password"
                      [class.is-invalid]="isInvalid('password')"
                    />
                    <c-form-feedback *ngIf="isInvalid('password')">
                      Password must be at least 6 characters
                    </c-form-feedback>
                  </div>

                  <button
                    cButton
                    color="primary"
                    type="submit"
                    class="w-100"
                    [disabled]="loading"
                  >
                    {{ loading ? 'Signing in...' : 'Sign In' }}
                  </button>
                </form>
              </c-card-body>
            </c-card>
          </c-col>
        </c-row>
      </c-container>
    </div>
  `
})
export class LoginComponent {
  loading = false
  error = ''

  form = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(6)]]
  })

  constructor(
    private fb: FormBuilder,
    private auth: AuthService,
    private router: Router
  ) {}

  isInvalid(field: string): boolean {
    const control = this.form.get(field)
    return !!(control?.invalid && control.touched)
  }

  async submit(): Promise<void> {
    if (this.form.invalid) {
      this.form.markAllAsTouched()
      return
    }

    this.loading = true
    this.error = ''

    try {
      await this.auth.login(
        this.form.value.email!,
        this.form.value.password!
      )
      this.router.navigate(['/dashboard'])
    } catch (err: any) {
      this.error = err.message ?? 'Login failed. Please try again.'
    } finally {
      this.loading = false
    }
  }
}

markAllAsTouched() triggers validation display on all fields when the user submits with empty fields. The isInvalid helper checks if a field has errors AND has been touched — this prevents showing errors before the user has interacted with the field.

Auth Service

Handle JWT storage and HTTP login.

// auth/auth.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { firstValueFrom } from 'rxjs'

@Injectable({ providedIn: 'root' })
export class AuthService {
  constructor(private http: HttpClient) {}

  async login(email: string, password: string): Promise<void> {
    const { token } = await firstValueFrom(
      this.http.post<{ token: string }>('/api/auth/login', { email, password })
    )
    localStorage.setItem('token', token)
  }

  logout(): void {
    localStorage.removeItem('token')
  }

  getToken(): string | null {
    return localStorage.getItem('token')
  }

  isLoggedIn(): boolean {
    return !!this.getToken()
  }
}

Route Guard

Redirect unauthenticated users to the login page.

// guards/auth.guard.ts
import { inject } from '@angular/core'
import { Router } from '@angular/router'
import { AuthService } from '../auth/auth.service'

export const authGuard = () => {
  const auth = inject(AuthService)
  const router = inject(Router)
  return auth.isLoggedIn() ? true : router.parseUrl('/login')
}

Best Practice Note

This is the exact login page structure used in CoreUI Angular Admin Template, which includes a polished login page with a full authentication flow ready to connect to your backend. For production, add token refresh logic, handle token expiry, and consider using sessionStorage instead of localStorage for stricter security. See how to build a signup page in Angular for the companion registration flow.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team