16

I have done a full Single Page Application (SPA) application using Angularjs.

So far so good.

As anyone knows, all javascript files are loaded in the first time access. Or, some file are loaded in lazy mode style when needed.

So far so good...

The situation is: the server updates all files (html partials, javascripts, css's) and the client remain with a lot of files out-dated.

This would be simply solved refreshing the browser, hit F5 key, control+f5, or refresh button in the browser. But this concept does not exists when working with SPA.

I'm not sure how to solve this problem.

I could detect somehow (doing a ping maybe) and just to re-load that specific file. With document.write strategy. But now rises another problem, I have a single javascript file with all javascript minified.

I could try to force a full reload in the browser or force to re-login (and reload because login are SPA part).

But reloading is an ugly solution, imagine the client lose all data in the form because he was unlucky the server have just updated. And worse, I must now create some "auto-save" feature just because of this.

I'm not sure how to handle this, if possible, doing in "angular way".

I wonder how google gmail handles this because I stay logged for many many hours without logging of.

Ismael
  • 2,330
  • 1
  • 25
  • 37
  • @TheSharpieOne It seems to be what I needed since the beginning. Much appreciated. – Ismael Sep 12 '13 at 19:38
  • Just ask the user to update? Send out an 'update msg' using sockets then let the user decide when to update (refresh). – Nate-Wilkins Sep 16 '13 at 13:52
  • @Nate I don't think you should let the user to choose the moment to update. Imagine a situation when you must update because new stuffs was added. Stuffs like new rules, new logics, new decision. Well... your solution should work, but in my opinion is not ideal. – Ismael Sep 16 '13 at 19:08
  • @Ismael Ok - Instead of asking your going to have to establish a client state, through cookies, session storage, local storage, application cache or (I believe) you can store to a local db Chrome only maybe? So websockets send update to client, save client state locally (doesn't have to be auto save) 'refresh page' then retrieve client state. I'm going to have to do this soon so when I have code I'll post an answer. – Nate-Wilkins Sep 16 '13 at 23:27

9 Answers9

3

As others have already suggested, keep the logged user on the old version of your webapp.

Not only what you ask is difficult to do in Angular, but it can also lead to a bad user experience and surprising behaviour, since there may not be a mapping between what the user is doing with the old version and what the new version provides. Views may be removed, renamed, split or merged. The behaviour of the same view may have changed, and doing so without notice for the user may cause mistakes.

You made an example with Gmail, but may have noticed that changes to the UI always happen after you logout, never while you're using it.

First of all, if your app is an intranet website used during office time, just update it while nobody is using it. This is a much simpler solution.

Otherwise, if you need to provide 24/24 availability, my suggestion is:

  • When you deploy the new version of your SPA, keep the old version in parallel with the new version, keep the current users on the old version, and log new users to the new version. This can be made in a number of ways depending on your setup, but it's not difficult to do.

  • Keep the old version around until you're confident that nobody is still using it or you're pretty sure that the new version is ok and you don't need to rollback to the old version.

  • The backend services should be backward-compatible with the old version of the frontend. If that's not possible you should keep multiple version of the backend services too.

Marco Righele
  • 2,702
  • 3
  • 23
  • 23
1

As the rest of the guys said a solution can be to versioning your files. So every time that your browser check those files out the browser notice that the files are different to the ones that are in the server so the browser cache the new files.

I suggest to use some build tool like gulp, grunt or webpack, the last one is becoming more popular.

By the moment I use gulp for my projects. I´m moving to webpack though.

if you are interested in gulp you can have a look to gulp-rev and gulp-rev-replace plugins.

What do they do?

let´s say that we have the next file in your project app.js what you get after apply gulp-rev to your project is something like app-4j8888dp.js then your html file where the app.js is injected is still pointing to app.js so you need to replace it. To do that you can use gulp-rev-replace plugin.

eg. gulp task where

var gulp = require('gulp');
var rev = require('gulp-rev');
var revReplace = require('gulp-rev-replace');
var useref = require('gulp-useref');
var filter = require('gulp-filter');
var uglify = require('gulp-uglify');
var csso = require('gulp-csso');

gulp.task("index", function() {
  var jsFilter = filter("**/*.js", { restore: true });
  var cssFilter = filter("**/*.css", { restore: true });
  var indexHtmlFilter = filter(['**/*', '!**/index.html'], { restore: true });

  return gulp.src("src/index.html")
    .pipe(useref())      // Concatenate with gulp-useref
    .pipe(jsFilter)
    .pipe(uglify())             // Minify any javascript sources
    .pipe(jsFilter.restore)
    .pipe(cssFilter)
    .pipe(csso())               // Minify any CSS sources
    .pipe(cssFilter.restore)
    .pipe(indexHtmlFilter)
    .pipe(rev())                // Rename the concatenated files (but not index.html)
    .pipe(indexHtmlFilter.restore)
    .pipe(revReplace())         // Substitute in new filenames
    .pipe(gulp.dest('public'));
});

if you want to know further details see the links bellow.

https://github.com/sindresorhus/gulp-rev https://github.com/jamesknelson/gulp-rev-replace

Daniel Perez
  • 591
  • 1
  • 5
  • 9
0

A single page application is that, a single stack that controls the client logic of your application. Thus, any navigation done through the application should be handled by your client, and not by the server. The goal is to have a one single "fat" HTTP request that loads everything you need, and then perform small HTTP requests.

That's why you can only have one ng-app in your apps. You are not suppose to have multiple and just load the modules you need (although the AngularJS team wants to move that way). In all cases, you should serve the same minified file and handle everything from your application.

It seems to me that you are more worried about the state of your application. As Tom Dale (EmberJS) described in the last Cage Match, we should aim to have applications that can reflect the same data between server and client at any point of time. You have many ways to do so, either by cookies, sessions or local storage.

Usually a SPA communicates with a REST based server, and hence perform idempotent operations to your data.

tl;dr You are not supposed to refresh anything from the server (styles or scripts, for instance), just the data that your application is handling. An initial single load is what SPA is all about.

jjperezaguinaga
  • 2,472
  • 14
  • 20
  • 1
    the main problem isn't just the date, but the controllers with businesses logic for example. Some brand new button created somewhere in the form that will not reflect because partial.html was already loaded. – Ismael Sep 12 '13 at 20:20
  • Mmm your partial.html should reflect the state of your model. If your UI has significant changes respecting to your data, then it should be a model too. That way you can have data that is persisted in the server (user A has 3 buttons, user B has 18) while the view reflects it through AngularJS scope (`$scope.buttons = buttonsService.get()`) – jjperezaguinaga Sep 12 '13 at 20:41
  • 1
    Maybe the updated js can fix a bug in logic that is affecting some clients. Or it an add a new screen (html partial) and a new menu item for it on main html file and all of this should be refreshed so current logged client can use it. – Zote Sep 12 '13 at 20:48
  • @jjperezaguinaga I can understand your point, but technicaly speaking i'm not sure how to design the code very well to treat this. Seem to be a full refactor in my app. Did you see the "@TheSharpieOne" comment? It looks like a possible solution, i'm studing it yet. – Ismael Sep 13 '13 at 11:22
  • @Ismael is a completely different beast, and it's just for real time communication; AngularJS is the client that manipulates the data that is going to be sent through X channel (can be Socket.io, for sure). I might not be understanding your question entirely, what exactly get's outdated in your app? What's your infrastructure? – jjperezaguinaga Sep 13 '13 at 11:26
  • 2
    That's all fine but what will you do when a CSS file is updated? Of a angular controller (.js) file is updated? How do you notify the client that a new version of the app has been deployed and a full refresh is in order? – AlexCode Apr 14 '14 at 09:52
  • What if the SPA changes (JavaScript/AngularJS logic loaded in the browser)? Then what? This is not a good answer and is circled around it that the SPA never changes - which it does. – Gaui Jun 05 '15 at 16:17
0

separate your data and logic and reload the data using ajax whenever you want, for that i will suggest you use REST API to get the data from server.

SPA helps you to reduce the HTTP request again and again but its also require some http request to update a new data to view.

Pushker Yadav
  • 866
  • 1
  • 8
  • 15
0

Well, you would have to unload the old existing code (i.e. the old AngularJS app, modules, controllers, services and so on). Theoretically, you could create a custom (randomized) app name (with all modules have this prefix for each unique build!) and then rebuild your app in the browser. But seriously.. that's a) very complex and b) will probably fail due memory leaks.

So, my answer is: Don't.

Caching issues

I would personally recommend to name/prefix all resources depended by a build with a unique id; either the build id, a scm hash, the timestamp or whatever like that. So, the url to the javascript is not domain.tld/resources/scripts.js but domain.tld/resources-1234567890/scripts.js which ensures that this path's content will never conflict with a (newer) version. You can choose your path/url like you want (depending on the underlaying structure: it is all virtually, can you remap urls, etcpp). It would be even not required that each version will exist forever (i.e. map all resources-(\d+)/ to resources/; however, this would be not nice for the concept of URLs.

Application state

Well, the question is how often will the application change that it would be important that such reloads are required. How long is the SPA open in a browser? Is it really impossible to support two versions at the same time? The client (the app in the browser) could even send its own version within the HTTP requests.

In the beginning of a new application, there are a lot of changes that would require a reload. But soon after your application has a solid state, the changes will be smaller and would not require a reload. The user itself will make more refreshs.. more than we ever expected :/

knalli
  • 1,973
  • 19
  • 31
0

As with what everyone else is saying...

Don't, and while socket.io could work it's asking for trouble if you are VERY careful.

You have two options, upon server update invalidate any previous session (I would also give users a half hours notice or 5 minutes depending on application before maintenance would be done.

The second option is versioning. If they are on version 10, then they communicate with backend 10. If you release version 11 and they are still on 10 then they can still communicate with backend 10.

Remember Google wave? It failed for a reason. Too many people writing one source as the same time causes more problems then it solves.

Jegsar
  • 501
  • 6
  • 22
0

use $state service. create state during loading page using ctor. after specified time re create state and load page.

function(state) {
  state.stateCtor(action);
  $state.transitionTo(action + '.detail', {}, {
    notify: true
  });
}
Alamgir
  • 686
  • 8
  • 14
0

Versioning your files, so on every update increment version number and the browser will update it automaticallly.

omar azmi
  • 11
  • 2
  • Is not that easy, i mean, update de file is not hard. But the consequences are hard to predict and can cause undebugable problems. Contextual variables, etc. The easiest solution is: refresh the browser. And voilà! – Ismael Aug 28 '15 at 11:57
0

My solution consists of several points.

  1. While this is not important, but I send one JavaScript file to the client side, I use grunt to prepare my release. The same grunt file adds to the JavaScript tag a query with version number. Regardless of whether you have one file or lots of files, you need to add a version number to them. You may need to do this for resources as well. (check at the end for an example)

  2. I send back in all my responses from the server (I use node) the version number of the app.

  3. In Angular, when I receive any response, I check the version number against the version number loaded, if it has changed (this means the server code has been updated) then I alert the user that the app is going to reload. I use location.reload(true);

  4. Once reloaded the browser will fetch all new files again because the version number in the script tag is different now, and so it will not get it from cache.

Hope this helps.

<script src="/scripts/spa.min.js?v=0.1.1-1011"></script>
Ralph
  • 1,480
  • 11
  • 16