From 90af5910d1b825c72f63db39c39c5e56d092f19b Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 2 Dec 2013 13:42:15 -0500 Subject: [PATCH] web staff: services for route persistence Signed-off-by: Bill Erickson --- web-staff-log.txt | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/web-staff-log.txt b/web-staff-log.txt index d8a91be65e..88c1693b7e 100644 --- a/web-staff-log.txt +++ b/web-staff-log.txt @@ -435,9 +435,121 @@ All 5 tests succeeded, even my rigorous $scope.foo='hello' test. Next steps for testing will be to create an environment around the staff client code so we can write some real tests... +2013-12-02 Scopes, Services, and Routing +---------------------------------------- + +If you have two routes like so, + +[source,js] +----------------------------------------------------------------------------- +$routeProvider.when('/circ/patron/1/items_out', {...}); +$routeProvider.when('/circ/patron/1/holds', {...}); +----------------------------------------------------------------------------- + +You can access these two pages by changing your browser's URL or you can +use links/actions in the page to jump between routes using pushstate +routing. + +Pushsate routing allows you to change the browser URL and have the JS +code respond to the change without actually reloading the web page. In +short, it makes page switching faster. It does this in two main ways. + +1. The browser does not have to fetch the page and scripts from the +server (or cache) or re-parse and execute scripts that run at page load +time. + +2. If two pages share data, which is common for route-linked pages, then +it's possible for the second page to access data retrieved by the first, +without having to re-fetch it from the server. + +How do we manage routing and caching with angular scopes and services? + +Any time a path is loaded in Angular, regardless of how the path was +accessed (routing or initial page load), all controllers within the page +are re-initialized with an empty $scope. Naturally, this means that any +data that was loaded into a $scope will no longer be available when the +next route is accessed. + +To persist data across routes, it has to be managed outside of the +controller. The Angular solution is to use a service. Generally +speaking, Angular services provide re-usable chunks of logic that are +scope and controller-agnostic. They are factory classes which produce +singleton objects, instantiated once at page load, and persisted through +all routes. Because of this, they double as a great place to store +route-global data (i.e. data that should be cached between all of our +known routes). + +Some examples, with lots of extra stuff left out: + +[source,js] +----------------------------------------------------------------------------- +.controller('PatronItemsOut', function($scope) { + fetchPatronFromServer(id).then( + function(p) { $scope.patron = p }); +}) + +.controller('PatronHolds', function($scope) { + fetchPatronFromServer(id).then( + function(p) { $scope.patron = p }); +}) +----------------------------------------------------------------------------- + +Here we fetch the patron object from the server every time the +PatronItemsOut or PatronHolds controllers are instantiated. Since +these two controllers are managed within the same angular app, they are +accessible via routing and can theoretically share date. Fetching the +user every time is wasteful. + +Here's an example where we mange user retrieval with a service. + +[source,js] +----------------------------------------------------------------------------- +.factory('patronService', function() { + var service = { + // only cache the last-accessed user. + // caching all accessed users is a recipe for memory leaks + current : null + }; + service.fetchPatron = function(id) { + // on initial page load, service.current will be null + if (service.current && service.current.id() == id) { + // no need to fetch! + } else { + // fetch patron and set service.current = patron + } + } + return service; +}) + +.controller('PatronItemsOut', function($scope, patronService) { + patronService.fetchPatron(id) + $scope.patron = function() { return patronService.current } +}) + +.controller('PatronHolds', function($scope, patronService) { + patronService.fetchPatron(id) + $scope.patron = function() { return patronService.current } +}) +----------------------------------------------------------------------------- + +Now the patron object lives within our patronService Angular service. It +will persist through page routes until we replace the value or reload +the page. Note that we are now accessing our patron at $scope.patron() +instead of $scope.patron, because $scope.patron() is guaranteed to always +return the correct value for the current patron. Angular templates are +perfectly happy to work with functions intead of attributes. + +[source,html] +----------------------------------------------------------------------------- +
Username: {{patron().usrname()}}
+----------------------------------------------------------------------------- + + Future Topics... ---------------- + * My (currently) preferred parent scope, child scope, service pattern + * Promises * Routing vs Loading * Angular Services / _Route Persistence_ * Deep Linking / Managing the _first load_ problem -- 2.11.0