The Ultimate Guide To The Angular App Shell


The App Shell has overtaken the web industry with a whir.

And it's no wonder because users are demanding that we deliver faster experiences. No one tolerates slow web apps these days. And the demand only accelerates the app shell architecture boom even more.

Slow Angular apps are boring - like watching paint dry on a moist day type of boring.

So if your Angular project needs a reboot why not hop on this band-wagon and use the app shell to...

  • Instantly display a splash screen (or a twirly-whirl-bird spinner😼 ).
  • Make your Angular app load faster.
  • Impress more people.
  • Make your competitors scramble.
  • And juggle 5 geese with one hand (wait, what?).

Geese and juggling aside, here's the complete guide on what an app shell is and how to use it in your Angular app.

What is an app shell?

In a nutshell, the app shell is nothing more then a glorified progressive web app (PWA) with a background service worker that is used to cache the shell of your application.

But what is the shell?

The shell can be any pieces of your application. But in most cases it's the minimal parts of your Angular app that are needed to deliver an initial user experience. The app shell helps give the impression of a fast-loading native app while it loads dynamic content from a remote server.

Here's what an app shell is according to Mozilla's documentation.

The App shell concept is concerned with loading a minimal user interface as soon as possible and then caching it so it is available offline for subsequent visits before then loading all the contents of the app. That way, the next time someone visits the app from the device, the UI loads from the cache immediately and any new content is requested from the server (if it isn’t available in the cache already). - Source

By caching the shell - the pieces of your application that aren't so dynamic - you can slash the load time and boost the performance of your Angular application.

Do you need an Angular app shell?

If you're looking for any kind of strategy to make your Angular application load faster and deliver a more impressive user experience then you probably need an Angular app shell.

Progressive web apps alone have seen wild successes. A good example of this is Starbucks.

According to this Wikipedia page, Starbucks launched a PWA that was 99.84% smaller then it's iOS app. And after releasing they doubled their number of online orders.

Way back in 2017 Twitter decided to release a PWA alternative to their native Android and iOS app. The alternative was 1-3% the size of their native app and it was so successful that they've ditched the native app, leaving the PWA as the only option.

Based on success stories like these why not convert your Angular project into a progressive web app to deliver an app shell?

I see no harm in improving the performance of your Angular app... so how do we use the app shell architecture with our Angular project?

Basic Angular app shell example

The only prerequisite is that your Angular app needs to be using the RouterModule and define a <router-outlet> within your application.

From here, it's so easy you'll swallow your gum in surprise. In the early days this would've been a hard job but thanks to the Angular team creating an app shell is pimple-simple. πŸ‘ πŸ‘ πŸ‘

Here's the command to add an app shell to your Angular app.

ng generate app-shell

You'll notice that this command generated a new component called the AppShellComponent.

This is the component that is used as an app shell so any pieces of your app shell like navigation bars, etc... go here.

Once you've got your shell ready then all you lack yet is the build command.

ng run app-name:app-shell:production

This command will build a production version of your Angular app with the app shell.

How to create an Angular app shell with Universal (Demo App)

For this demo, our Angular app will have an app shell that displays a loading spinner in the middle of the page.

The loading spinner will be displayed immediately and stay there while the application contacts an API server for some fresh data. Then the app shell with the loading spinner will disappear and our app will be ready to use.


The first step is to generate our demo app.

ng new AppShellDemo

The next step is to generate a basic component that loads fake data from an API server.

ng generate component todos

Here's the Typescript code for our new component.

import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

export interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;

  selector: 'app-todos',
  templateUrl: './todos.component.html',
  styleUrls: ['./todos.component.css']
export class TodosComponent implements OnInit {

  todos$: Observable<Todo[]>;

  constructor(private httpClient: HttpClient) { }

  ngOnInit(): void {
    this.todos$ = this.getTodos();

  getTodos(): Observable<Todo[]> {
    return this.httpClient.get<Todo[]>('');

And in our HTML file we'll add the code needed to render the response from the API server.

<h1>Fake Todos</h1>
<table class="table">
            <th scope="col">#</th>
            <th scope="col">Title</th>
        <tr *ngFor="let todo of todos$ | async">
            <th scope="row">{{ }}</th>
            <td> {{ todo.title }}</td>

The next step is to set our new component to be the home page. You'll have to open the app-routing.module.ts file and add a new route like this.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TodosComponent } from './todos/todos.component';

const routes: Routes = [
    path: '',
    component: TodosComponent

  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
export class AppRoutingModule { }

When we run the app we should see a list of fake data from the remote server.


We've finally come to the sauce - adding an app shell to our Angular project.

ng generate app-shell

This will create a new app-shell component for our loading spinner.

Here's the CSS code for the app-shell.component.css file.

.cssload-container {
    position: fixed;
    width: 100%;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-color: rgba(255, 255, 255, 0.7);
    z-index: 9999;

  .cssload-speeding-wheel {
    content: "";
    display: block;
    position: absolute;
    left: 48%;
    top: 40%;
    width: 63px;
    height: 63px;
    margin: 0 auto;
    border: 4px solid rgb(0, 0, 0);
    border-radius: 50%;
    border-left-color: transparent;
    border-right-color: transparent;
    animation: cssload-spin 500ms infinite linear;
    -o-animation: cssload-spin 500ms infinite linear;
    -ms-animation: cssload-spin 500ms infinite linear;
    -webkit-animation: cssload-spin 500ms infinite linear;
    -moz-animation: cssload-spin 500ms infinite linear;

  @keyframes cssload-spin {
    100% {
      transform: rotate(360deg);
      transform: rotate(360deg);

  @-o-keyframes cssload-spin {
    100% {
      -o-transform: rotate(360deg);
      transform: rotate(360deg);

  @-ms-keyframes cssload-spin {
    100% {
      -ms-transform: rotate(360deg);
      transform: rotate(360deg);

  @-webkit-keyframes cssload-spin {
    100% {
      -webkit-transform: rotate(360deg);
      transform: rotate(360deg);

  @-moz-keyframes cssload-spin {
    100% {
      -moz-transform: rotate(360deg);
      transform: rotate(360deg);

And the code for the app-shell.component.html file.

<div class="cssload-container">
    <div class="cssload-speeding-wheel"></div>

Our Angular app now has an app shell and is ready to be deployed. You might notice that if we run the normal ng serve command that the app shell doesn't show. There's no need to worry because that is normal behavior.

Last of all we'll generate a production build.

ng run AppShellDemo:app-shell:production

And now when we serve it with a web server we should see the app shell load with the spinner while it prepares the rest of the application. When the application is ready the app shell disappears and we can see the list of fake data from the API server.


How to create an Angular app shell without Universal (Demo App)

In our last point we used the Angular CLI to generate an app shell.

One of the catch-20's of this method is the reliance on Angular Universal.

How can we create an app shell for our Angular app without depending on Universal?

The quickest way to do this is convert our Angular app into a PWA. It's very easy to do this with the handy @angular/pwa schematics package.

Here's the first command.

ng add @angular/pwa

This will make various changes to your project. Here's a complete list.

  • Adds @angular/service-worker as a dependency.
  • Enables service worker builds in the Angular CLI.
  • Imports and registers the service worker in the app module.
  • Adds a web app manifest.
  • Updates the index.html file to link to the manifest and set theme colors.
  • Adds required icons for the manifest.
  • Creates a config file ngsw-config.json that is used to specify caching behaviors and other settings.

If you open the ngsw-config.json file you'll see the service worker is configured to cache these files...

  • index.html
  • favicon.ico
  • Build artifacts (JS and CSS bundles).
  • Any files inside the assets directory.
  • And more.

So now that we've converted our Angular app into a PWA, how do we create the app shell?

We've just done that, buster.

Since all of our JavaScript and CSS assets are stored in cache after the first load we'll see our service worker load the application immediately and then request dynamic content from our web servers.

Slick, eh? πŸ€“

Is my Angular app a bit shelly?

So what are your thoughts about the app shell architecture?

Are you going to create an app shell for your Angular application?

I'd be tickled to hear your thoughts in the comments below.


Angular Consultant