Showing posts with label angular. Show all posts
Showing posts with label angular. Show all posts

Understanding NgRx in Angular 20: From Basics to Your First Store

Angular 20 · NgRx 20 · Signals

A counter is the smallest app that still uses every part of NgRx. This post walks through the real code behind one: actions, a reducer, selectors, a facade, and a zoneless component that reads the count as a signal.

(click)→ dispatch→ reduce→ store→ select→ render

12 min readAngular 20.3@ngrx/store 20.1

Why use a store for a counter?

You could keep the count in a signal inside the component and stop there. That works until a second component needs the same number, or a bug makes you ask why the number changed. NgRx handles both. It keeps app data in one place, called the Store, and only lets that data change through one path that you can log, inspect and replay.

A bank account is a useful picture. You never edit your balance by hand. You hand in a deposit slip, the bank applies its rules, and a new balance goes into the ledger. When you want to know the balance, you ask for it. Each piece of NgRx has a job like that:

PieceFileIts jobIn the bank
Actioncounter.actions.tsSays what happened, like “Increment was clicked”.Deposit slip
Reducercounter.reducer.tsTakes the current state and an action, returns the next state.The bank’s rules
Stateinside the StoreThe current data, { count: 0 }. One copy for the whole app.The ledger
Selectorcounter.selector.tsReads one piece of state and caches the result.Balance inquiry
Facadecounter.facade.tsA small service that hides NgRx behind plain methods.The teller window
Componentapp.ts, app.htmlShows the count and calls the facade on click.You

The architecture in one picture

This is how the files in the project connect. Writes travel down the left side. Reads come back up the right side.

App component app.ts + app.html · shows count() and calls increment(), decrement(), reset() counterFacade.increment() count: Signal<number> CounterFacade counter.facade.ts · the only class that talks to the Store NgRx Store · provideStore() + provideState('counter', counterReducer) store.dispatch(action) store.selectSignal(selectCount) Action counter.actions.ts [Counter] Increment what happened Reducer counter.reducer.ts count: 0 → 1 returns new state State state.counter { count: 1 } one source of truth Selector counter.selector.ts selectCount → 1 reads one value on() returns read by every action + state action stream Redux DevTools provideStoreDevtools · keeps the last 25 actions Effects provideEffects() · empty for now, HTTP calls go here
Figure 1. The component only talks to the facade. The facade dispatches actions in and reads a signal out. Inside the Store, an action goes to the reducer, the reducer produces new state, and the selector reads it. DevTools and Effects see every action.

Two rules keep this predictable. Data moves in one direction only. And only the reducer creates new state. Everything else either asks for a change by dispatching an action, or reads the result through a selector.

Where each file lives

All NgRx code for the counter sits in one folder, with one file per job. The component and config files outside it only needed small edits.

src/app/
├── app.config.ts               registers the Store
├── app.ts                      component, uses the facade
├── app.html                    template, shows count()
├── app.scss                    button styles
└── counterStore/
    ├── counter.actions.ts      what can happen
    ├── counter.reducer.ts      how state changes
    ├── counter.selector.ts     how to read state
    └── counter.facade.ts       simple API for components

Step 1 · Packages

Install NgRx

NgRx releases follow Angular’s major versions, so an Angular 20 app uses NgRx 20. This project adds three packages:

terminal
npm install @ngrx/store@20 @ngrx/effects@20 @ngrx/store-devtools@20
  • @ngrx/store gives you the Store, actions, reducers and selectors.
  • @ngrx/store-devtools connects the Store to the Redux DevTools browser extension.
  • @ngrx/effects runs side effects such as HTTP calls. The counter doesn’t need one yet, but it is registered so it is ready.

Afterwards, package.json has three new lines:

package.json
     "@angular/router": "^20.3.0",
+    "@ngrx/effects": "^20.1.0",
+    "@ngrx/store": "^20.1.0",
+    "@ngrx/store-devtools": "^20.1.0",
     "rxjs": "~7.8.0",
Side note

This commit also adds "cli": { "analytics": false } to angular.json. That only turns off Angular CLI usage analytics, which is what ng analytics disable writes. It isn’t related to NgRx.

Step 2 · Actions

Describe what can happen

An action is a plain object that says something happened. It always has a type string and can carry extra data. createActionGroup creates every action for one feature in a single call.

src/app/counterStore/counter.actions.ts
import { createActionGroup, emptyProps } from '@ngrx/store';

export const counterActions = createActionGroup({
  source: 'Counter',
  events: {
    Increment: emptyProps(),
    Decrement: emptyProps(),
    Reset: emptyProps(),
  },
});

Each event name turns into a function you can call. NgRx builds the type string from the source and the event name:

You writeIncrement: emptyProps()
You callcounterActions.increment()
The Store receives{ type: '[Counter] Increment' }

emptyProps() means these actions carry no extra data. The [Counter] prefix tells you where an action came from when you read the action log later.

Step 3 · Reducer

Decide how the state changes

The reducer is the only code that creates new state. It is a pure function: give it the current state and an action, and it returns the next state. The same input always gives the same output.

src/app/counterStore/counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { counterActions } from './counter.actions';

export interface countState {
  count: number;
}

export const initialState: countState = {
  count: 0,
};

export const counterFeatureKey = 'counter';

export const counterReducer = createReducer(
  initialState,
  on(counterActions.increment, (state) => ({
    ...state,
    count: state.count + 1,
  })),

  on(counterActions.decrement, (state) => ({
    ...state,
    count: state.count - 1,
  })),

  on(counterActions.reset, (state) => ({
    ...state,
    count: 0,
  })),
);
  • countState describes the shape of this slice of state.
  • initialState is the value before any action runs, so the count starts at 0.
  • counterFeatureKey is the name of this slice in the global state object, so it lives at state.counter.
  • Each on() pairs an action with a function that returns the next state.
  • ...state copies any other fields. The counter only has one today, but this keeps the reducer correct when you add more.

The most common beginner mistake is changing the state object directly. Compare the two versions:

✗ Changes the old object throws in dev mode

wrong
on(counterActions.increment, (state) => {
  state.count++;
  return state;
}),

✓ Returns a new object what this project does

right
on(counterActions.increment, (state) => ({
  ...state,
  count: state.count + 1,
})),

NgRx notices changes by comparing object references. If you change the old object and return it, the reference stays the same, so selectors assume nothing happened and the screen doesn’t update. In development mode NgRx also freezes the state, so the left version throws an error on the first click.

Step 4 · Selectors

Read exactly what you need

The Store keeps one object for the whole app. After the counter registers, it looks like this:

global state
{
  "counter": {
    "count": 0
  }
}

Selectors are functions that read pieces of that object.

src/app/counterStore/counter.selector.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { counterFeatureKey, countState } from './counter.reducer';

export const selectCounterState = createFeatureSelector<countState>(counterFeatureKey);

export const selectCount = createSelector(selectCounterState, (state: countState) => state.count);
  • createFeatureSelector picks the counter slice out of the global state.
  • createSelector takes that slice and returns just the number.
Good to know

Selectors are memoized. If the counter slice is the same object as last time, selectCount returns its cached result without running again. That keeps reads cheap, even when many components use them.

Step 5 · Configuration

Register the Store in app.config.ts

This project is a standalone Angular app with no AppModule, so NgRx is registered with provider functions in app.config.ts.

src/app/app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideState, provideStore } from '@ngrx/store';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { provideEffects } from '@ngrx/effects';
import { counterFeatureKey, counterReducer } from './counterStore/counter.reducer';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZonelessChangeDetection(),
    provideRouter(routes),
    provideStore(),
    provideState(counterFeatureKey, counterReducer),
    provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
    provideEffects(),
  ],
};
ProviderWhat it does
provideStore()Creates the global Store. It starts empty, and features add themselves.
provideState(counterFeatureKey, counterReducer)Adds the counter slice at state.counter.
provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() })Connects to Redux DevTools and keeps the last 25 actions. Watch-only in production builds.
provideEffects()Starts the effects system. There are no effects yet.
provideZonelessChangeDetection()Already in the project. Runs Angular without zone.js, so signals tell Angular when to re-render.
Why a constant

The feature key is used in two places: provideState here and createFeatureSelector in the selector file. Both read the counterFeatureKey constant, so a typo can’t make them disagree.

Step 6 · Facade

Hide the Store behind a facade

A facade is a normal Angular service that sits between components and the Store. Components call its methods and read its signals. They never import actions, selectors or the Store itself.

src/app/counterStore/counter.facade.ts
import { Injectable, inject } from '@angular/core';
import { Store } from '@ngrx/store';

import { counterActions } from './counter.actions';
import { selectCount } from './counter.selector';

@Injectable({ providedIn: 'root' })
export class CounterFacade {
  private readonly store = inject(Store);

  readonly count = this.store.selectSignal(selectCount);

  increment(): void {
    this.store.dispatch(counterActions.increment());
  }

  decrement(): void {
    this.store.dispatch(counterActions.decrement());
  }

  reset(): void {
    this.store.dispatch(counterActions.reset());
  }
}

selectSignal turns a selector into an Angular signal. When the counter state changes, the signal gets the new value, and every template that reads it updates. providedIn: 'root' gives the whole app one shared instance.

Here is the difference from the component’s point of view:

Without a facade the component knows NgRx

app.ts
export class App {
  private store = inject(Store);

  count = this.store.selectSignal(selectCount);

  increment(): void {
    this.store.dispatch(counterActions.increment());
  }
}

With a facade this project

app.ts
export class App {
  private counterFacade = inject(CounterFacade);

  count = this.counterFacade.count;

  increment(): void {
    this.counterFacade.increment();
  }
}
  • The component stays small and only knows about counting.
  • Tests can swap in a fake facade without setting up a Store.
  • If you move to NgRx SignalStore later, only the facade has to change.

Step 7 · Component

Show the count on screen

The component injects the facade and hands its signal and methods to the template.

src/app/app.ts
import { Component, inject, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';

import { CounterFacade } from './counterStore/counter.facade';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.html',
  styleUrls: ['./app.scss'],
})
export class App {
  protected readonly title = signal('angular20_Learning');
  private readonly counterFacade = inject(CounterFacade);

  count = this.counterFacade.count;

  increment(): void {
    this.counterFacade.increment();
  }

  decrement(): void {
    this.counterFacade.decrement();
  }

  reset(): void {
    this.counterFacade.reset();
  }
}
src/app/app.html
<h1>Welcome to Angular 20 Learning</h1>

<p>COUNT: &nbsp; {{ count()}}</p>

<div class="counter">
  <button (click)="decrement()">Decrement</button>
  <button (click)="increment()">Increment</button>
  <button (click)="reset()">Reset</button>
</div>

<router-outlet />

count is the same signal the facade exposes, so the template calls it like a function: {{ count() }}. Each button calls a method that forwards to the facade.

Why signals matter here

This app uses provideZonelessChangeDetection(), so there is no zone.js watching every click and timer. Angular re-renders a component when a signal its template reads has changed. A signal from selectSignal fits that model directly. You don’t need the async pipe, a manual subscribe, or ChangeDetectorRef.

app.scss (button styles only)
src/app/app.scss
.counter {
  display: flex;
  width: 300px;
  justify-content: space-between;
  font-size: 1rem;
  font-weight: bold;
  font-family: system-ui, sans-serif;
}

.counter button {
  background-color: #f0f0f0;
  border: none;
  padding: 10px;
  cursor: pointer;
  font-size: 1rem;
  border-radius: 5px;
}

.counter button:active {
  background-color: #e0e0e0;
}

Follow one click through the app

Now put it all together. This is everything that happens when you click Increment while the count is 0.

Template App CounterFacade Store counterReducer selectCount 1 (click) 2 increment() 3 dispatch(action) 4 (state, action) 5 { count: 1 } 6 state changed 7 count signal: 0 → 1 8 count() → re-render
Figure 2. One click, eight steps. Solid arrows are calls going in. Dashed arrows are values coming back out.
  1. 1

    The button’s (click) binding calls increment() on the App component.

  2. 2

    App forwards the call to counterFacade.increment().

  3. 3

    The facade dispatches counterActions.increment(), which is just { type: '[Counter] Increment' }.

  4. 4

    The Store hands the current state, { count: 0 }, and the action to counterReducer.

  5. 5

    The matching on() returns a new object, { count: 1 }. The Store saves it as state.counter.

  6. 6

    The state changed, so the Store lets its selectors know.

  7. 7

    selectCount reads the new slice, and the count signal now holds 1.

  8. 8

    The template reads count(), gets 1, and Angular re-renders that part of the page.

The Store updates the state synchronously during dispatch. Angular then schedules a re-render, because a signal the template reads has changed.

Try it yourself

This panel runs the same reducer logic in plain JavaScript. Click the buttons and watch the path light up in order, the action log grow, and the state change. Click any log entry to see the state right after it, the way Redux DevTools shows it.

The path of one click
(click) facade method action counterReducer new state selectCount count()
Your app
localhost:4200

Welcome to Angular 20 Learning

COUNT:  0

Last action: none yet

Redux DevTools (simulated) 2 / 25 kept
    State after @ngrx/store/update-reducers
    {
      "counter": {
        "count": 0
      }
    }
    Diff
    + counter: { count: 0 }

    The log drops its oldest entry after 25 actions, like maxAge: 25 in app.config.ts. The two @ngrx entries at the top are NgRx’s own startup actions. update-reducers is the moment provideState adds the counter slice, which is why its diff shows counter appearing.

    Debug with Redux DevTools

    1. Install the Redux DevTools extension for Chrome, Edge or Firefox.
    2. Run ng serve and open http://localhost:4200.
    3. Open the browser’s developer tools and choose the Redux tab.

    The left side lists every action in order: NgRx’s startup actions first, then one [Counter] entry per click. Select an action and use the tabs on the right:

    TabWhat it shows
    ActionThe action object, for example { type: '[Counter] Increment' }.
    StateThe whole state tree after that action.
    DiffOnly what changed, for example count: 1 → 2.

    The slider at the bottom moves back and forth through history. This is called time-travel debugging, and it works because every state is a new object that never changes afterwards.

    Two options in app.config.ts control the connection. maxAge: 25 keeps the last 25 actions, so memory doesn’t grow forever. logOnly: !isDevMode() puts the extension in watch-only mode in production builds.

    Mistakes to avoid

    • Don’tChange state in place, like state.count++.
      DoReturn a new object with the spread operator.
    • Don’tPut side effects in a reducer: HTTP calls, localStorage, Date.now(), Math.random().
      DoMove them to an effect. provideEffects() is already registered for this.
    • Don’tInject the Store into every component.
      DoGo through the facade, so components stay simple and easy to test.
    • Don’tType the feature key as a string in two files.
      DoShare one constant, like counterFeatureKey.
    • Don’tSubscribe to store.select() by hand in a zoneless app.
      DoUse selectSignal and read the signal in the template.
    Naming tip

    TypeScript types usually use PascalCase. Renaming countState to CounterState matches Angular’s own style and makes types easy to spot in a file.

    Where to go next

    The counter covers the core loop. These four small changes each add one new idea on top of it.

    Send data with an action

    Use props when an action needs a value. The event name 'Increment By' becomes counterActions.incrementBy.

    counter.actions.ts · counter.reducer.ts · counter.facade.ts
    // counter.actions.ts
    import { createActionGroup, emptyProps, props } from '@ngrx/store';
    
    export const counterActions = createActionGroup({
      source: 'Counter',
      events: {
        Increment: emptyProps(),
        Decrement: emptyProps(),
        Reset: emptyProps(),
        'Increment By': props<{ amount: number }>(),
      },
    });
    
    // counter.reducer.ts
    on(counterActions.incrementBy, (state, { amount }) => ({
      ...state,
      count: state.count + amount,
    })),
    
    // counter.facade.ts
    incrementBy(amount: number): void {
      this.store.dispatch(counterActions.incrementBy({ amount }));
    }

    Let createFeature write the selectors

    createFeature takes a name and a reducer and generates a selector for every property in the state. With it, you could delete counter.selector.ts.

    counter.reducer.ts
    import { createFeature, createReducer, on } from '@ngrx/store';
    
    export const counterFeature = createFeature({
      name: 'counter',
      reducer: createReducer(
        initialState,
        on(counterActions.increment, (state) => ({ ...state, count: state.count + 1 })),
        on(counterActions.decrement, (state) => ({ ...state, count: state.count - 1 })),
        on(counterActions.reset, (state) => ({ ...state, count: 0 })),
      ),
    });
    
    // Generated for you:
    //   counterFeature.selectCounterState
    //   counterFeature.selectCount
    
    // app.config.ts
    provideState(counterFeature),

    Save the count with an effect

    Effects run after the reducer, so the count they read is already the new one. This one writes it to localStorage on every change, which a reducer must never do.

    src/app/counterStore/counter.effects.ts
    import { inject } from '@angular/core';
    import { Actions, createEffect, ofType } from '@ngrx/effects';
    import { Store } from '@ngrx/store';
    import { tap, withLatestFrom } from 'rxjs';
    
    import { counterActions } from './counter.actions';
    import { selectCount } from './counter.selector';
    
    export const saveCount = createEffect(
      (actions$ = inject(Actions), store = inject(Store)) =>
        actions$.pipe(
          ofType(counterActions.increment, counterActions.decrement, counterActions.reset),
          withLatestFrom(store.select(selectCount)),
          tap(([, count]) => localStorage.setItem('count', String(count))),
        ),
      { functional: true, dispatch: false },
    );
    
    // app.config.ts
    // import * as counterEffects from './counterStore/counter.effects';
    // provideEffects(counterEffects),

    Test the reducer

    Reducers are pure functions, so they are the easiest code in the app to test. No TestBed, no Store, no mocks.

    src/app/counterStore/counter.reducer.spec.ts
    import { counterReducer, initialState } from './counter.reducer';
    import { counterActions } from './counter.actions';
    
    describe('counterReducer', () => {
      it('adds 1 on increment', () => {
        const next = counterReducer(initialState, counterActions.increment());
        expect(next.count).toBe(1);
      });
    
      it('leaves the old state untouched', () => {
        counterReducer(initialState, counterActions.increment());
        expect(initialState.count).toBe(0);
      });
    
      it('goes back to 0 on reset', () => {
        const next = counterReducer({ count: 5 }, counterActions.reset());
        expect(next.count).toBe(0);
      });
    });





    Angular Standalone Routing Explained — provideRouter() Example Without AppModule

    Angular Standalone Routing Explained — How provideRouter() Works Without AppModule

    With Angular 15 and later, the framework introduced a standalone component architecture — making Angular applications cleaner and more modular by removing the need for NgModule. If you're wondering where AppModule and RouterModule.forRoot() disappeared, you're not alone! In this guide, you’ll learn:

    • How routing works in modern Angular
    • How to configure routes with provideRouter()
    • How Angular internally handles navigation
    • A complete working example with standalone components

    Step 1: Your Project Setup

    In new Angular projects (created via )ng new my-app --standalone you’ll typically see these two files:

    app.config.ts

    
    import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
    import { provideRouter } from '@angular/router';
    import { routes } from './app.routes';
    import { provideHttpClient } from '@angular/common/http';
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideHttpClient(),
      ]
    };
    

    app.routes.ts

    
    import { Routes } from '@angular/router';
    
    export const routes: Routes = [];
    

    No AppModule needed — the configuration lives in app.config.ts and app.routes.ts.


    Step 2: Define Routes

    Define routes normally, but now use standalone components:

    
    // app.routes.ts
    import { Routes } from '@angular/router';
    import { HomeComponent } from './home/home.component';
    import { AboutComponent } from './about/about.component';
    
    export const routes: Routes = [
      { path: '', component: HomeComponent },
      { path: 'about', component: AboutComponent },
      { path: '**', redirectTo: '' }
    ];
    

    Step 3: Create Standalone Components

    home.component.ts

    
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-home',
      standalone: true,
      template: `

    Welcome Home!

    `, }) export class HomeComponent {}

    about.component.ts

    
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-about',
      standalone: true,
      template: `

    About Us

    `, }) export class AboutComponent {}

    Step 4: Bootstrap the Application

    Angular now bootstraps your app using bootstrapApplication() instead of AppModule:

    main.ts

    
    import { bootstrapApplication } from '@angular/platform-browser';
    import { AppComponent } from './app/app.component';
    import { appConfig } from './app/app.config';
    
    bootstrapApplication(AppComponent, appConfig)
      .catch(err => console.error(err));
    

    app.component.ts

    
    import { Component } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [RouterOutlet],
      template: `
        

    My Angular App

    `, }) export class AppComponent {}

    Step 5: How provideRouter() Works Internally

    Under the hood, provideRouter(routes) replaces RouterModule.forRoot(routes). It registers all routing-related providers into Angular’s dependency injection (DI) system:

    • Router service for navigation
    • RouterOutlet directive for rendering components
    • RouterLink directive for link handling

    The flow looks like this:

    1. bootstrapApplication() creates the root injector
    2. provideRouter(routes) adds router providers
    3. The Router parses the URL and renders the matching component inside <router-outlet>

    Step 6: Why No AppModule?

    Old AngularNew Standalone
    AppModule with RouterModule.forRoot()app.config.ts with provideRouter(routes)
    bootstrapModule(AppModule)bootstrapApplication(AppComponent, appConfig)
    NgModule imports & declarationsComponent-level imports via standalone: true

    This new approach makes Angular apps faster, simpler, and more tree-shakable.


    Folder Structure

    
    src/
     ├─ app/
     │   ├─ app.component.ts
     │   ├─ app.config.ts
     │   ├─ app.routes.ts
     │   ├─ home/
     │   │   └─ home.component.ts
     │   └─ about/
     │       └─ about.component.ts
     └─ main.ts
    

    Conclusion

    Angular’s new standalone routing system with provideRouter() is the future of Angular development. It removes the need for modules, simplifies setup, and improves app performance.

    Now you can define routes directly, use standalone components, and enjoy a cleaner Angular architecture.

    What is SPA (Single Page Application)? | Angular SPA Explained

    What is SPA (Single Page Application)? | Angular SPA Explained

    Single Page Application (SPA) Explained for Developers

    What is a Single Page Application?

    A Single Page Application (SPA) is a modern web application that loads one HTML page and dynamically updates the content using JavaScript, eliminating full page reloads. Frameworks like Angular, React, and Vue enable this interactive, fast-loading web experience.

    How SPAs Work Compared to Traditional MPAs

    Traditional Multi-Page Application (MPA)

    • Every navigation (for example, /home or /about) sends a new HTTP request to the server.
    • The server responds with a complete HTML page.
    • The browser unloads the old page and loads the new one, causing slower transitions.

    Single Page Application (SPA) Flow

    • The browser loads the base HTML, CSS, and JavaScript files once.
    • Client-side routing is managed by frameworks like the Angular Router.
    • Only content updates dynamically — no new HTML pages are fetched.
    • Data is retrieved as JSON via REST API or GraphQL calls.

    Example Workflow

    When a user visits /home, the server returns index.html and the Angular bundles. Angular renders HomeComponent inside the root component. Navigating to /about loads AboutComponent dynamically without reloading the page.

    APIs deliver any needed data asynchronously through Angular’s HttpClient.

    Frontend and Backend Separation in SPAs

    • Frontend: Handles UI rendering, client-side routing, and user interactions.
    • Backend: Focuses on serving data via REST APIs and handling business logic.

    This separation makes SPAs faster and reduces server workload significantly.

    How SPAs Reduce Server Load

    • One-Time Page Load: The HTML and scripts are loaded once; no repeated full-page requests.
    • Only Data Travels: The browser fetches only required data instead of entire HTML pages.
    • Browser Caching: Cached CSS and JS files make repeat visits lightning-fast.

    The result is quick navigation and minimal server usage, improving performance and SEO ranking.

    SPA vs MPA Summary

    Aspect SPA MPA
    Navigation Client-side routing Server-side routing
    Reloads Single initial load Each route reloads page
    Performance Faster with smooth transitions Slower with full-page reloads
    Server Load Lower (serves only data) Higher (renders full pages)

    Conclusion

    Single Page Applications redefine web speed and user experience by combining client-side rendering with real-time data fetching. Frameworks like Angular make SPAs easy to develop, scale, and optimize for performance and SEO.

    Start building your SPA today — and deliver fast, seamless experiences to your users.

    Enhancing Angular Copy Functionality: A Step-by-Step Guide

    Enhancing Angular Copy Functionality: A Step-by-Step Guide with Dynamic 'Copied!' Message

    Step 1: Install ngx-clipboard
    Install the ngx-clipboard library using npm

    npm install ngx-clipboard

    Step 2: Import ClipboardModule
    In your Angular module, import the ClipboardModule:

    // your-module.module.ts

    import { ClipboardModule } from 'ngx-clipboard';

    @NgModule({
      imports: [
        // other imports
        ClipboardModule,
      ],
      // other module properties
    })
    export class YourModule { }

    Step 3: Update Component HTML
    Update your component's HTML file:

    <!-- your-component.component.html -->
    <button (click)="copyToClipboard()">{{ buttonText }}</button>

    Step 4: Update Component TypeScript
    Update your component's TypeScript file:

    // your-component.component.ts

    import { Component } from '@angular/core';
    import { ClipboardService } from 'ngx-clipboard';

    @Component({
      selector: 'app-your-component',
      templateUrl: './your-component.component.html',
      styleUrls: ['./your-component.component.css']
    })
    export class YourComponent {
      buttonText = 'Copy';

      constructor(private clipboardService: ClipboardService) {}

      copyToClipboard() {
        const numberToCopy = this.obj.number;

        // Use clipboard service to copy the content
        this.clipboardService.copyFromContent(numberToCopy);

        // Change button text to "Copied!" for 2 seconds
        this.buttonText = 'Copied!';
        setTimeout(() => {
          // Revert button text to "Copy" after 2 seconds
          this.buttonText = 'Copy';
        }, 2000);
      }
    }


    Step 5: Add CSS (Optional)
    You can add some styling to enhance the visual feedback. For example, you might want to change the button color when it's in the "Copied!" state. Update your component's CSS file:

    /* your-component.component.css */

    button.copied {
        background-color: lightgreen;
        /* Add any additional styling you want for the "Copied!" state */
    }

    A Deep Dive into Angular modules



    In Angular, the `app.module.ts` file is a key file that plays a central role in organizing and configuring an Angular application.
    simply we can say 
    the `app.module.ts` file is the entry point for configuring and organizing your Angular application. It defines the structure of the module, including its components, services, and other features, and establishes the dependencies between different parts of the application.

    1. NgModule (NgModule decorator):
       - The `app.module.ts` file is typically where you define an Angular module using the `@NgModule` decorator.
       - This decorator provides metadata that Angular uses to understand how to compile, build, and run your application.

    2. Declaration of Components, Directives, and Pipes:
       - Within the `@NgModule` decorator, you declare the components, directives, and pipes that belong to the module.
       - For example, you list all the components created for your application in the `declarations` array.

    3. Imports:
       - The `imports` array is used to import other Angular modules that are needed by components declared in the current module. This allows you to organize your application into feature modules and reuse them across the app.

    4. Providers:
       - The `providers` array is where you specify the services or dependencies that should be available for dependency injection within the module.

    5. Bootstrap Component:
       - The `bootstrap` property specifies the root component of the application. This is the component that Angular will bootstrap when the application starts.

    6. Exports:
       - The `exports` array allows you to export components, directives, and pipes from the current module, making them available for use in other modules.

    7. Entry Components:
       - The `entryComponents` array is used to specify components that are not referenced in the template but need to be dynamically created, such as components created programmatically.


    Example:
    app.module.ts: [file]

    // Import necessary Angular modules
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';

    // Import components, directives, and services
    import { AppComponent } from './app.component';
    import { MyCustomDirective } from './directives/my-custom.directive';
    import { MyPipe } from './pipes/my.pipe';
    import { MyService } from './services/my.service';

    @NgModule({
      // Declarations: Components, directives, and pipes used in this module
      declarations: [
        AppComponent,
        MyCustomDirective,
        MyPipe
      ],
      // Imports: Other modules that are required by components in this module
      imports: [
        BrowserModule
      ],
      // Providers: Services and dependencies available for dependency injection
      providers: [
        MyService
      ],
      // Bootstrap: The root component of the application
      bootstrap: [AppComponent],
      // Exports: Components, directives, and pipes that can be used by other modules
      exports: [
        MyCustomDirective,
        MyPipe
      ],
      // Entry Components: Components created dynamically
      entryComponents: [/* SomeDynamicComponent */]
    })
    export class AppModule { }

    In the above example code:

    - We import necessary Angular modules like `BrowserModule` and `NgModule`.
    - We import components (`AppComponent`), a directive (`MyCustomDirective`), a pipe (`MyPipe`), and a service (`MyService`).
    - The `@NgModule` decorator is used to define the metadata for the module, specifying declarations, imports, providers, bootstrap component, exports, and entry components.
    - The `AppModule` class is exported, indicating that this is the main module of the Angular application.