Ionic XMPP chat Client using Strophe.js: Part 3

Now lets start building our very own chat client. Please make you have gone through my previous articles. If you are new to Ionic Cordova, please look into my introductory post.

Now let us start building the main screens.You can download my complete project here :

[wpi_designer_button text=’Download’ link=’’ style_id=’48’ icon=’github’ target=’_blank’]

If there is any trouble understanding the concepts please read my other articles on XMPP:

XMPP BASICS : part 1

XMPP chat in Ionic Cordova (Setting chat server in local host): Part 2

Strophe.js :

We are using Strophe.js , an opensource XMPP client library for javascript.

Inorder for Strophe.js to run  we also need to use jquery

  • Download Strophe.js
  • Include Strophe files in the lib folder
  • Now download jQuery.  Save it to a jquery folder in your lib.
  • Include the Strophe.js and  jquery-1.12.3.min.js  in your index.html.

    <!DOCTYPE html>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
        <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    	<!-- compiled css output -->
    	<link href="css/" rel="stylesheet">
    	<!-- jQuery js -->
        <!-- cordova script (this will be a 404 during development) -->
    	 <!--// Strophe.js-->
        <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
        <link href="css/" rel="stylesheet">
        <style type="text/css">
          .platform-ios .manual-ios-statusbar-padding{
          .manual-remove-top-padding .scroll{
            padding-top:0px !important;
        <!-- Only required for Tab projects w/ pages in multiple tabs 
      <body ng-app="app" animation="slide-left-right-ios7">
      <div style="">
        <div style="">
            <ion-nav-bar class="bar-stable">
                <ion-nav-back-button class="button-icon icon ion-ios-arrow-back">Back</ion-nav-back-button>

Login :



<ion-view style="" id="page5" title="Login" hide-back-button="true">
    <ion-content class="has-header" padding="true">
        <form style="" class="list">
            <ion-list style="">
				<!--Login Form-->
                <label style="" class="item item-input">
                    <span class="input-label">Jabber ID</span>  
                    <input ng-model="user.jid" placeholder="admin" type="text">  <!--user class contains all the user inputs-->
				<label style="" class="item item-input">
                    <span class="input-label">Password</span>
                    <input ng-model="user.pass" placeholder="admin" type="password">  <!--user class contains all the user inputs-->
				<label style="" class="item item-input">
                    <span class="input-label">Host</span>
                    <input ng-model="" placeholder="localhost" type="text">  <!--user class contains all the user inputs-->
            <div style="height: 40px;" class="spacer"></div>
			<!-- Onclick listener is 'login()' with parameter as 'user' object -->
			 <button  class="button button-stable button-block "  id="login-button" ng-click="login(user)" >Log in</button>  
			 <!--Just moves to the Register Screen -->
			 <button  class="button button-stable button-block "  id="register-button" ng-click="goToRegister()" >Register</button>


.controller('loginCtrl', function($scope , sharedConn ,$state ) {  

//sharedConn is a shared service for handling XMPP connection
// It is used to share the same connection object with different controllers

/*For debuggin purpose -- automatically logs in to the app on start */
	var XMPP_DOMAIN  = 'xvamp';     // Domain we are going to be connected to.
	var xmpp_user    = 'admin';     // xmpp user name
	var xmpp_pass    = 'admin';
//$scope is used to access the variables and functions defined within the html file
		$state.go('register', {}, {location: "replace", reload: true});  //Forwards it to register page
//Calling the login function in sharedConn 	
	$scope.login=function(user){  //gets user parameter

	//sharedConn.login(xmpp_user,XMPP_DOMAIN,xmpp_pass);  //Debuggin purpose

SERVICES : (SharedConn)

//within sharedConn service we are using 
$state for forwarding to another page
$rootScope for accessing broadcast function
$ionicPopup for making Responsive Popups 


.factory('sharedConn', ['$ionicPopup','$state','$rootScope','$ionicPopup',function($ionicPopup,$state, $rootScope , $ionicPopup ){
	//Declaring the SharedConnObj which will be returned when we call sharedConn
	 var SharedConnObj={};
	// Setting up the variables
	 SharedConnObj.BOSH_SERVICE = 'http://xvamp:7070/http-bind/';  
	 SharedConnObj.connection   = null;    // The main Strophe connection object.

	 //------------------------------------------HELPER FUNCTIONS---------------------------------------------------------------
			return SharedConnObj.connection; 
			return SharedConnObj.loggedIn; 
	 //The jabber id ie user id will be usually of the form admin@xvamp\convertsdfs 
	 // We only need the part admin@xvamp
	 //So we take the substring to get only admin@xvamp
		var str=SharedConnObj.connection.jid;
		str=str.substring(0, str.indexOf('/'));
        return str;
	 //--------------------------------------***END HELPER FUNCTIONS***----------------------------------------------------------
	//Login Function
	SharedConnObj.login=function (jid,host,pass) {	 
	//Strophe syntax
	// We are creating Strophe connection Object
		SharedConnObj.connection = new Strophe.Connection( SharedConnObj.BOSH_SERVICE , {'keepalive': true});  // We initialize the Strophe connection.
		SharedConnObj.connection.connect(jid+'@'+host, pass , SharedConnObj.onConnect);  
		//Here onConnect is the callback function
		//ie after getting the response for connect() function onConnect() is called with the response
	//On connect XMPP
		//Self explanatory 
		if (status == Strophe.Status.CONNECTING) {
			console.log('Strophe is connecting.');
		} else if (status == Strophe.Status.CONNFAIL) {
			console.log('Strophe failed to connect.');
		} else if (status == Strophe.Status.DISCONNECTING) {
			console.log('Strophe is disconnecting.');
		} else if (status == Strophe.Status.DISCONNECTED) {
			console.log('Strophe is disconnected.');
		} else if (status == Strophe.Status.CONNECTED) {		
				//The connection is established. ie user is logged in 
				// We are adding handler function for accetping message response
				//ie handler function  [ onMessage() ]  will be call when the user recieves a new message 
				SharedConnObj.connection.addHandler(SharedConnObj.onMessage, null, 'message', null, null ,null);
				//Setting our presence in the server. so that everyone can know that we are online
				//Handler function for handling new Friend Request
				SharedConnObj.connection.addHandler(SharedConnObj.on_subscription_request, null, "presence", "subscribe");
				//Now finally go the Chats page 	
				$state.go('tabsController.chats', {}, {location: "replace", reload: true});

	//When a new message is recieved
		//Here we will braodcast that we have recieved a message.
		//This broadcast will be handled in the 'Chat Details controller'
		//In broadcast we are also sending the message
		$rootScope.$broadcast('msgRecievedBroadcast', msg );
		return true;
	SharedConnObj.register=function (jid,pass,name) {
		//to add register function

	//Logout funcion
	SharedConnObj.logout=function () {
		console.log("logout called");  //In chrome you can use console.log("TEXT") for dubugging
		SharedConnObj.connection.options.sync = true; // Switch to using synchronous requests since this is typically called onUnload.
		SharedConnObj.connection.flush();  //Removes all the connection variables
		SharedConnObj.connection.disconnect(); //Disconnects from the server

	SharedConnObj.on_subscription_request = function(stanza){
		console.log(stanza);  //Debuggin
		//Handling subscribe request ... ie freind request
		if(stanza.getAttribute("type") == "subscribe" && !is_element_map(stanza.getAttribute("from")) )
			//the friend request is recieved from Client 2
			//Creats a ionic popup
			var confirmPopup = $ionicPopup.confirm({
				 title: 'Confirm Friend Request!',
				 template: ' ' + stanza.getAttribute("from")+' wants to be your freind'
			// Yes or No option
		   confirmPopup.then(function(res) {
			 if(res) {
			 //Inorder to say that you have accepted their friend request, you just have to send them a 'presence' with 'type = subscribed'
			   SharedConnObj.connection.send($pres({ to: stanza.getAttribute("from") , type: "subscribed" })); 
			   //Adds the accepted jabber id to the map
			   push_map( stanza.getAttribute("from") ); 
			 } else {
			   // Rejected the Friend Request
			   SharedConnObj.connection.send($pres({ to: stanza.getAttribute("from") , type: "unsubscribed" }));
			return true;

	//---------------------Helper Function------------------------------
	//This is used as a helper function for on_subscription_request
	// These functions ensure that you dont need to accept the friend request of a person again and again.
	//Basically we are adding the accepted friends jabber id to a map, and checking if the new friend request is alredy accpeted or not.
	var accepted_map={};  //store all the accpeted jid
	//Check if the jabber id is in the map
	function is_element_map(jid){
		if (jid in accepted_map) {return true;} 
		else {return false;}	
	//Adds jabber id into the map
	function push_map(jid){

	//Finally returns  the SharedConnObj
	return SharedConnObj;

CHATS : [ i.e. Contacts or Rosters ]

2 13

This is the next page that comes after successful login. Here we can see all our contacts, and we can start a conversation by clicking on there icons.


<ion-view style="" id="page3" title="Chats">

    <ion-content class="has-header" >
	<!-- Text field to send Friend Request to a jabber id -->
	<div class="item item-input-inset">
		<label class="item-input-wrapper">
		  <input type="text" placeholder="Jabber ID" ng-model="add_jid">
		<button class="button button-small icon ion-plus" ng-click="add(add_jid)">  <!-- Add function is called -->

		<!-- Roster list -->
        <ion-list style="">
		<!-- chat in chats is used to iterate through chats array-->
		<!--chatDetails() is trigged when user clicks on the listed contact-->
		 <ion-item class="item-remove-animate item-avatar item-icon-right" ng-repeat="chat in chats" type="item-text-wrap" ng-click="chatDetails(">
			<img ng-src="{{chat.face}}">
			<i class="icon ion-chevron-right icon-accessory"></i>

			<!-- Responsive delete button!-->
			<ion-option-button class="button-assertive" ng-click="remove(chat)">



// Chats service handles the rosters and other functions  
// ChatDetails service handles the chatDetails screen varaible i.e. holds to_jabber_id and from_jabber_id 
// $state for forwarding 
.controller('chatsCtrl', function($scope,Chats,$state,ChatDetails) {
	 $scope.chats = Chats.allRoster();  //gets the chats array
	  //Remove functions
	  $scope.remove = function(chat) {
		Chats.removeRoster(chat);  // Call the remove function
		ChatDetailsObj.setTo(to_id);  	// sets the to_jabber_id in the ChatDetails service so that it can be accessed within any class
		$state.go('tabsController.chatDetails', {}, {location: "replace", reload: true});
	  $scope.add = function(add_jid){
		Chats.addNewRosterContact(add_jid);  //Adding friend request


.factory('Chats', ['sharedConn','$rootScope','$state', function(sharedConn,$rootScope,$state){
	connection=sharedConn.getConnectObj();  //gets the connection object from SharedConn
	ChatsObj.roster=[]; //Holds the roster list 

	// This will load the rosters
	loadRoster= function() {
			//Sends the roster iq (*iq => Information Query)
			var iq = $iq({type: 'get'}).c('query', {xmlns: 'jabber:iq:roster'});
					//-----------*********Handler function for -> On recieve roster iq****---------------------------//
					function(iq) {
						if (!iq || iq.length == 0)  // if nothing recieved then send null
						//jquery load data after loading the page.This function updates data after jQuery loading
						$rootScope.$apply(function() {
									id: $(this).attr("jid"),
									name:  $(this).attr("name") || $(this).attr("jid"),
									lastText: 'Available to Chat',
									face: 'img/ben.png'
					// set up presence handler and send initial presence
					//-----------------****Handler function for -> on recieve precence iq********----------------//
						function (presence){		
						   var from = $(presence).attr('from'); // the jabber_id of the contact
						   if (presence_type != 'error'){
							 if (presence_type === 'unavailable'){
								console.log("offline"); //alert('offline');
							   var show = $(presence).find("show").text(); // this is what gives away, dnd, etc.
							   if (show === 'chat' || show === ''){
								 console.log("online"); //alert('online');
						   return true;
					, null, "presence");
						//----------------******handler function for on recieve update roster iq****--------------//
						function(iq) {
							if (!iq || iq.length == 0)
							//jquery load data after loading the page.This function updates data after jQuery loading
							$rootScope.$apply(function() {
								//Jquery code
								Basically $(iq) is an xml response.
								find("item") will return the array of <item subscription="value" />
								each(	function(){}  is used to loop through all items
								$(iq).find("item").each( function(){  
									//roster update via Client 1(ie this client) accepting request
											id: $(this).attr("jid"),
											name:  $(this).attr("name") || $(this).attr("jid"),
											lastText: 'Available to Chat',
											face: 'img/ben.png'
									// Waiting for the Client 2 to accept the request
									else if ( $(this).attr("subscription")=="none"  && $(this).attr("ask")=="subscribe" ){
											id: $(this).attr("jid"),
											name:  $(this).attr("name") || $(this).attr("jid"),
											lastText: 'Waiting to Accept',   // Adds roster and waits for the client 2 to accept request
											face: 'img/ben.png'

									//roster update via Client 2 deleting the roster contact
									else if($(this).attr("subscription")=="none"){ 
										//Removes the roster
										ChatsObj.removeRoster( ChatsObj.getRoster( $(this).attr("jid") ) );
								//Reloads the page
								$state.go('tabsController.chats', {}, {location: "replace", reload: true});

					,"jabber:iq:roster", "iq", "set");
					return ChatsObj.roster;
	//Helper function
	ChatsObj.allRoster= function() {
		return ChatsObj.roster;
	//splice is used to remove object from an array
	ChatsObj.removeRoster= function(chat) {
		ChatsObj.roster.splice(ChatsObj.roster.indexOf(chat), 1);
	//Gets the roster with id
	ChatsObj.getRoster= function(chatId) {
		for (var i = 0; i < ChatsObj.roster.length; i++) {
			if (ChatsObj.roster[i].id == chatId) {
			  return ChatsObj.roster[i];
	//Add Friend Request
		connection.send($pres({ to: to_id , type: "subscribe" }));		
	return ChatsObj;




Chat details is the page where users can chat with other client


For CSS styling please refer to my git hub download link.

<ion-view style="" id="page6" title="Chat Details">

	<!--Chat messages-->
	<ion-content class="content-stable">
		<div ng-repeat="message in messages" ng-class="{other: message.userId != myId}" class="messages">
		   	<div class="message" >
				<span >{{ message.text }}</span>

	<!-- Chat box-->
	<ion-footer-bar keyboard-attach class="bar-stable item-input-inset">
		<label class="item-input-wrapper">
			<input type="text" placeholder="Type your message" on-return="showSendMessage(); closeKeyboard()" ng-model="data.message" on-focus="inputUp()" on-blur="inputDown()" />
		<button class="button button-clear" ng-click="showSendMessage()">



functions for hiding the keyboard


.controller('chatDetailsCtrl', function($scope, $timeout, $ionicScrollDelegate,sharedConn,ChatDetails) {

  $ = {};
  $scope.myId = sharedConn.getConnectObj().jid;
  $scope.messages = [];  // Holds the array of messages
  $scope.to_id=ChatDetails.getTo();  //ChatDetails service holds the to_jabber_id

  var isIOS = ionic.Platform.isIOS(); 
		var to_jid  = Strophe.getBareJidFromJid(to);
		var timestamp = new Date().getTime();
		//Creats a message xml dom object
		//Timestamp is used as the unique id for each message.
		//type = chat

		var reqChannelsItems = $msg({id:timestamp, to:to_jid , type: 'chat' })
		//Sends the message via connection object		
	//This is basically a UI function to set the message UI
  $scope.showSendMessage = function() {

    var d = new Date();
    d = d.toLocaleTimeString().replace(/:\d+ /, ' ');

	//Pushes our message to message array
      userId: $scope.myId,
      text: $,
      time: d
	//Remove message from the send text field
    delete $;
	//Scrolls to the bottom

	//Handles broadcast request -- ie msgRecievedBroadcast
     $scope.$on('msgRecievedBroadcast', function(event, data) {

  //Handles message recieve after broadcast response
	var from = msg.getAttribute('from');
	var type = msg.getAttribute('type');
	var elems = msg.getElementsByTagName('body');
	var d = new Date();
    d = d.toLocaleTimeString().replace(/:\d+ /, ' ');

	if (type == "chat" && elems.length > 0) {
		var body = elems[0];
		var textMsg = Strophe.getText(body);
		//Pushes recieved message into the message array
		  userId: from,
		  text: textMsg,
		  time: d
		console.log('Message recieved from ' + from + ': ' + textMsg);

  //-------------------****Helper Functions*******-------------------------------------	
  $scope.inputUp = function() {
    if (isIOS) $ = 216;
    $timeout(function() {
    }, 300);


  $scope.inputDown = function() {
    if (isIOS) $ = 0;

  $scope.closeKeyboard = function() {
    // cordova.plugins.Keyboard.close();



Basically this hold the shared variables like to_jabber_id

.factory('ChatDetails', ['sharedConn','$rootScope', function(sharedConn,$rootScope){
	ChatDetailsObj.setTo = function(to_id){;
	ChatDetailsObj.getTo = function(){
	return ChatDetailsObj;	



17 thoughts on “Ionic XMPP chat Client using Strophe.js: Part 3

  1. Hi Arjun I have worked on ionic jabber code you have provide and was able to register but after login when send a request to users login I am not able to see popup to accept the request so that user can chat. Please help


      1. Sorry for the trouble @disqus_2Gn54thYko:disqus . I am currently busy with my academic works and placement procedures. I will surely update you when the issue is fixed. Have a nice day 🙂


  2. thanks for reply I cannot see converse.js file where t find it also I have tried the link provided by you and tried to open two browser where I was able to register user, but not able to chat as it says waiting for request and then after restart it shows avilable but unable to chat I used ionic serve –lab


    1. Have you read this article :


      Hope you have configured the server and index.html properly.


      1. Open index.html in two browsers.

      2. Create two accounts (sender@xvamp, reciever@xvamp) on separate browsers.

      3. From sender@xvamp send a friend request to reciever@xvamp.

      4. From second browser accept the request.

      5. Now you can see reciever@xvamp in the available people list.

      Thats it.!


  3. Hi,
    How can I search with usernames from all available users in the server.
    I want to add a functionality where a user can search another user based on username and add the another user from the search.

    Please help.



    1. I don’t think, it would be possible. We are adding people to our roster list by sending them a request using jabber id. The jabber id is similar to an email address. This jabber id could be registered to any site.

      If you want to explicitly search a user within your domain, I would suggest, you to maintain a database for the same.


  4. How Can I add new user to XMPP OPenfire server? I tried strophe.js.register, but the code is not working. Can you post some source code which will compatible with your code base.
    My Code :
    var callback = function (status) {
    if (status === Strophe.Status.REGISTER) {
    // fill out the fields
    connection.register.fields.username = “juliet”;
    connection.register.fields.password = “R0m30”;
    // calling submit will continue the registration process
    } else if (status === Strophe.Status.REGISTERED) {
    // calling login will authenticate the registered JID.
    } else if (status === Strophe.Status.CONFLICT) {
    console.log(“Contact already existed!”);
    } else if (status === Strophe.Status.NOTACCEPTABLE) {
    console.log(“Registration form not properly filled out.”)
    } else if (status === Strophe.Status.REGIFAIL) {
    console.log(“The Server does not support In-Band Registration”)
    } else if (status === Strophe.Status.CONNECTED) {
    // do something after successful authentication
    } else {
    // Do other stuff

    SharedConnObj.register = function (jid, pass, name) {
    //to add register function
    SharedConnObj.connection = new Strophe.Connection(SharedConnObj.BOSH_SERVICE, { ‘keepalive’: true }); // We initialize the Strophe connection.
    SharedConnObj.connection.register.connect(XMPP_DOMAIN, callback,1,1);


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s