Angular reload page when new version is deployed

When developing a Single Page Application we want to deploy frequent changes, and do not want a user stuck at a stale version. We also do not want to interrupt the user if they are in the middle of a process to refresh the entire website. Here is my current solution to solve this problem:

System Specs/Configuration

  1. Angular 4.0.1
  2. Webpack 2.3.3
  3. Webpack builds go into "/dist" folder
  4. webpack.config.json is in the "/config" folder

Step 1: Use Webpack to generate a hash.json after every build

My webpack config adds different plugins based on environment. It always adds the following plugin to grab the hash:
plugins.push(
    function() {
        this.plugin("done", function(stats) {
            require("fs").writeFileSync(
                path.join(__dirname, "../dist", "hash.json"),
                JSON.stringify(stats.toJson().hash));
        });
    }
);
/dist/hash.json is my version. When any code file changes, that hash will change. The contents of the file look like this: "9363b1ba4e6a8ec5f47c"

Step 2:Write an Angular Service to check current version

We will do a GET to hash.json, and break any caching using current time-stamp as a cache buster.

ServerResponse is a reusable object I use for all http requests so I can handle errors.

common.ts:
export class ErrorItem {
    constructor(public msg: string, public param: string = "") {

    }
}

export class ServerResponse {
    public data: T;
    public errors: ErrorItem[];
    public status: number;
}
version.service.ts:
import { Injectable } from '@angular/core';
import { Http, Headers, Response  } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { ServerResponse } from '../models/common'
@Injectable()

export class VersionService {

    public needsRefresh : boolean = false;

    constructor(private http: Http) {

    }
    getVersion() : Observable> {

        let headers = new Headers();
        headers.append('Content-Type', 'application/json');

        let output = new ServerResponse();

        return this.http
            .get(
                'hash.json?v=' + (new Date()).getTime(),
                { headers }
            )
            .map((res : Response) => {
                output.status = res.status;
                var response = res.json();
                output.data = response;
                output.errors = [];

                return output;
            }).catch(err => {
                output.status = err.status;
                output.data = null;
                return Observable.of(output)
            });

    }
}

Step 3: Periodically check for version update

In my main app component, I call the VersionService and store the latest hash value. If the new value does not match the current value, a refresh is needed. The needsRefresh boolean is stored in the shared singleton service and it can be accessed by any module to get the latest value.

app.component.ts: 
import { Component, OnInit, ViewEncapsulation} from '@angular/core';
import { VersionService } from './shared/services/version.service';

@Component({
    selector: 'app',    
})
export class AppComponent implements OnInit{

    version : string = "";

    constructor(private versionService: VersionService) {

    }

    checkVersion() {
        this.versionService.getVersion().subscribe((result) => {
            if (result.data) {

                if (this.version && this.version != result.data) {
                    this.versionService.needsRefresh = true;
                }
                this.version = result.data;
            }
        });

        setTimeout(() => {
            this.checkVersion();
        }, 15000);
    }

    ngOnInit() {
        this.checkVersion();
    }
}

Step 4: On route change reload the entire page to new route if needsRefresh

I use a a routing module to handle all my routing needs. One of its jobs is to do a full page reload and not just change routes when a new version is deployed. The redirect takes the user to the the page they were trying to go to so the entire process is very smooth for the user.

app.routing.ts:

import { NgModule } from '@angular/core';
import { SharedGlobalModule} from './shared/modules/shared.global.module'
import { Routes, RouterModule, Router, NavigationEnd, ActivatedRouteSnapshot, NavigationStart } from '@angular/router';
import { NgIdleKeepaliveModule } from '@ng-idle/keepalive';

import { PublicComponent} from './public/public.component'
import { PublicRoutes } from './public/public.routing'
import { PublicAuthGuard } from './public/public.guard'
import { SecureComponent} from './secure/secure.component'
import { SecureRoutes } from './secure/secure.routing'
import { SecureAuthGuard } from './secure/secure.guard'
import { VersionService } from './shared/services/version.service'

const routes: Routes = [
    { path: '', redirectTo: '/public/login', pathMatch: 'full' },
    { path: 'public', component: PublicComponent, children: PublicRoutes, canActivate: [PublicAuthGuard] },
    { path: 'secure', component: SecureComponent, children: SecureRoutes, canActivate: [SecureAuthGuard] },
    { path: 'p/:token', redirectTo: '/public/reset/:token', pathMatch: 'full' },
    { path: '**', redirectTo: '/public/login' },
];

@NgModule({
    imports: [RouterModule.forRoot(routes),SharedGlobalModule, NgIdleKeepaliveModule.forRoot()],
    exports: [RouterModule],
    declarations: [PublicComponent,SecureComponent],
    providers: [SecureAuthGuard,PublicAuthGuard,VersionService]
})
export class AppRoutingModule {
    watchRouteChanges() {
        this.router.events.subscribe((val) => {
            if (val instanceof NavigationStart && this.versionService.needsRefresh === true) {
             location.href = val.url;
            }
        });
    }

    constructor (private router : Router, private versionService: VersionService) {
        this.watchRouteChanges();
    }

}

Notes:


  • I am a little hesitant polling every 15 seconds, but it is a tiny static file, so I am currently OK with it.
  • Some code was omitted from the actual files I use in production, to focus on the topic of this post.

Comments

  1. The approach described here provides a practical solution for handling stale versions in Angular single-page applications without unnecessarily interrupting users during active sessions. Generating a build hash with Webpack and storing it in hash.json gives the application a simple way to identify whether the deployed code has changed, while using a timestamp as a cache buster helps ensure the version check retrieves the latest value. Developers working with these concepts can strengthen their foundation through TypeScript Training.

    ReplyDelete
  2. The article also demonstrates how an Angular service can encapsulate version-checking and HTTP-related logic, making the solution reusable within the application. This fits well with Angular’s structured approach to services and dependency injection, especially when applications need background checks without tightly coupling the logic to individual components. An Angular Training course can provide further practical understanding of services, HTTP communication, and application architecture.

    ReplyDelete
  3. For larger applications, deployment-version detection can become an important part of maintaining a reliable user experience, particularly when frequent builds are released. The combination of Webpack-generated hashes, a dedicated version service, and Angular application logic also offers a useful pattern for larger implementations. Exploring Angular Projects can provide additional ideas for applying Angular architecture and deployment-related techniques in complete project scenarios.

    ReplyDelete
  4. Youcine is an entertainment app that provides a simple way to explore movies and TV series across different genres. Users interested in a Youcine download can learn more about the app’s interface, content categories, and device compatibility before getting started.

    ReplyDelete

Post a Comment

Popular posts from this blog

Selenium on Heroku

Angular directory structure for large projects

Vue Multiselect