feat(templateCache): automatically cache template files to prevent flicker on page navigation and improve performance

State templates are cached automatically, but you can optionally cache other templates.
```js
$ionicTemplateCahce('myNgIncludeTemplate.html');
```

Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
in the $state definition
```js
$ionicTemplateCahce('myNgIncludeTemplate.html');
```js
  angular.module('myApp', ['ionic'])
  .config(function($stateProvider, $ionicConfigProvider) {

    // disable preemptive template caching globally
    $ionicConfigProvider.prefetchTemplates(false);

    // disable individual states
    $stateProvider
      .state('tabs', {
        url: "/tab",
        abstract: true,
        prefetchTemplate: false,
        templateUrl: "tabs-templates/tabs.html"
      })
      .state('tabs.home', {
        url: "/home",
        views: {
          'home-tab': {
            prefetchTemplate: false,
            templateUrl: "tabs-templates/home.html",
            controller: 'HomeTabCtrl'
          }
        }
      });
  });
```
This commit is contained in:
Perry Govier
2014-08-15 15:10:19 -05:00
parent 834e2bb173
commit 944a92b08d
9 changed files with 359 additions and 0 deletions

142
js/angular/service/templateCache.js vendored Normal file
View File

@@ -0,0 +1,142 @@
// closure to keep things neat
(function() {
var templatesToCache = [];
/**
* @ngdoc service
* @name $ionicTemplateCache
* @module ionic
* @description An angular service that preemptively caches template files to reduce flicker and boost performance.
* @usage
* State templates are cached automatically, but you can optionally cache other templates.
* ```js
* $ionicTemplateCahce('myNgIncludeTemplate.html');
* ```
*
* Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
* in the $state definition
* ```js
* $ionicTemplateCahce('myNgIncludeTemplate.html');
* ```js
* angular.module('myApp', ['ionic'])
* .config(function($stateProvider, $ionicConfigProvider) {
*
* // disable preemptive template caching globally
* $ionicConfigProvider.prefetchTemplates(false);
*
* // disable individual states
* $stateProvider
* .state('tabs', {
* url: "/tab",
* abstract: true,
* prefetchTemplate: false,
* templateUrl: "tabs-templates/tabs.html"
* })
* .state('tabs.home', {
* url: "/home",
* views: {
* 'home-tab': {
* prefetchTemplate: false,
* templateUrl: "tabs-templates/home.html",
* controller: 'HomeTabCtrl'
* }
* }
* });
* });
* ```
*/
IonicModule
.factory('$ionicTemplateCache', [
'$http',
'$templateCache',
'$timeout',
'$ionicConfig',
function($http, $templateCache, $timeout, $ionicConfig) {
var toCache = templatesToCache,
hasRun = false;
/**
* @ngdoc method
* @name $ionicTemplateCache
* @description Add template(s) to be cached.
* @param {string | array} string or array of strings of templates to cache.
*/
function $ionicTemplateCache(templates){
if(toCache.length > 500) return false;
if(typeof templates === 'undefined')return run();
if(isString(templates))templates = [templates];
forEach(templates, function(template){
toCache.push(template);
});
// is this is being called after the initial IonicModule.run()
if(hasRun) run();
}
// run through methods - internal method
var run = function(){
if($ionicConfig.prefetchTemplates === false)return
//console.log('prefetching', toCache);
//for testing
$ionicTemplateCache._runCount++;
hasRun = true;
// ignore if race condition already zeroed out array
if(toCache.length === 0)return;
//console.log(toCache);
var i = 0;
while ( i < 5 && (template = toCache.pop()) ) {
// note that inline templates are ignored by this request
if (isString(template)) $http.get(template, { cache: $templateCache });
i++;
}
// only preload 5 templates a second
if(toCache.length)$timeout(function(){run()}, 1000);
};
// exposing for testing
$ionicTemplateCache._runCount = 0;
// default method
return $ionicTemplateCache;
}])
/**
* @ngdoc config
* @name $ionicTemplateCache
* @module ionic
* @private
*
* Intercepts the $stateprovider.state() command to look for templateUrls that can be cached
*/
.config([
'$stateProvider',
'$ionicConfigProvider',
function($stateProvider, $ionicConfigProvider) {
var stateProviderState = $stateProvider.state;
$stateProvider.state = function(stateName, definition) {
// don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all
if($ionicConfigProvider.prefetchTemplates() !== false){
var enabled = definition.prefetchTemplate != false;
if(enabled && isString(definition.templateUrl))templatesToCache.push(definition.templateUrl);
if(angular.isObject(definition.views)){
for (var key in definition.views){
enabled = definition.views[key].prefetchTemplate != false;
if(enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);
}
}
}
return stateProviderState.call($stateProvider, stateName, definition);
};
}])
/**
* @ngdoc run
* @name $ionicTemplateCache
* @module ionic
* @private
*
* process the templateUrls collected by the $stateProvider, adding them to the cache
*/
.run(function($ionicTemplateCache) {
$ionicTemplateCache();
});
})();

112
test/html/tabs-cache.html Normal file
View File

@@ -0,0 +1,112 @@
<html ng-app="ionicApp">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title>Tabs Example</title>
<link href="../../../../dist/css/ionic.css" rel="stylesheet">
</head>
<body>
<ion-nav-bar class="nav-title-slide-ios7 bar-positive">
<ion-nav-back-button class="button-icon ion-arrow-left-c">
</ion-nav-back-button>
</ion-nav-bar>
<ion-nav-view animation="slide-left-right"></ion-nav-view>
<script id="tabs-templates/contact.html" type="text/ng-template">
<ion-view title="Contact">
<ion-content>
<div class="list">
<div class="item">
@IonicFramework
</div>
<div class="item">
@DriftyTeam
</div>
</div>
</ion-content>
</ion-view>
</script>
<script src="../../../../dist/js/ionic.bundle.js"></script>
<script>
angular.module('ionicApp', ['ionic'])
.config(function($stateProvider, $ionicConfigProvider) {
$ionicConfigProvider.prefetchTemplates(false);
$stateProvider
.state('tabs', {
url: "/tab",
abstract: true,
templateUrl: "tabs-templates/tabs.html"
})
.state('tabs.home', {
url: "/home",
views: {
'home-tab': {
templateUrl: "tabs-templates/home.html",
controller: 'HomeTabCtrl'
}
}
})
.state('tabs.facts', {
url: "/facts",
views: {
'home-tab': {
templateUrl: "tabs-templates/facts.html"
}
}
})
.state('tabs.facts2', {
url: "/facts2",
views: {
'home-tab': {
templateUrl: "tabs-templates/facts2.html"
}
}
})
.state('tabs.about', {
url: "/about",
views: {
'about-tab': {
templateUrl: "tabs-templates/about.html"
}
}
})
.state('tabs.navstack', {
url: "/navstack",
views: {
'about-tab': {
templateUrl: "tabs-templates/nav-stack.html",
prefetchTemplate:false
}
}
})
.state('tabs.contact', {
url: "/contact",
views: {
'contact-tab': {
templateUrl: "tabs-templates/contact.html"
}
}
});
$urlRouterProvider.otherwise("/tab/home");
})
.controller('HomeTabCtrl', function($scope, $ionicTemplateCache) {
$ionicTemplateCache('test');
});
</script>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<ion-view title="About">
<ion-content class="padding">
<h3>Create hybrid mobile apps with the web technologies you love.</h3>
<p>Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components for building highly interactive apps.</p>
<p>Built with Sass and optimized for AngularJS.</p>
<p>
<a class="button icon icon-right ion-chevron-right" href="#/tab/navstack">Tabs Nav Stack</a>
</p>
</ion-content>
</ion-view>

View File

@@ -0,0 +1,13 @@
<ion-view title="Facts">
<ion-content class="padding">
<p>Banging your head against a wall uses 150 calories an hour.</p>
<p>Dogs have four toes on their hind feet, and five on their front feet.</p>
<p>The ant can lift 50 times its own weight, can pull 30 times its own weight and always falls over on its right side when intoxicated.</p>
<p>A cockroach will live nine days without it's head, before it starves to death.</p>
<p>Polar bears are left handed.</p>
<p>
<a class="button icon ion-home" href="#/tab/home"> Home</a>
<a class="button icon icon-right ion-chevron-right" href="#/tab/facts2">More Facts</a>
</p>
</ion-content>
</ion-view>

View File

@@ -0,0 +1,14 @@
<ion-view title="Also Factual">
<ion-content class="padding">
<p>111,111,111 x 111,111,111 = 12,345,678,987,654,321</p>
<p>1 in every 4 Americans has appeared on T.V.</p>
<p>11% of the world is left-handed.</p>
<p>1 in 8 Americans has worked at a McDonalds restaurant.</p>
<p>$283,200 is the absolute highest amount of money you can win on Jeopardy.</p>
<p>101 Dalmatians, Peter Pan, Lady and the Tramp, and Mulan are the only Disney cartoons where both parents are present and don't die throughout the movie.</p>
<p>
<a class="button icon ion-home" href="#/tab/home"> Home</a>
<a class="button icon ion-chevron-left" href="#/tab/facts"> Scientific Facts</a>
</p>
</ion-content>
</ion-view>

View File

@@ -0,0 +1,10 @@
<ion-view title="Home">
<ion-content class="padding">
<p>Example of Ionic tabs. Navigate to each tab, and
navigate to child views of each tab and notice how
each tab has its own navigation history.</p>
<p>
<a class="button icon icon-right ion-chevron-right" href="#/tab/facts">Scientific Facts</a>
</p>
</ion-content>
</ion-view>

View File

@@ -0,0 +1,5 @@
<ion-view title="Tab Nav Stack">
<ion-content class="padding">
<p><img src="http://ionicframework.com/img/diagrams/tabs-nav-stack.png" style="width:100%"></p>
</ion-content>
</ion-view>

View File

@@ -0,0 +1,15 @@
<ion-tabs class="tabs-icon-top tabs-positive">
<ion-tab title="Home" icon="ion-home" href="#/tab/home">
<ion-nav-view name="home-tab"></ion-nav-view>
</ion-tab>
<ion-tab title="About" icon="ion-ios7-information" href="#/tab/about">
<ion-nav-view name="about-tab"></ion-nav-view>
</ion-tab>
<ion-tab title="Contact" icon="ion-ios7-world" ui-sref="tabs.contact">
<ion-nav-view name="contact-tab"></ion-nav-view>
</ion-tab>
</ion-tabs>

View File

@@ -0,0 +1,38 @@
describe('$ionicTemplateCache', function() {
beforeEach(module('ionic'));
beforeEach(inject(function($ionicTemplateCache, $templateCache) {
ionicTemplateCache = $ionicTemplateCache;
templateCache = $templateCache;
}));
it('should run during initial startup', function () {
var info = templateCache.info();
expect(info.size).toBe(0);
expect(ionicTemplateCache._runCount).toBe(1);
});
it('should run immediately after a new addition after initial run', inject(function ($templateCache, $httpBackend, $timeout) {
$httpBackend.whenGET("/test").respond([{hello:"world"}]);
ionicTemplateCache('/test');
$timeout.flush();
var info = templateCache.info();
expect(info.size).toBe(1);
expect(ionicTemplateCache._runCount).toBe(2);
}));
it('should cache 5 templates at a time', inject(function ($httpBackend, $timeout) {
$httpBackend.whenGET("/test1").respond([{hello:"world"}]);
$httpBackend.whenGET("/test2").respond([{hello:"world"}]);
$httpBackend.whenGET("/test3").respond([{hello:"world"}]);
$httpBackend.whenGET("/test4").respond([{hello:"world"}]);
$httpBackend.whenGET("/test5").respond([{hello:"world"}]);
$httpBackend.whenGET("/test6").respond([{hello:"world"}]);
$httpBackend.whenGET("/test7").respond([{hello:"world"}]);
ionicTemplateCache(['/test1','/test2','/test2','/test3','/test4','/test5','/test6','/test7']);
$timeout.flush();
var info = templateCache.info();
expect(info.size).toBe(7);
expect(ionicTemplateCache._runCount).toBe(3);
}));
});