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

7 Replies to “AngularJS + JQueryMobile and Dynamic Loading of pages”

  1. Hi,

    Thank you for the tip. I tried using the same in IBM worklight and it works really well in the browser but doesnt work when it is run on any device.. especially android phones.
    The moment it is launched from worklight it shows a glimpse of the page and disappears.

    Please help me with this..

    Thank you,
    worklightuser

    1. That is because in this example trigger(“create”) is called on $includeContentLoaded. For ng-repeat you have to call it after the last iteration of ng-repeat. You can create a directive for that.

      .directive(‘jqMobileRepeat’, function () {
      return function (scope) {
      if (scope.$last) {
      $(‘#myElement’).trigger(‘create’);
      }
      }
      });

      And then as an attribute in your html-code.

      {{item}}

      And that should work.

  2. Hello Ram,

    I have a similar situation,where page does not render if I use popUp on the directive
    (Im new to both JQM and Angular. Will appreciate your help – Naresh)

    Source code and live example on the plunker
    link: http://plnkr.co/edit/ZNXfBbdivbrxqA3x7IGz?p=preview

    I have a main-page ( id=”mainPageId” ) which display a ListItem as “List 1”. Once clicked on “List 1”, its goes to next page where it display angular directive ( ) where I have a button with name as “AA” and has value “11” ( button gets value form jsonData.json file)

    Directive is defined in a file rating.js,where button is has two actions:

    1) Button click: (This action works fine without any issue) via ng-click=”toggle($index), where button changes its color from silver to yellow by replacing its value from “11” to “” ( meaning make it empty value), and, directive gets refresh via function updateDirective(), which is called by a callback function registerd with the provider.

    2) PopUp on Button: THIS DOES NOT REFRESH DIRECTIVE** by pressing button down, activate popUp dialog (id=templateID) via funtion $(“#templateID”).on(“taphold”,function(event ). PopUp display its current value “11”. Now, if I replace value “11” by “” ( meaning make it empty value), and, directive gets refresh via function updateDirective(), which is called by a callback function registerd with the provider. It DOES NOT REFRESH DIRECTIVE**

    Plunker Link:
    http://plnkr.co/edit/ZNXfBbdivbrxqA3x7IGz?p=preview

    **Im using JqueryMobile along with AngularJS and for page loading, using
    scope.$on(‘$viewContentLoaded’, elem.parent().trigger(“create”))
    This area is not clear for me , it works on Button Click, and,
    DOES NOT WORK ON Button Hold-Down ie via PopUP message.

    Please help.
    Scource code and
    http://plnkr.co/edit/ZNXfBbdivbrxqA3x7IGz?p=preview

Leave a Reply

Social