How to Implement Okta Authentication in Angular Apps
Learn how to integrate Okta authentication in Angular using OAuth 2.0 and OIDC. Step-by-step guide for secure login, token handling, and route protection.angular
Managing large-scale Angular applications can quickly become overwhelming without the right tools and structure. That’s where Nx Workspace comes in — a powerful monorepo tool that simplifies development, boosts performance, and improves collaboration. Whether you're building a modular enterprise app or managing multiple Angular projects, Nx provides a clean and scalable solution.
In this step-by-step guide, we’ll walk you through setting up an Nx Workspace with Angular from scratch. From creating your first workspace to understanding libraries, generators, and dependency graphs, this guide is designed for both beginners and experienced Angular developers looking to enhance their productivity and maintainability.
To follow along, ensure you have:
Node.js V20.1.0 (should be greater than 16.9.0)
Angular CLI v19.2.10 (or later)
Once Node.js is installed, you can proceed to install Angular CLI.
Let’s start working on Project Setup Requirements
To install the nx CLI globally using npm, open a VS Code terminal/console window. If you're using Windows, macOS, or Linux, the following command works the same way:
npm install -g nx to install nx globally
This process might take a few moments as the necessary packages are downloaded and installed.
To create a workspace for angular using npx, use this command. If you are on a Mac or Linux type following command.
npx create-nx-workspace@latest [workspace-name] --preset=angular --strict
Create a new folder for your project (e.g., angular_monorepo) and navigate into it. Open a terminal and execute the following command to scaffold a new nx workspace. The flag strict is for strict type checking. The name of the workspace is TestAngularMonoRepo.
npx create-nx-workspace@latest TestAngularMonoRepo--preset=angular --strict
• Choose an integrated repo for multiple projects or standalone projects? Select integrated monorepo
• Add application name - app-one
• Choose bundler -Es-build
• Choose stylesheet format - css
• Choose to enable server side Rendering - No
• Choose unit test runner - Jest
• Choose test runner to use end to end tests E2E - Playwright
• Choose CI provider - Github actions
After package installation add "strict": true in the compilerOptions tsconfig.base.json file at the root of your workspace. For more granular control, enable specific strict checks individually (e.g., "strictNullChecks": true) in tsconfig.json of project app-one angularCompilerOptions.
Navigate into TestAngularMonoRepo by using command
cd TestAngularMonoRepo
After this run the command to install plugins
Run ‘npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser’
Run ‘npm install --save-dev @nx/angular @nx/linter @nx/storybook’
Run these commands in console window in project TestAngularMonoRepo
To serve the app run ‘npx nx serve app-name’
Run ‘nx serve app-one’
To Lint the app run ‘nx lint app-name’ and for libraries run command ‘nx lint lib-name’
Run ‘nx lint app-one’
To Build the app run ‘nx build app-name’ and for libraries run command ‘nx build lib-name’ and for production build add --configuration=production after the command
Run ‘nx build app-one’
To Test the app run ‘nx test app-name’ and for libraries run command ‘nx test lib-name‘
Run ‘nx test app-one’
For strict type checking check required flags strict:true
are present in compilerOptions in tsconfig.json
of app-one
To create a new angular application by using command nx g @nx/angular:application apps/application-name
Run ‘nx g @nx/angular:application apps/app-two’
• Choose Bundler - esbuild
• Choose server side rendering - No
It will serve on another port if the first project is already serving.
To create a library in feature, shared and ui directory, run command ‘nx g @nx/angular:library libs/directory-folder/lib-name --standalone --buildable’. Buildable flag is used to create the build of libraries.
At first create auth and dashboard libraries in feature directory
Run ‘nx g @nx/angular:library libs/feature/auth --standalone --buildable’
Run ‘nx g @nx/angular:library libs/feature/dashboard --standalone --buildable’
Create models, services, utils and constants library in shared directory
Run ‘nx g @nx/angular:library libs/shared/models --standalone --buildable’
Run ‘nx g @nx/angular:library libs/shared/services --standalone --buildable’
Run ‘nx g @nx/angular:library libs/shared/utils --standalone --buildable’
Run ‘nx g @nx/angular:library libs/shared/constants --standalone --buildable’
Create button and form library in ui directory
Run ‘nx g @nx/angular:library libs/ui/button --standalone --buildable’
Run ‘nx g @nx/angular:library libs/ui/form --standalone --buildable’
For domain-driven folder structure create service in auth library with command
Run ‘nx g @nx/angular:service libs/feature/auth/data-access --project=auth’
To check each library build independently run command ‘nx build <lib-name>’ for each library.
Run ‘nx build auth’
Run ‘nx build dashboard’
Run ‘nx build models’
Run ‘nx build services’
Run ‘nx build utils’
Run ‘nx build constants’
Run ‘nx build button’
Run ‘nx build form’
To check for paths object for libraries in tsconfig.base.json
for all libraries
"paths": {
"@angular-monorepo/auth": ["libs/feature/auth/src/index.ts"],
"@angular-monorepo/button": ["libs/ui/button/src/index.ts"],
"@angular-monorepo/constants": ["libs/shared/constants/src/index.ts"],
"@angular-monorepo/dashboard": ["libs/feature/dashboard/src/index.ts"],
"@angular-monorepo/form": ["libs/ui/form/src/index.ts"],
"@angular-monorepo/models": ["libs/shared/models/src/index.ts"],
"@angular-monorepo/services": ["libs/shared/services/src/index.ts"],
"@angular-monorepo/utils": ["libs/shared/utils/src/index.ts"]
},
To check if library is Tree Shakable
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class DataAccessService {
constructor() {}
}
To create a new component in project app-one run this command
Run ‘nx g @nx/angular:component apps/app-one/src/app/login-page’
To define architectural boundaries in nx.json
add an object named workspaceLayout and define keys for apps directory and library directory.
{
"workspaceLayout": {
"appsDir": "apps",
"libsDir": "libs"
},
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "main",
Define tags for libraries in nx.json
at last of code
"tags": [
"type:ui",
"type:feature",
"type:shared",
"domain:auth",
"domain:dashboard"
],
project.json
of librariesAdd in project.json of form and button library
"tags": ["type:ui"],
Add in project.json of models, utils, constants, services library
"tags": ["type:shared"],
Add in project.json in auth library
"tags": ["type:feature", "domain:auth"],
Add in project.json in dashboard library
"tags": ["type:feature", "domain:dashboard"],
Add in project.json of app-one component
"tags": ["app:one"],
Add in project.json of app-two component
"tags": ["app:two"],
nx.json
as mentioned belowAdd targetDependencies object
"targetDependencies": {
"build": [
{
"target": "build",
"projects": "dependencies"
}
]
},
Add dependencyConstraints object
"dependencyConstraints": [
{
"sourceTag": "domain:auth",
"onlyDependsOnLibsWithTags": ["domain:auth", "type:shared"]
},
{
"sourceTag": "domain:dashboard",
"onlyDependsOnLibsWithTags": ["domain:dashboard", "type:shared"]
},
{
"sourceTag": "type:feature",
"onlyDependsOnLibsWithTags": ["type:feature", "type:shared"]
},
{
"sourceTag": "type:ui",
"onlyDependsOnLibsWithTags": ["type:ui", "type:shared"]
},
{
"sourceTag": "*",
"notDependsOnLibsWithTags": ["domain:auth", "domain:dashboard"],
"onlyDependsOnLibsWithTags": [
"*",
"!domain:auth",
"!domain:dashboard",
"type:shared"
]
}
]
Add linting rules to prevent circular dependencies. First of all run the command
Run ‘npm install -D eslint-plugin-import’
Secondly, in eslint.config.mjs of workspace add rules key for files ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx']
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@typescript-eslint/no-empty-function': 'warn',
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'],
depConstraints: [
{
sourceTag: 'domain:auth',
onlyDependOnLibsWithTags: ['domain:auth', 'type:shared'],
},
{
sourceTag: 'domain:dashboard',
onlyDependOnLibsWithTags: ['domain:dashboard', 'type:shared'],
},
{
sourceTag: 'type:feature',
onlyDependOnLibsWithTags: ['type:feature', 'type:shared'],
},
{
sourceTag: 'type:ui',
onlyDependOnLibsWithTags: ['type:ui', 'type:shared'],
},
{
sourceTag: '*',
notDependOnLibsWithTags: ['domain:auth', 'domain:dashboard'],
onlyDependOnLibsWithTags: [
'*',
'!domain:auth',
'!domain:dashboard',
'type:shared',
],
},
],
},
],
},
},
Add rules in eslint.config.mjs of app-one component
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'],
depConstraints: [
// ... other existing constraints
{
sourceTag: ['app:one'],
onlyDependOnLibsWithTags: [
'app:one',
'domain:auth',
'domain:dashboard',
'type:shared',
],
},
// ... any other constraints
],
},
],
// ... other rules
},
},
To Visualize with Dependency Graph boundaries run
Run ‘nx dep-graph’
To serve an angular app run command ‘nx serve app-name’
Run ‘nx serve app-one’ or ‘nx s app-two’
To build an angular app run command ‘nx build app-name’
Run ‘nx build app-one’
Run ‘nx build app-two’
To build an library run ‘nx build library-name’
Run ‘nx build auth’
Run ‘nx build dashboard’
Run ‘nx build form’
Run ‘nx build button’
Run ‘nx build constants’
Run ‘nx build utils’
Run ‘nx build models’
Run ‘nx build services’
To test an Angular app run ‘nx test app-name’
Run ‘nx test app-one’
Run ‘nx test app-two’
To test an angular library run ‘nx test library-name’
Run ‘nx test auth’
Run ‘nx test dashboard
Run ‘nx test form’
Run ‘nx test button’
Run ‘nx test constants’
Run ‘nx test utils’
Run ‘nx test models’
Run ‘nx test services’
To lint an Angular app run ‘nx lint app-name’
Run ‘nx lint app-one’
Run ‘nx lint app-two’
To lint a library run ‘nx lint library-name’
Run nx lint lib-name
Run nx lint auth
Run nx lint dashboard
Run nx lint form
Run nx lint button
Run nx lint constants
Run nx lint utils
Run nx lint models
Run nx lint services
For consistent formatting run this command to check formatting
Run ‘nx format:check’
And to write formatting
Run ‘nx format:write’
The steps for setup of storybooks are mentioned below
To Integrate Storybook for @libs/ui-* libraries run command ‘nx g @nx/angular:storybook-configuration <your-ui-lib-name>'
Run ‘nx g @nx/angular:storybook-configuration button’
• Do you want to set up Storybook interaction tests? (Y/n) · true
• Automatically generate *.stories.ts files for components declared in this project? (Y/n) · true
• Configure a static file server for the storybook instance? (Y/n) · true
Change the component Selector in button.component.ts
to '[lib-button]' to change the selector to attribute
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: '[lib-button]',
imports: [CommonModule],
templateUrl: './button.component.html',
styleUrl: './button.component.css',
If you face linting error in es.lint.mjs
in button library then change the value of type attribute to [‘element’, ‘attribute’] inside @angular-eslint/component-selector
{
files: ['**/*.ts'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'lib',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: '["element", "attribute"] ',
prefix: 'lib',
style: 'kebab-case',
},
],
},
},
Next step to add this data in button.component.stories.ts to create 4 types of styling
import {moduleMetadata, Meta, StoryFn } from '@storybook/angular';
import { ButtonComponent } from './button.component';
export default {
title: 'ui-button',
component: ButtonComponent,
decorators:[
moduleMetadata({
imports:[],
}),
],
} as Meta<ButtonComponent>;
const Template : StoryFn<ButtonComponent> = (args:ButtonComponent)=>({
template:'<button lib-button [type]="type">Button Text</button>',
props : args
})
export const Basic = Template.bind({})
Basic.args= {
type:'basic'
}
export const Stroked = Template.bind({})
Stroked.args= {
type:'stroked'
}
export const Flat = Template.bind({})
Flat.args= {
type:'flat'
}
export const Raised = Template.bind({})
Raised.args= {
type:'raised'
}
Add selector <ng-content></ng-content> to button.component.html
<ng-content></ng-content>
Add code to button.component.css to define different designs for basic, stroked, raised and flat.
:host{
height: 40px;
padding: 0 24px;
font-size: 1rem;
border-radius: 5px;
box-shadow: none;
color: black;
&.rm-basic{
background: none;
border: none;
}
&.rm-stroked{
border: 1px solid grey;
background: none;
}
&.rm-raised{
border: none;
background: white;
box-shadow: 1px 2px 5px 1px grey;
}
&.rm-flat{
border: none;
background: rgb(235, 230, 230);
}
}
Add the code to button.component.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: '[lib-button]',
imports: [CommonModule],
templateUrl: './button.component.html',
styleUrl: './button.component.css',
changeDetection:ChangeDetectionStrategy.OnPush,
host:{
'[class.rm-flat]':'type==="flat"',
'[class.rm-raised]':'type==="raised"',
'[class.rm-stroked]':'type==="stroked"',
'[class.rm-basic]':'type==="basic"'
}
})
export class ButtonComponent{
@Input() type : 'flat' | 'basic' | 'raised' | 'stroked' = 'raised'
constructor(){}
}
Configure CI to validate Storybook builds add this to .github/workflows/ci.yml
after main job
storybook:
runs-on: ubuntu-latest
needs: main
strategy:
matrix:
ui_lib: ['button']
steps:
- uses: actions/checkout@v4
with:
filter: tree:0
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci --legacy-peer-deps
- name: Build Storybook for ${{ matrix.ui_lib }}
run: npx nx build-storybook ${{ matrix.ui_lib }}
To verify components display of story book run the following command
Run ‘nx run button:storybook’
To confirm that there is no storybook build errors run the this command
Run ‘nx run button:build-storybook’
Provided a sample CI configuration using github actions that runs build, test, lint, and format checks. Runs Storybook build (optional).
name: CI
on:
push:
branches:
- main
pull_request:
permissions:
actions: read
contents: read
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
filter: tree:0
fetch-depth: 0
# This enables task distribution via Nx Cloud
# Run this command as early as possible, before dependencies are installed
# Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun
# Uncomment this line to enable task distribution
# - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci"
# Cache node_modules
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci --legacy-peer-deps
- run: npx playwright install --with-deps
- uses: nrwl/nx-set-shas@v4
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
# - run: npx nx-cloud record -- echo Hello World
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
# When you enable task distribution, run the e2e-ci task instead of e2e
- run: npx nx affected -t lint test build e2e --base=main --head=HEAD
storybook:
runs-on: ubuntu-latest
needs: main
strategy:
matrix:
ui_lib: ['button']
steps:
- uses: actions/checkout@v4
with:
filter: tree:0
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci --legacy-peer-deps
- name: Build Storybook for ${{ matrix.ui_lib }}
run: npx nx build-storybook ${{ matrix.ui_lib }}
Push to the Git repo and trigger CI pipeline and confirm pipeline runs all configured steps successfully.
Nx is an open source build system that provides tools and techniques to manage monorepo. Monorepo is a repository that contains multiple different projects. It is typescript based monorepo tool, primarily supporting Node Js based frontend, backend apps, and libraries with a single code base. It provides linting, formatting, testing and builds.
Learn how to integrate Okta authentication in Angular using OAuth 2.0 and OIDC. Step-by-step guide for secure login, token handling, and route protection.angular
Learn how to integrate IdentityServer with Angular for secure authentication and authorization. Step-by-step guide using OAuth2, OIDC, and token handling.
Create dynamic function-based redirects in Angular using Nx CLI. Supports Angular v19+ and Node.js v20+ for scalable and smart navigation.
Get in touch with Prishusoft – your trusted partner for custom software development. Whether you need a powerful web application or a sleek mobile app, our expert team is here to turn your ideas into reality.