Skip to content

Commit

Permalink
feat(templateCache): automatically cache template files to prevent fl…
Browse files Browse the repository at this point in the history
…icker 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'
          }
        }
      });
  });
```
  • Loading branch information
perrygovier committed Aug 15, 2014
1 parent 834e2bb commit 944a92b
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 0 deletions.
142 changes: 142 additions & 0 deletions js/angular/service/templateCache.js
Original file line number Diff line number Diff line change
@@ -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 changes: 112 additions & 0 deletions test/html/tabs-cache.html
Original file line number Diff line number Diff line change
@@ -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>
10 changes: 10 additions & 0 deletions test/html/tabs-templates/about.html
Original file line number Diff line number Diff line change
@@ -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>
13 changes: 13 additions & 0 deletions test/html/tabs-templates/facts.html
Original file line number Diff line number Diff line change
@@ -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>
14 changes: 14 additions & 0 deletions test/html/tabs-templates/facts2.html
Original file line number Diff line number Diff line change
@@ -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>
10 changes: 10 additions & 0 deletions test/html/tabs-templates/home.html
Original file line number Diff line number Diff line change
@@ -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>
5 changes: 5 additions & 0 deletions test/html/tabs-templates/nav-stack.html
Original file line number Diff line number Diff line change
@@ -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>
15 changes: 15 additions & 0 deletions test/html/tabs-templates/tabs.html
Original file line number Diff line number Diff line change
@@ -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>
38 changes: 38 additions & 0 deletions test/unit/angular/service/templateCache.unit.js
Original file line number Diff line number Diff line change
@@ -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);
}));
});

0 comments on commit 944a92b

Please sign in to comment.