print all data in paginated table/grid

direct Tabular data display

Recently our project has a page need to show tabular data from 30-6000 rows with 3-4 columns. At first, I thought this is pretty reasonable data to show in one page so I just throw the data into a ng-repeat table with my own implementation of filtering/sorting which is pretty straightforward in angular. Every time user select new category/type, fetch data from backend and replace the data in vm/$scope. Also with this implementation, it is quite easy to fulfill our another requirement which is export/print the page content. For export I just need to get the DOM content to the server and return as downloadable. For print, even easier, just call window.print() ,that’s it.

Performance issue with IE

Everything works fine until  our QA hits IE which is super slow when the data in the list is replaced from backend. Did some profiling in IE11, turns out the appendChild and removeChild calls are taking forever when it tries to clear the rows in the dom and put the new elements into dom. Also another slowness is from styleCalculation which it does for every column/row. Overall, IE takes 20s to render a page with 5000 rows and FF/safari/chrome need only 1-2 seconds. This forces us to abandon the straightforward way to use the more IE friendly way which is pagination with angular ui-grid. But this brings us to another problem which is print since data is now paginated and DOM only has 20 rows.

Server side render and client side print

What I eventually did is sending the model data back to server and do server side rendering and eventually send back to browser where an iFrame is created on the fly for printing. The pros of doing this is we have a lot of flexibility on content/layout by whatever manipulation/styling etc… The cons is we added more stuff to the stack and one more round trip comparing to the direct print.

server side

So on server side, when we get the REST call for print, we have a Thymeleaf template there for generating the html. I compared different java server side rendering engines like Velocity/Freemaker/Rythm etc, looks like Thymeleaf has the best Spring integration and most active development/release.

@Configuration
public class ThymeleafConfig
{
    @Autowired
    private Environment env;

    @Bean
    @Description("Thymeleaf template rendering HTML ")
    public ClassLoaderTemplateResolver exportTemplateResolver() {
        ClassLoaderTemplateResolver exportTemplateResolver = new ClassLoaderTemplateResolver();
        exportTemplateResolver.setPrefix("thymeleaf/");
        exportTemplateResolver.setSuffix(".html");
        exportTemplateResolver.setTemplateMode("HTML5");
        exportTemplateResolver.setCharacterEncoding(CharEncoding.UTF_8);
        exportTemplateResolver.setOrder(1);
        //for local development, we do not want template being cached so that we could do hot reload.
        if ("local".equals(env.getProperty("APP_ENV")))
        {
            exportTemplateResolver.setCacheable(false);
        }
        return exportTemplateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        final SpringTemplateEngine engine = new SpringTemplateEngine();
        final Set<ITemplateResolver> templateResolvers = new HashSet<>();
        templateResolvers.add(exportTemplateResolver());
        engine.setTemplateResolvers(templateResolvers);
        return engine;
    }
}

With the engine we confined, we could used like:

            Context context = new Context();
            context.setVariable("firms", firms);
            context.setVariable("period", period);
            context.setVariable("rptName", rptName);
            context.setVariable("hasFirmId", hasFirmId);
            if (hasFirmId)
            {
                context.setVariable("firmIdType", FirmIdType.getFirmIdType(maybeFirmId).get());
            }

            return templateEngine.process("sroPrint", context);

Template with name sroPrint has some basic Theymleaf directives:

<html xmlns:th="http://www.thymeleaf.org">
<head>
<style>
    table thead tr th, table tbody tr td {
      border: 1px solid black;
      text-align: center;
    }
  </style>

</head>
<body>
<div>
<h4 th:text="${rptName}">report name</h4>
<div style="margin: 10px 0;"><b>Period:</b> <span th:text="${period}"></span>
<div>
  <h4 th:text="${rptName}">report name</h4>
  <div style="margin: 10px 0;"><b>Period:</b> <span th:text="${period}"></span></div>
  <table style="width: 100%; ">
    <thead>
    <tr>
      <th th:if="${hasFirmId}" th:text="${firmIdType}"></th>
      <th>crd #</th>
      <th>Firm Name</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="firm : ${firms}">
      <td th:if="${hasFirmId}" th:text="${firm.firmId}"></td>
      <td th:text="${firm.crdId}">CRD</td>
      <td th:text="${firm.firmName}">firm name</td>
    </tr>
    </tbody>
  </table>
</div>
</body>
</html>

client side

Now on the client side we need to consume the HTML string from the client side. The flow is we create an iFrame, write the html into it and call browser print on that iFrame and remove the element from DOM. The below implementation is inside the success callback of $http call for getting that dom string. It is in pure js without jQuery, with which it might be a bit more concise.


var printIFrame = document.createElement('iframe');
document.body.appendChild(printIFrame);
printIFrame.style.position = 'absolute';
printIFrame.style.top = '-9999px';
printIFrame.style.left = '-9999px';
var frameWindow = printIFrame.contentWindow || printIFrame.contentDocument || printIFrame;
var wdoc = frameWindow.document || frameWindow.contentDocument || frameWindow;
wdoc.write(res.data);
// tell browser write finished
wdoc.close();
$scope.$emit('UNLOAD');
// Fix for IE : Allow it to render the iframe
frameWindow.focus();
try {
    // Fix for IE11 - printng the whole page instead of the iframe content
    if (!frameWindow.document.execCommand('print', false, null)) {
        // document.execCommand returns false if it failed -http://stackoverflow.com/a/21336448/937891
        frameWindow.print();
    }
    // focus body as it is losing focus in iPad and content not getting printed
    document.body.focus();
}
catch (e) {
    frameWindow.print();
}
frameWindow.close();
setTimeout(function() {
    printIFrame.parentElement.removeChild(printIFrame);
}, 0);

PDF/XLS Export

For xls/pdf export, it is similar to the other POST that I have before. The only difference is the dom string was passed from client there. Here we generate the dom string in server side.

use contoller as in angularjs

The syntax when using routeprovider is:

$routeProvider
    .when('/', {
        templateUrl: 'Views/Home/Index.html',
        controller: 'HomeCtrl',
        controllerAs: 'vm'
    })
    .otherwise({ redirectTo: '/' });

Some benefit are described here

The name vm stands for View-model in mvvm

angularjs $scope form undefined

Was developing some form with angular, things work perfect until I add a angular-ui bootstrap tab to enclose 2 different form in each tab.

when i try to access the form with $scope.formName, the formName is undefined.

It turns out that the angularUI tab directive uses transclude which will create a child scope to the parent which here is the controller $scope as the sibling of the directive scope. Then the formName can only be access by the new child scope.

One solution is define a var in the controller scope like $scope.forms = {};

Then for the form name in the transclude directive, we use forms.formName1. This way we could still access it from our controller by just call $scope.forms.formName1.

This works because the inheritance mechanism in JS is prototype chain. So when child scope tries to create the forms.formName1, it first tries to find the forms object in its own scope which definitely does not have it since it is created on the fly. Then it will try to find it from the parent(up to the prototype chain) and since we have it defined in the controller scope, it uses this ‘forms’ object we created to define the variable formName1. As a result we could still use it in the controller like $scope.forms.formName1.$valild to do our stuff.

angularjs filter

I think it’s best to understand what we even want to learn. To do that, we need to understand what filters really are and how we use them. For me, there are four types of filters. Yes, four, but there of course can be other variants. Let’s rattle through them:

Filter 1: Static (single) use filter

Filter 1 just filters a single piece of Model data (not a loop or anything fancy) and spits it out into the View for us. This could be something like a date:

<p>{{ 1400956671914 | date: 'dd-MM-yyyy' }}</p>

When rendered, the DOM would look like this:

<p>24-05-2014</p>

So how do we create this, or something similar?

Let’s take my full name for example, if I wanted to quickly filter it and make it uppercase, how would we do it?

Angular has a .filter() method for each Module, which means we can write our own custom filters. Let’s look at a stripped down filter:

app.filter('', function () {
  return function () {
    return;
  };
});

As you can see, we can name our filter and we return a function. What the heck do these do?

The returned function gets invoked each time Angular calls the filter, which means two-way binding for our filters. The user makes a change, the filter runs again and updates as necessary. The name of our filter is how we can reference it inside Angular bindings.

Let’s fill it in with some data:

app.filter('makeUppercase', function () {
  return function (item) {
    return item.toUpperCase();
  };
});

So what do these mean? I’ll annotate:

// filter method, creating `makeUppercase` a globally
// available filter in our `app` module
app.filter('makeUppercase', function () {
  // function that's invoked each time Angular runs $digest()
  // pass in `item` which is the single Object we'll manipulate
  return function (item) {
    // return the current `item`, but call `toUpperCase()` on it
    return item.toUpperCase();
  };
});

As an example app:

var app = angular.module('app', []);

app.filter('makeUppercase', function () {
  return function (item) {
      return item.toUpperCase();
  };
});

app.controller('PersonCtrl', function () {
  this.username = 'Todd Motto';
});

Then we declare it in the HTML:

ng-app="app">
ng-controller="PersonCtrl as person">

{{ person.username | makeUppercase }}

</div>

And that’s it, jsFiddle link and output:

http://jsfiddle.net/toddmotto/xz39g/embedded/result,js,html

Filter 2: Filters for repeats

Filters are really handy for iterating over data and without much more work, we can do exactly that.

The syntax is quite similar when filtering a repeat, let’s take some example data:

app.controller('PersonCtrl', function () {
  this.friends = [{
    name: 'Andrew'        
  }, {
    name: 'Will'
  }, {
    name: 'Mark'
  }, {
    name: 'Alice'
  }, {
    name: 'Todd'
  }];
});

We can setup a normal ng-repeat on it:

<ul>
  <li ng-repeat="friend in person.friends">
    {{ friend }}
  </li>
</ul>

Add a filter called startsWithA, where we only want to show names in the Array beginning with A:

<ul>
  <li ng-repeat="friend in person.friends | startsWithA">
    {{ friend }}
  </li>
</ul>

Let’s create a new filter:

app.filter('startsWithA', function () {
  return function (items) {
    var filtered = [];
    for (var i = 0; i < items.length; i++) {
      var item = items[i];
      if (/a/i.test(item.name.substring(0, 1))) {
        filtered.push(item);
      }
    }
    return filtered;
  };
});

There are two different things happening here! First, item previously is now items, which is our Array passed in from the ng-repeat. The second thing is that we need to return a new Array. Annotated:

app.filter('startsWithA', function () {
  // function to invoke by Angular each time
  // Angular passes in the `items` which is our Array
  return function (items) {
    // Create a new Array
    var filtered = [];
    // loop through existing Array
    for (var i = 0; i < items.length; i++) {
      var item = items[i];
      // check if the individual Array element begins with `a` or not
      if (/a/i.test(item.name.substring(0, 1))) {
        // push it into the Array if it does!
        filtered.push(item);
      }
    }
    // boom, return the Array after iteration's complete
    return filtered;
  };
});

ES5 version using Array.prototype.filter for a super clean filter:

app.filter('startsWithA', function () {
  return function (items) {
    return items.filter(function (item) {
      return /a/i.test(item.name.substring(0, 1));
    });
  };
});

And that’s it, jsFiddle link and output:

http://jsfiddle.net/toddmotto/GDmN7/embedded/result,js,html

Filter 3: Filters for repeats with arguments

Pretty much the same as the above, but we can pass arguments into the functions from other Models. Let’s create an example that instead of “filtering by letter A”, we can let the user decide, so they can type their own example:

<input type="text" ng-model="letter">
<ul>
  <li ng-repeat="friend in person.friends | startsWithLetter:letter">
    {{ friend }}
  </li>
</ul>

Here I’m passing the filter the letter Model value from ng-model="letter". How does that wire up inside a custom filter?

app.filter('startsWithLetter', function () {
  return function (items, letter) {
    var filtered = [];
    var letterMatch = new RegExp(letter, 'i');
    for (var i = 0; i < items.length; i++) {
      var item = items[i];
      if (letterMatch.test(item.name.substring(0, 1))) {
        filtered.push(item);
      }
    }
    return filtered;
  };
});

The most important thing to remember here is how we’re passing in arguments! Notice letter now exists inside the return function (items, letter) {};? This corresponds directly to the :letter part. Which means we can pass in as many arguments as we need (for example):

<input type="text" ng-model="letter">
<ul>
  <li ng-repeat="friend in person.friends | startsWithLetter:letter:number:somethingElse:anotherThing">
    {{ friend }}
  </li>
</ul>

We’d then get something like this:

app.filter('startsWithLetter', function () {
  return function (items, letter, number, somethingElse, anotherThing) {
    // do a crazy loop
  };
});

And that’s it, jsFiddle link and output:

http://jsfiddle.net/toddmotto/53Xuk/embedded/result,js,html

Filter 4: Controller/$scope filter

This one’s a bit of a cheat, and I would only use it if you really have to use it. We take advantage of the :argsyntax and pass a $scope function into Angular’s filter Object!

The difference with these type of filters is that the functions declared are the ones passed into the filter function, so we’re technically writing a function that gets passed into our return function. We don’t get Array access, just the individual element. Important to remember.

Let’s create another function that filters by letter w instead. First let’s define the function in the Controller:

app.controller('PersonCtrl', function () {
  // here's our filter, just a simple function
  this.startsWithW = function (item) {
    // note, that inside a Controller, we don't return
    // a function as this acts as the returned function!
    return /w/i.test(item.name.substring(0, 1));
  };
  this.friends = [{
    name: 'Andrew'        
  }, {
    name: 'Will'
  }, {
    name: 'Mark'
  }, {
    name: 'Alice'
  }, {
    name: 'Todd'
  }];
});

Then the repeat:

ng-controller="PersonCtrl as person">
  • ng-repeat="friend in person.friends | filter:person.startsWithW"> {{ friend }}

These functions are obviously scoped and not reusable elsewhere. If it makes sense then use this setup, else don’t…

The official doc: The filter function should be a pure function, which means that it should be stateless and idempotent. Angular relies on these properties and executes the filter only when the inputs to the function change.

Be careful of infinite loop being triigered

FROM HERE

$scope.$evalAsync() vs. $timeout() In AngularJS

Sometimes, in an AngularJS application, you have to explicitly tell AngularJS when to initiate it’s $digest() lifecycle (for dirty-data checking). This requirement is typically contained within a Directive; but, it may also be in an asynchronous Service. Most of the time, this can be easily accomplished with the $scope.$apply() method. However, some of the time, you have to defer the $apply() invocation because it may or may not conflict with an already-running $digest phase. In those cases, you can use the $timeout() service; but, I’m starting to think that the $scope.$evalAsync() method is a better option.

Generally speaking, it’s clear as to whether or not an AngularJS $digest is already executing. But, sometimes, depending on the context, this distinction becomes blurry. Consider the following pseudo-code for a Directive link() function:

// PSEUDO-CODE for AngularJS directive link function.
function link( $scope ) {
function handler( data ) {
$scope.$apply(
function() {
// …
}
);
}
if ( cachedData ) {
handler( cachedData );
} else {
getDataAsync( handler );
}
}
view rawpseudo-code.js hosted with ❤ by GitHub

Here, we are working with data that may or may not be cached locally. If it’s cached, we use it immediately; if it’s not cached, we get it asynchronously. This duality causes a problem for the data handler. In one context – the cached data – the handler is called within the lifecycle of an active $digest. Then, in the other context – the asynchronous get – the handler is called outside of an AngularJS $digest.

This means that some of the time, the directive will work properly; and, some of the time, it will throw the following error:

Error: $digest already in progress

To side-step this problem, we either put in logic that explicitly checks the AngularJS $$phase (which is a big no-no!); or, we make sure that the callback handler initiates a $digest at a later time.

Up until now, my approach to deferred-$digest-invocation was to replace the $scope.$apply() call with the $timeout() service (which implicitly calls $apply() after a delay). But, yesterday, I discovered the $scope.$evalAsync() method. Both of these accomplish the same thing – they defer expression-evaluation until a later point in time. But, the $scope.$evalAsync() is likely to execute in the same tick of the JavaScript event loop.

Take a look at the following code. Notice that there are two calls to $timeout() that sandwich a call to $scope.$evalAsync():

<!doctype html>
<html ng-app=Demo>
<head>
<meta charset=utf-8 />
<title>
$scope.$evalAsync() vs. $timeout() In AngularJS
</title>
</head>
<body>
<h1>
$scope.$evalAsync() vs. $timeout() In AngularJS
</h1>
<p bn-timing>
Check the console!
</p>
<!– Load scripts. –>
<script type=text/javascript src=../../vendor/jquery/jquery-2.0.3.min.js></script>
<script type=text/javascript src=../../vendor/angularjs/angular-1.2.4.min.js></script>
<script type=text/javascript>
// Create an application module for our demo.
var app = angular.module( Demo, [] );
// ————————————————– //
// ————————————————– //
// Test the timing of the $timeout() and $evalAsync() functions.
app.directive(
bnTiming,
function( $timeout ) {
// I bind the JavaScript events to the local scope.
function link( $scope, element, attributes ) {
$timeout(
function() {
console.log( $timeout 1 );
}
);
$scope.$evalAsync(
function( $scope ) {
console.log( $evalAsync );
}
);
$timeout(
function() {
console.log( $timeout 2 );
}
);
}
// Return the directive configuration.
return({
link: link
});
}
);
</script>
</body>
</html>
view raweval-async.htm hosted with ❤ by GitHub

When we run this code, we get the following console output:

$evalAsync
$timeout 1
$timeout 2

Run this demo in my JavaScript Demos project on GitHub.

Even though the first $timeout() call was before the $scope.$evalAsync() method, you can see that the $scope.$evalAsync() expression was evaluated first. This is because the $scope.$evalAsync() expressions are placed in an “async queue” that is flushed at the start of each $digest iteration. As a very high level, the $digest loop looks like this:

  • Do:
  • – – – If asyncQueue.length, flush asyncQueue.
  • – – – Trigger all $watch handlers.
  • – – – Check for “too many” $digest iterations.
  • While: ( Dirty data || asyncQueue.length )

If some aspect of the $digest phase adds an expressions to the asyncQueue (using $scope.$evalAsync()), AngularJS will perform another iteration of the $digest loop in order to flush the asyncQueue. This way, your expression is very likely to be evaluated in the same tick of the JavaScript event loop.

Of course, there are outlier cases where this isn’t true, such as if the $scope.$evalAsync() puts the $digest loop over its “max iterations” limit or another expression throws an error. This is why AngularJS also uses a timeout in the $scope.$evalAsync() method. In addition to updating the asyncQueue, AngularJS also initiates a timeout that checks the asyncQueue length. This way, if the asyncQueue isn’t flushed during the current $digest cycle, it will surely be flushed in a later tick of the event loop.

So, in essence, $scope.$evalAsync() combines the best of both worlds: When it can (which is most of the time), it will evaluate your expression in the same tick; otherwise, it will evaluate your expression in a later tick, which is exactly what $timeout() is doing.

I’m not saying that all instances of $timeout() should be replaced with $scope.$evalAsync() – they serve two different, albeit related, purposes. If you truly want to execute code at a later point in time, use $timeout(). However, if your only goal is tell AngularJS about a data change without throwing a “$digest already in progress” error, I would suggest using $scope.$evalAsync().

FROM HERE

angularjs bootstrapui print iframe modal

1. Print modal fit in one screen

If the content of the modal is in one screen, we could just put a css in the modal template to hide all other content

/*general styles*/
.printable{display:none;}
/* print styles*/
@media print {
 .printable {display:block;}
 .screen {display:none;}
}

Or put a wrap to the parent content and assign an id to it then in the print css, use the id and set it as display: none

2. Print modal with long iframe

If we load a iframe with long content in the modal, the above print will just print the on-screen part content which is not desirable since we definite want to print the whole iframe.

2.1 add a function in the modal controller

	$scope.printIframe = function (iFrameId) {
		var frame = window.frames[iFrameId];
		if (!frame) {
			console.log('No iFrame found for id: ' + iFrameId);
			return;
		}
		frame.focus();
		frame.print();
	};

2.2 assign id to the iframe and call the print function from the template

      <iframe ng-src="{{request.url}}" frameborder="0" style="overflow-x:hidden; height: 100%; width: 100%;" id="modalIframe" name="modalIframe"></iframe>

....

<button class="btn btn-primary" ng-click="printIframe('modalIframe')">Print</button>

The other part is just regular setup

//popup for detail data
$scope.openDetail = function (mmid) {
//	$event.preventDefault();
	var detailUrl = '/someurl?rptid=' + $scope.rptId + '&firmname=' + $scope.reportData.firmName + '&mmid=' + mmid;
	$scope.detailRequest = {url:detailUrl};
	$modal.open({
		templateUrl: 'detailModalContent.html',
		controller: 'ModalInstanceCtrl',
		resolve: {
			request: function () {
				return $scope.detailRequest;
			}
		},
		// set width to 900px(bootstrap);
		size: 'lg'
	});
};

3. Print modal with long content(no iFrame)

The problem here is if the content is longer than the modal view port, then we can only see the viewable area in the print.
The only CSS that works for me when the modal is LONGER than the view port is:

@media print {
    .modal {
        position: absolute;
        left: 0;
        top: 0;
        margin: 0;
        padding: 0;
        overflow: visible!important;
    }
}

Note: position:absolute and overflow:visible are MUST have.

export html to excel/pdf with angularjs and servlet(spring mvc)

As we konw, the XHR request that angularjs uses cannot init a download.

iFrame version

One way to achieve is thru an invisible iframe and assign the url to the iframe for download.

The basic idea is to construct a iframe on the fly and append a html FORM in it and then submit the form to server in javascript. On the server side, return the stream with ‘Content-disposition’ header.

Angularjs code

just pass in fileType(here is pdf/xls) in your ng-click or from some other delegations

	$scope.exportReport = function (fileType) {
		var printIframe = angular.element("<iframe class='hidden'>");
		var formElement = angular.element("<form>");
		formElement.attr("action", "/scrc/rest/trace/export/" + fileType);
		formElement.attr("method", "post");
		var contentElement = angular.element("<input>").attr("type", "hidden").attr("name",
				"domContent").val(angular.element('.report-outer-wrapper').html());
		//build file name
		var fileName = $scope.reportData.mpid + '_' + $scope.reportData.periodDate.replace(' ', '_') + '_' + $scope.reportData.viewType;
		var fileNameElement = angular.element("<input>").attr("type", "hidden").attr("name",
				"fileName").val(fileName);
		formElement.append(contentElement);
		formElement.append(fileNameElement);
		printIframe.append(formElement);
		angular.element('body').append(printIframe);
		formElement.submit();
	};

Java code in Spring controller

The buildExportContent() is just to put the style and content into a html’s head and body and return a String.

    @RequestMapping(value = "/export/{fileType}", method = RequestMethod.POST)
    @ResponseBody
    public void handleExport(@PathVariable("fileType") String fileType, @RequestParam("domContent") String domContent,
        @RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException, DocumentException
    {
        OutputStream out = response.getOutputStream();
        String finalFileName = fileName + "." + fileType;
        response.setHeader("Content-disposition", "attachment; filename=" + finalFileName);
        String styles = "";
        if ("pdf".equalsIgnoreCase(fileType))
        {
            String bootstrapStylePath = context.getRealPath("/css/bootstrap/bootstrap.min.css");
            styles = Files.toString(new File(bootstrapStylePath), StandardCharsets.UTF_8);
            //we need xhtml here.
            String exportContent = reportService.buildExportContent(domContent, styles).replaceAll("<br>", "<br/>");
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocumentFromString(exportContent);
            renderer.layout();
            renderer.createPDF(out);
        }
        else if ("xls".equalsIgnoreCase(fileType))
        {
            styles = " table{border:1px solid gray !important}; " + " td, th {border:1px solid gray !important}; ";
            //we need xhtml here.
            String exportContent = reportService.buildExportContent(domContent, styles).replaceAll("<br>", "<br/>");
            out.write(exportContent.getBytes(StandardCharsets.UTF_8));
        }
        out.close();

    }

Blob version

another way is to use Blob and generate the url with browser. however only IE>10 support this. WHat a sad story.

<a download="content.txt" ng-href="{{ url }}">download

in your controller:

var content = 'file content';
var blob = new Blob([ content ], { type : 'text/plain' });
$scope.url = (window.URL || window.webkitURL).createObjectURL( blob );

in order to enable the URL:

app = angular.module(...);
app.config(['$compileProvider',
    function ($compileProvider) {
        $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|blob):/);
});

Please note that

Each time you call createObjectURL(), a new object URL is created, even if you’ve already created one for the same object. Each of these must be released by calling URL.revokeObjectURL() when you no longer need them. Browsers will release these automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so.

Source: MDN

Another blob version

Pure html Form version

Bassically use a HTML form with ng-submit, on return add ‘Content-disposition’ header so that the page would not re-direct but just a file download.

code in Angular:

I did some customization to the form element so that the content of the report could be posted to the server side for the flying saucer to render and process to pdf file.

	$scope.printExcel = function (e) {
		var form = angular.element(e.target);
		form.attr("action", "/scrc/rest/trace/pdf");
		var contentElement = angular.element("<input>").attr("type", "hidden").attr("name",
				"printContent").val(angular.element('.report-outer-wrapper').html());
		form.append(contentElement);
		form.submit();
	};

HTML:

                       <form name="printPdfForm" method="POST" ng-submit="printPdf($event)">
                           <button type="submit" class="print-button">
                              <img src="/scrc/images/icon_pdf.gif" border="0" alt="download pdf"/>
                           </button>
                        </form>

Code in Spring controller.

The excel version is basically just return to the client the same content with a Content-disposition header which is for the origin server to suggest a default filename if the user requests that the content is saved to a file. It would be nice if we could do this in the client side with javascipt so that we can save a trip(Please notify me if you know how to do it.).

    @RequestMapping(value = "/pdf", method = RequestMethod.POST)
    @ResponseBody
    public void handlePdf(@RequestParam("printContent") String printContent, HttpServletResponse response) throws Exception
    {
        String fileName = "result.pdf";
        response.setHeader("Content-disposition", "attachment; filename=" + fileName);
        OutputStream out = response.getOutputStream();

        String content = printContent.replaceAll("<br>", "<br/>");
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(getPdfHtml(content));
        renderer.layout();

        renderer.createPDF(out);
        out.close();
    }

    private String getPdfHtml(String content) throws Exception
    {
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<head><style language='text/css'>");
        //if performance is bad we could ignore this big css
        String rcStylePath = context.getRealPath("/css/bootstrap/bootstrap.min.css");
        sb.append(Files.toString(new File(rcStylePath), StandardCharsets.UTF_8));
        sb.append("</style></head>");
        sb.append("<body>");
        sb.append(content);

        sb.append("</body>");
        sb.append("</html>");

        return sb.toString();
    }

    @RequestMapping(value = "/excel", method = RequestMethod.POST)
    @ResponseBody
    public void handleExcel(@RequestParam("printContent") String printContent, HttpServletResponse response) throws IOException
    {
        String fileName = "result.xls";
        response.setHeader("Content-disposition", "attachment; filename=" + fileName);
        OutputStream out = response.getOutputStream();

        out.write(getExcelHtml(printContent).getBytes(StandardCharsets.UTF_8));
        out.close();
    }

    private String getExcelHtml(String content) throws IOException
    {
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<head><style language='text/css'>");

        sb.append(" table{border:1px solid gray !important}; ");
        sb.append(" td, th {border:1px solid gray !important}; ");
        sb.append("</style></head>");
        sb.append("<body>");
        sb.append(content);

        sb.append("</body>");
        sb.append("</html>");

        return sb.toString();
    }

For some IE8 issue, look at my other POST