Single page web app using AngularJs

An AngularJS single-page application (SPA) can keep one document loaded while ngRoute swaps partial templates inside ng-view. I exercised the finished Home and About routes in Chromium with AngularJS 1.8.3, confirmed that the document-navigation count stayed at 1, reloaded the About URL, and checked the fallback route.

Check the AngularJS support boundary first

Google ended AngularJS support in January 2022, and the npm packages carry the same end-of-support notice, so use this example to understand or maintain an existing AngularJS application rather than to choose a framework for a new production project.

If you are starting a new application, use the actively supported Angular documentation or another maintained framework. The AngularJS support page names the archived repositories and extended support options for systems that cannot migrate yet.

How ngRoute keeps one document loaded

The browser loads index.html as the application shell, then a hash route such as #!/about changes the client-side URL so the route service can select about.html and insert it into the ng-view element.

PieceJob in the application
ngRouteAdds routing and deep-linking services to the AngularJS module.
$routeProviderMaps each client-side path to a template and optional controller.
ng-viewMarks the part of the shell where the matched template appears.
otherwise()Redirects an unknown path to a safe default route.

The shell, navigation, styles, and loaded JavaScript remain in place during a route change, and only the view region receives a new template.

Create the project files

The example uses one shell, one route configuration, and two partial templates, which stay in the same directory so the template URLs resolve from the local web server.

  • index.html contains the application shell and ng-view.
  • app.js registers the module, routes, and controllers.
  • home.html contains the Home route template.
  • about.html contains the About route template.

Install AngularJS and ngRoute

Create a package manifest, then install both packages without a version pin. npm returned 1.8.3 for each package during the July 2026 execution.

npm init -y
npm install --no-save --package-lock=false angular angular-route

The deprecation warning is expected because AngularJS no longer receives fixes. The install remains useful for reproducing a legacy application without relying on an old CDN path.

Build the application shell

The script order matters because angular-route depends on the AngularJS core, and ng-view stays empty until a route matches.

<!doctype html>
<html lang="en" ng-app="spaDemo">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>AngularJS single-page app</title>
  <link rel="icon" href="data:,">
  <style>
    body { font: 18px/1.5 system-ui, sans-serif; margin: 0; background: #f4f7fb; color: #172033; }
    main { max-width: 760px; margin: 64px auto; padding: 32px; background: white; border-radius: 16px; box-shadow: 0 10px 30px rgba(23,32,51,.12); }
    nav { display: flex; gap: 12px; margin-bottom: 28px; }
    nav a { color: #2457d6; padding: 8px 14px; border: 1px solid #b9c8ee; border-radius: 8px; text-decoration: none; }
    nav a:hover { background: #eef3ff; }
    [ng-view] { min-height: 150px; }
    .eyebrow { color: #64708a; text-transform: uppercase; letter-spacing: .08em; font-size: 13px; }
  </style>
  <script src="node_modules/angular/angular.min.js"></script>
  <script src="node_modules/angular-route/angular-route.min.js"></script>
  <script src="app.js"></script>
</head>
<body>
  <main>
    <p class="eyebrow">AngularJS ngRoute demo</p>
    <nav aria-label="Primary navigation">
      <a href="#!/">Home</a>
      <a href="#!/about">About</a>
    </nav>
    <div ng-view></div>
  </main>
</body>
</html>

The href values begin with #! because AngularJS 1.8.3 uses an exclamation mark as its default hash prefix. This keeps route changes inside the browser instead of requesting a new document from the server.

Map routes to templates

Register ngRoute as a module dependency before injecting $routeProvider into config(), where each route maps a URL fragment to a template and controllerAs exposes the controller instance as page.

angular.module('spaDemo', ['ngRoute'])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'home.html',
        controller: 'HomeController',
        controllerAs: 'page'
      })
      .when('/about', {
        templateUrl: 'about.html',
        controller: 'AboutController',
        controllerAs: 'page'
      })
      .otherwise({
        redirectTo: '/'
      });
  })
  .controller('HomeController', function () {
    this.title = 'Home';
    this.message = 'The shell stayed loaded while ngRoute rendered this view.';
  })
  .controller('AboutController', function () {
    this.title = 'About';
    this.message = 'The URL changed and ng-view received a different template.';
  });

The fallback route prevents a blank view when the fragment does not match Home or About. It redirects to the root route, so an invalid URL returns to a known screen.

Add the route templates

A partial template contains only the markup for the view region because AngularJS inserts it inside the existing document.

<section>
  <h1>{{ page.title }}</h1>
  <p>{{ page.message }}</p>
</section>

<section>
  <h1>{{ page.title }}</h1>
  <p>{{ page.message }}</p>
</section>

Both templates read title and message from the controller assigned by $routeProvider, and the same view can use the techniques in the AngularJS two-way data binding tutorial when you need fields that update immediately.

Run the app through an HTTP server

Serve index.html over HTTP because ngRoute fetches templateUrl files through browser requests that file origins commonly block.

npx http-server . -p 4173

Open http://127.0.0.1:4173 and select About to reach http://127.0.0.1:4173/#!/about while the same document remains loaded.

AngularJS ngRoute demo showing the About view inside the single-page application
The About route rendered inside ng-view while the AngularJS application shell stayed loaded.

Verify navigation, reloads, and fallback behavior

The browser run checked more than the visible heading. It clicked About, compared the document-navigation count before and after the route change, reloaded the About fragment, and opened an undefined route.

{
  "homeHeading": "Home",
  "aboutHeading": "About",
  "aboutUrl": "http://127.0.0.1:4173/#!/about",
  "documentNavigations": "1 -> 1",
  "reloadPreservedRoute": "About",
  "unknownRouteRedirectedTo": "Home",
  "browserErrors": []
}

The 1 to 1 navigation count shows that the About click did not create a second document navigation, and the remaining checks confirm that reload preserved About before otherwise() returned the missing route to Home.

Understand the failure boundaries

Hash routing works with a static server because the browser requests index.html before it interprets the fragment. With HTML5 mode and paths such as /about, the web server must return index.html for unknown application routes or a direct reload will produce a 404 response.

Because AngularJS receives no official security fixes, a legacy deployment needs compensating controls, dependency review, browser testing, and a migration plan, especially when templates process untrusted data.

Choose the next step

Keep this ngRoute setup when you need a small, inspectable reproduction for an AngularJS codebase, or start with the broader AngularJS tutorial if modules, controllers, and dependency injection are unfamiliar.

For a maintained application, list every route, controller, template, and server fallback before choosing a migration sequence so the replacement preserves the same URLs and user flows.

Is AngularJS still supported?

No. Google states that AngularJS support officially ended in January 2022. Existing applications can keep running, but they do not receive official framework fixes.

Why does the AngularJS route use #!?

AngularJS 1.8.3 uses the exclamation mark as the default hash prefix. The fragment lets ngRoute change views without asking the server for a new document.

Why must I run a local HTTP server?

The templateUrl setting loads partial templates through browser requests. Serving the directory over HTTP avoids the file-origin restrictions that can block those requests.

What does ng-view do?

ng-view is the placeholder where ngRoute inserts the template for the matched route. The surrounding application shell remains loaded.

Should I use AngularJS for a new single-page application?

No. Use an actively maintained framework for a new production application. This example fits legacy maintenance, debugging, and migration preparation.

Pankaj Kumar
Pankaj Kumar

Pankaj Kumar is the founder and CEO of CodeForGeek, with more than 14 years in IT. He is an open-source enthusiast who enjoys sharing what he learns through CodeForGeek and YouTube, with a focus on Python, data analytics, machine learning, Angular, Node.js, and Kafka.

Articles: 335