AngularJS + JQueryMobile and Dynamic Loading of pages

I was experimenting with using AngularJS and JQueryMobile together and loading pages dynamically. The page loading and routing can either be handled by JQueryMobile ($.mobile.pageContainer.pagecontainer(“change”,“page_name”)) or AngularJS. With AngularJS you can use $route service along with ngView and/or use ngInclude .

ngInclude looked easy enough to start with, but did not turn out to be that easy. I will take as simple example of a login application. If user is not logged-in, the app will display the login page and after successfully logging in, the application will display the main page, with the option to logout.

Here is index.html –

<!DOCTYPE html>

<html ng-app="loginSampleApp">
	<head>
		<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">

		<link rel="stylesheet" href="css/jquery.mobile-1.4.2.min.css" ></link>

		<script src="js/jquery-2.1.0.min.js" ></script>
		<script src="js/jquery.mobile-1.4.2.min.js" ></script>
		<script src="js/angular.min.js" ></script>

		<script src="angular_app.js"></script>

	</head>

	<body >
		<div ng-controller="mainController" data-role="page">
			<div data-role="header" data-position="fixed">
				<h2>Login Application</h2>
			</div>
			<div data-role="content" id="mainDiv" >
				<ng-include src="getTemplate()"></ng-include>
			</div>
			<div data-role="footer" data-position="fixed">
				<h3>Created with AngularJS and JQuery Mobile</h3>
			</div>
		</div>
	</body>
</html>

Body of the page has a JQueryMobile div-page with header, content and footer. The content div calls ng-include directive and source is returned by getTemplate() function, which is declared in angular_app.js.

angular_app.js –

window.loggedIn = false;
var loginSampleApp = angular.module("loginSampleApp",[]).
controller (
	"mainController",
	[
		"$scope",
		function ($scope)
		{
			$scope.getTemplate = function()
			{
				if (!window.loggedIn )
					return "login.html";
				else
					return "main.html";
			}
		}
	]
). controller(
	"loginController",
	[
		"$scope",
		function ($scope)
		{
			$scope.userName = "";
			$scope.password = "";

			$scope.onSubmit = function()
			{
				if ($scope.userName !== "admin" || $scope.password !== "admin")
				{
					alert("Invalid user name or password");
					return ;
				}
				window.loggedIn  = true;
				return ;
			}

			$scope.onReset = function()
			{
				$scope.userName = "";
				$scope.password = "";
			}
		}
	]
). controller (
	"mainPageController",
	[
		"$scope",
		function ($scope)
		{

			$scope.logout = function()
			{
				window.loggedIn  = false;
			}
		}
	]
)
;

First I initialize window.loggedIn flag to false. Then declare Angular App and controllers – mainController (used in index.html), loginController (used in login.html) and mainPageController (used in main.html). getTemplate function is declared in the mainController and it returns appropriate page name depending on value of the flag window.loggedIn.

login.html –

<div ng-controller="loginController">
	User Name : <input type="text" id="userName" ng-model="userName"><br>
	Password  : <input type="password" id="password" ng-model="password"><br>

	<a href="#" data-role="button" data-inline="true" id="submitBtn" ng-click="onSubmit()">Submit</a>
	<a href="#" data-role="button" data-inline="true" id="resetBtn" ng-click="onReset()">Reset</a>
</div>

And main.html –

<div ng-controller="mainPageController">
	You are logged-in <br>
	<a href="#" data-role="button" data-inline="true" ng-click="logout()">Logout</a>
</div>

You can see how this application works here – enter user name and password as admin.
The problem is that JQueryMobile styles are not applied to the dynamically included files. You will see that buttons appear as links instead of JQM buttons and text fields are also not JQM text fields.

The solution for this problem is to trigger JQuery Mobile create event on the parent div, in this example it is mainDiv in index.html. So I need to call $(“#mainDiv”).trigger(“create”);

But when and where do I call it from? It has to be called after ng-include is done with loading and compiling the page. So I need to handle $includeContentLoaded, which is triggered when ng-include is done loading the content. This event has to be handled on the AngularJS scope belonging to ng-include directive – in this example it is the scope in mainController because ng-include is within mainDiv and mainController is associated with mainDiv. So I modified mainController as follows –

controller (
	"mainController",
	[
		"$scope",
		function ($scope)
		{
			$scope.$on("$includeContentLoaded", function(){
				$("#mainDiv").trigger("create");
			});

			$scope.getTemplate = function()
			{
				if (!window.loggedIn )
					return "login.html";
				else
					return "main.html";
			}
		}
	]
)

Notice $scope.$on(“$includeContentLoaded” …), $(“#mainDiv”).trigger(“create”) is called in this event handler. You can see updated application here – notice that buttons and text fields appear properly now.

So important thing to remember is that you need to apply JQueryMobile styles to dynamic pages not loaded using JQueryMobile by triggering create event. And if you are loading such pages using ngInclude then call trigger function of JQueryMobile in the handler for $includeContentLoaded event.

-Ram Kulkarni

Leave a Reply