web staff: services for route persistence
authorBill Erickson <berick@esilibrary.com>
Mon, 2 Dec 2013 18:42:15 +0000 (13:42 -0500)
committerBill Erickson <berick@esilibrary.com>
Mon, 2 Dec 2013 18:42:15 +0000 (13:42 -0500)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
web-staff-log.txt

index d8a91be..88c1693 100644 (file)
@@ -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]
+-----------------------------------------------------------------------------
+<div>Username: {{patron().usrname()}}</div>
+-----------------------------------------------------------------------------
+
 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