Skip to content
barezazadPublic

About

Angular 22 admin boilerplate — Material, JWT refresh, RBAC, i18n (en/ar/ckb), CRUD tables, and PWA

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

Repository files navigation

Dragon — Angular Admin Boilerplate

Dragon logo

Production-oriented Angular admin starter
Angular 22 · Material · Transloco · JWT refresh · RBAC · PWA

Angular 22 Angular Material TypeScript Node.js Transloco PWA Playwright MIT license


Table of contents

  1. What is Dragon?
  2. What makes it different
  3. Architecture
  4. Quick start
  5. Configuration
  6. Authentication & session
  7. RBAC
  8. Building screens
  9. HTTP client & interceptor
  10. List / filters / pagination
  11. Forms, dialogs & unsaved changes
  12. i18n & RTL
  13. Theme
  14. PWA
  15. Scripts & tests
  16. Contributing
  17. License

What is Dragon?

Dragon is an open-source Angular 22 admin boilerplate: standalone components, Angular Material, lazy routes, JWT login with refresh, resource permissions, Transloco i18n (including RTL), shared CRUD tables, and a production PWA.

Clone it, point apiUrl at your admin API, add pages; keep auth, HTTP, theme, and table patterns.

Keywords: angular admin boilerplate, angular material dashboard, jwt refresh, rbac, transloco rtl, pwa starter


What makes it different

Most Angular starters stop at “login + sidebar”. Dragon is opinionated about admin day-to-day:

Area Typical starter Dragon
Auth Access JWT only Access + refresh; interceptor retries once
Session Token in memory localStorage session; JWT expiry checked before the request
Login Hard redirect to / ?returnUrl= after silent logout / deep link
Permissions Role name string Resource RBAC (city:read, user:write, …)
Write vs read Separate checks :write also satisfies matching :read
Lists Client search= query Filter DSL + paging + sort
CRUD Copy-paste per page Shared createListPage() (load, empty, error, dialog, delete)
Forms Close dialog anytime Unsaved-form guard on create / edit
i18n English only en / ar / ckb, RTL, paginator + snackbars translated
Errors Console only 403 / 404 / 500 pages, global handler, API field errors
Offline None Production service worker; API traffic is never cached

Architecture

Browser
  → Guards (guest / auth / permission / first-tab / unsaved-form)
    → Pages (standalone, lazy loadComponent)
      → HttpService + apiInterceptor
        → /api/admin/v1  (proxied in dev)
src/app/
  @core/          auth, HTTP, interceptor, guards, models, i18n, theme, spinner, PWA
  @shared/        logo, dialogs, list-page helper, permission directive
  dragon/
    auth/         login
    home/         shell (toolbar + sidenav)
    dashboard/    landing page
    setting/      system, activities, cache, regions, cities
    account/      roles, users, login blocks, customers
    accounting/   currencies, account credits
    section/      shared tab shell for admin sections
src/environments/ environment.ts, environment.prod.ts
src/assets/i18n/  en.json, ar.json, ckb.json
src/assets/img/   logo.png, logo-sm.png
public/           PWA manifest and icons
Route prefix Section
/login Sign in (guest only)
/dashboard Home
/settings System, activities, cache, regions, cities
/accounts Roles, users, login blocks, customers
/accounting Currencies, account credits
/403 /404 /500 Error pages

Dev proxy: proxy_local.json forwards /api/** to the backend.


Quick start

Requirements

  • Node.js 22.22.3+ (24 and 26 also supported) — see .nvmrc
  • npm 10+
  • An admin API on the proxy target (default http://0.0.0.0:6969)
nvm use
npm install
npm start

Open http://localhost:4200.

npm run build
npx ng build --configuration production

Production output: dist/dragon/browser.


Configuration

Edit src/environments/environment.ts (and environment.prod.ts for production).

Key Purpose
apiUrl Admin API prefix, default /api/admin/v1
localStorageKey Session key (operator-dragon)
theme Color palette (see Theme)
defaultLang en, ar, or ckb
perPageOptions Table page sizes
snackBarDuration Toast duration in ms

Change the backend host/port in proxy_local.json. Restart npm start after env or proxy edits.

Logo

File Used for
src/assets/img/logo.png Favicon, README, PWA-scale mark
src/assets/img/logo-sm.png Toolbar and login (<app-logo>)
public/icons/*.png Installable PWA icons

Keep logo-sm.png square; the chrome uses it at 34–72px.


Authentication & session

POST /api/admin/v1/login
{ "username": "admin", "password": "…" }

Session is stored in localStorage (token, refresh_token, resources, …).

Authorization: Bearer <access_token>
POST /api/admin/v1/refresh-token
{ "refresh_token": "<refresh_token>" }
  • Interceptor attaches Authorization except on login and refresh
  • Expired access or 401 → refresh once, then retry
  • You stay logged in while access or refresh JWT is valid
  • Failed refresh → /login?returnUrl=…
  • authGuard protects the shell; guestGuard keeps signed-in users off /login
  • Mutating requests show the spinner and a success toast on 200

Empty resources is full access.


RBAC

Routes use permissionGuard (data.permission). Missing access opens /403; a section opens on the first tab you can use. Buttons use *appPermission="'city:write'". Write also satisfies the matching read.

Catalog: src/app/@core/nav/permissions.ts. Add a resource there, on the route/tab, and on write actions.


Building screens

  1. Endpoint — ApiEndPoints in src/app/@core/const.ts
  2. Model — src/app/@core/models/models.ts
  3. List page — copy Regions; point createListPage at the endpoint, search columns, and dialog
  4. Dialog — reactive form, UnsavedFormService, ErrorHandlingComponent
  5. Route — lazy loadComponent + permissionGuard (and unsavedFormGuard if the page opens dialogs)
  6. Tab — src/app/@core/nav/admin-nav.ts
  7. i18n — en.json / ar.json / ckb.json
  8. Permission — *appPermission="'resource:write'" on add / edit / delete
readonly list = createListPage<Region>({
  endpoint: ApiEndPoints.REGIONS,
  searchColumns: ['name'],
  dialog: RegionDialogComponent,
});

createListPage() owns paging, search, sort, dialogs, delete, loading / error, and unsubscribes when the page is destroyed.

this.http.post(ApiEndPoints.REGIONS, this.form.value).subscribe({
  next: () => this.dialogRef.close(true),
});

Use put(endpoint, body, id) for edits and delete(endpoint, id) for removes. HttpService unwraps { data: … }.


HTTP client & interceptor

HttpService (src/app/@core/services/http.service.ts):

Method Maps to
list GET /:url?page&page_size&order_by&direction&filter
get GET /:url → response.data
getByID GET /:url/:id
post POST /:url
put PUT /:url/:id
delete DELETE /:url/:id

Do not call HttpClient from pages.

Interceptor (src/app/@core/interceptor/http.interceptor.ts):

  • Skips i18n JSON under assets/i18n
  • Refresh + retry on expired access or 401 (once)
  • Maps invalid_params onto form fields via ValidatorService
  • Toasts API errors; 401 logs out; 403 stays on the page and toasts

List / filters / pagination

Tables send a filter query, not search:

filter=name[like]"%erb%"
filter=full_name[like]"%erb%"[or]username[like]"%erb%"[or]phone[like]"%erb%"
Screen Columns
Users / Customers full_name, username, phone
Roles name
Regions / Cities / Currencies name
Settings property, value, description
Activities event, username, ip, uri
Account credits currency
Login blocks key

A numeric query also adds id[eq]"…". Operators: [eq] [ne] [gt] [lt] [gte] [lte] [like] [and] [or].

Helper: likeOrFilter(columns, term) in src/app/@shared/crud/crud.util.ts.

Default list params: page=1, page_size from perPageOptions[0], order_by=id, direction=desc.


Forms, dialogs & unsaved changes

  • Dialogs use dialogSize() (maxWidth / maxHeight for mobile, disableClose: true)
  • UnsavedFormService tracks dirty forms; unsavedFormGuard asks before leave
  • Delete uses confirmDelete() → AlertMessageComponent
  • Field errors from the API (invalid_params) render through ErrorHandlingComponent

Do not bypass the unsaved guard by calling dialogRef.close() on a dirty form without unsaved.tryDiscard().


i18n & RTL

Transloco. Files: src/assets/i18n/{en,ar,ckb}.json.

Code Language Direction
en English LTR
ar Arabic RTL
ckb Central Kurdish RTL

Language switch lives on login and in the header. Choice is stored as dragon-lang. index.html sets lang / dir before bootstrap to avoid a flash.

When you add a string, add it to all three files.

Paginator labels go through AppPaginatorIntl. Snackbars follow RTL (start / end).


Theme

Set theme in the environment to one of:

indigo · violet · teal · rose · amber · forest · ocean

theme: 'forest',

Restart after changing the palette. Users still toggle dark / light from the header; that choice is dragon-color-scheme in localStorage (or prefers-color-scheme on first visit). Palettes live in src/app/@core/theme/app-themes.ts and src/app.scss.


PWA

Production builds register @angular/service-worker (ngsw-config.json). The app shell, icons, i18n, and fonts are cached; /api/** is not. npm start does not register the worker.

Install from the browser on HTTPS or localhost. After a new deploy, a snackbar offers Reload.

Manifest and icons: public/.


Scripts & tests

Command Description
npm start Dev server with API proxy
npm run build Default build
npm test Karma unit tests
npm run test:ci Headless, one run
npm run lint ESLint
npm run format Prettier rewrite of src/
npm run format:check Prettier check
npm run e2e Playwright
npm run e2e:ui Playwright UI
npx playwright install chromium   # first time only
npm run e2e

Playwright starts the dev server and mocks the API.


Contributing

Stars help others find this boilerplate. Issues and PRs welcome.

Topics: angular angular-material admin-dashboard boilerplate jwt rbac pwa transloco playwright


License

MIT — see LICENCE.

Author: Barez Azad

About

Angular 22 admin boilerplate — Material, JWT refresh, RBAC, i18n (en/ar/ckb), CRUD tables, and PWA

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages