You are on page 1of 11

CreatingaRealtimeImageSharingAppWithIonicandSocket.

io
IfyouareintohybridmobileappdevelopmentyoumusthavecomeacrossIonicFramework.
BasicallyIonicletsyoubuildmobileappsusingyourfavoritewebtechnologies.Thatmeansyoudon't
needtolearnJava,ObjectiveCetctocreatesmartphoneapps.RatheryouwillcreateHTML5appsas
usualandtheywillberunninginsmartphonesasWebView.BehindthescenesIonicusesCordovafor
itscoretools.AnothergreatthingaboutIonicisthatitutilizesAngularJSperfectlysothatyoucan
createrichandrobustAngularJSappsthathappentorunassmartphoneapps!
Thistutorial,targetedtowardsIonicbeginners,showshowtocreatearealtimeimagesharing
appusingIonicandalittlebitofSocket.io.Theonlyrequirementofthistutorialisthatyoushould
havebasicknowledgeofIonicandworkingknowledgeofNode.js.So,let'sgetstarted.

GettingStarted
Theappwearegoingtodevelopisreallysimple.Wewillcreateamobileappforimagesharing.Users
havetoprovideanicknametoenterthechat.Onceyouareinyoucanbrowseyourdeviceand
uploadimageswhichwillbesharedwithallotherusersoftheapp.Tomakeitworkwewilluse
Node.js+Socket.ioasthebackend.Finally,ourIonicappwillcommunicatewiththisbackendtowork.
Hereisavideowhichshowshowtheendproductwilllookandwork.Iamworkingwithtwobrowser
windowsandsharingimages.Butwewillalsoseehowtoinstalltheapponyoursmartphoneand
shareimageswithyourcomputer.

DesigningBackend
Tostartlet'sfirstdesigntheNode.jsbackend.So,justcreateanemptydirectorycalledsocketio
backendandintheterminaltypecdsocketiobackend.Thisisthedirectorynameusedbyme.Youcan
chooseadifferentnameifyoulike.Nowtypenpminit,hitenteranditwillaskyousomequestions
regardingtheappname,version,entrypointetc.Havealookatthescreenshotbelowtogetanidea.

Justfillupname,version,description,entrypointandskiptherestbyhittingenter.Let'snameour
entrypointasapp.js.ThisisthescriptthatwillbootstrapourNode.jsapp.Intheenditwillshowyou
aconfirmationasfollowing:

Typeyesanditwillcreateapackage.jsonforyou.

NowyouneedtoinstallExpressandSocket.iomodulesinyourapp.So,typethefollowingcommands
intheterminal:
npminstallexpresssave

Hitenterandthentype
npminstallsocket.iosave

Hitenterandyouwillhaveabrandnewnode_modulesdirectorywiththeabove2modules.
Thenextstepforyouistocreateafileapp.jsinyourappandpastethefollowingcodeinit:

varapp=require('express')();
varserver=require('http').Server(app);
vario=require('socket.io')(server);

io.on('connection',function(socket){
socket.on('event:new:image',function(data){
socket.broadcast.emit('event:incoming:image',data);
});
});

server.listen(8000,function(){
console.log('Socket.ioRunning');
});

Note:Ifyoudon'twanttofollowtheaboveprocessyoucandirectlyheadovertoGitHuband
downloadthezipcontainingthesourcecode.
So,theabovecodefirstrequiresexpressandcreatesanapp.Nextwerequirehttpmoduleandcreate
aserver.

Finally,wecreateavariablecallediotoemitandreceiveevents.Wegetitbyrequiringsocket.ioand
passingthevariableservertoit.
Socket.ioisverysimpletouse.Wheneveranewclientisconnectedtoyourappaconnectioneventis
triggeredandthecallbackexecutes.Asocketisalsopassedtothecallbackwhichyoucanuseto
communicatewiththeclientbidirectionally.Youlistentoeventsbywritingsocket.on('EVENT_NAME').
Inthiscasewelistenforacustomeventcalledevent:new:imagewhichindicatesaconnecteduseris
sendingsomeimage.Weemitthiseventfrombrowserwhentheuserneedstoshareanimage.
Thedataparameterinthecallbackrepresentsthedata.Itconsistsoftwo
properties:senderandimage.Oncewehavethedatafromoneuserwebroadcastittoallotherusers
excepttheonewhosentit.Alltheconnecteduserslistentothiseventintheclientsidesothatthey
canupdatetheirviewwiththeincomingimage.Weemittheeventby
writing:socket.broadcast.emit('event:incoming:image',data).
Well,weareallset!Justrunnodeapp.jsandyourSocket.iobackendstarts.

DesigningIonicApp
TheIonicappisnothingbutanHTML5appwhichpresentsafileuploadbuttontotheuser.When
someoneclicksonithewillbeaskedtobrowseafile.Onceusersselectafilewewillemitaneventto
socket.ioserverwiththeimagedata.
BeforeproceedingyoucandownloadthesourcecodefromGitHubanduseitasreference.
So,let'screateanemptyIonicappfirst.IassumeyoualreadyhaveIonicsetuponyourmachine.The
followingcommandshouldcreateasimpleIonicapp:
ionicstartimageshareblank

Anewappwillbecreatedinthecurrentdirectorywiththenameimageshare.AgainIchosethis
nameonmymachine.Youcandefinitelyhaveadifferentname.
NowyouaregoingtoneedSocket.ioclientsothatyoucanlistentoeventsandemitnewonesfrom
Ionicside.Don'tworrythat'ssupereasy.Justheadovertohttps://cdn.socket.io/socket.io
1.0.6.jsanddownloadthescript.Oncedoneputitinsidewww/libinyourIonicproject.That'sit!
Fromnowonlet'srefertotheIonicprojectrootdirectoryas/imageshare.
DesigningHomeScreen
So,beforeanyonesendsanimagehe/sheneedstoprovideanickname.Youhavealreadyseenthisin
chatrooms,right?So,let'sdesignascreenforthat.AsweareusingAngularJSwewilltakeastate
basedapproach.IonicalreadycomeswithAngularJSUIRouter.So,youcanjuststartdefiningstates
rightaway.
Let'sdefinestatehomewhichaskstheuserforanickname.Openup/image
share/www/js/app.jsandcreateaconfig()blockwiththefollowingcontent.

config(['$stateProvider',function($stateProvider){
$stateProvider.state('home',{
url:'/home',
controller:'HomeController',

templateUrl:'views/home.html'
});
}]);

ThisisausualUIRouterstate.Weprovideaurl,controllerandtemplateUrlforthestate.The
controllerHomeControllergoesinsidethefile/imageshare/www/js/controllers.js.Thisfiledoesn't
existyet.So,youneedtocreateit.Hereistheinitialcontentofthefile:

angular.module('com.htmlxprs.imageShare.controllers',[]).controller('HomeController',['$scope','USER','$state',function($sc
ope,USER,$state){
$scope.user={};
$scope.next=function(){
USER.name=$scope.user.name;
$state.go('chat');
}
}]);

Ihavenamedthemoduleascom.htmlxprs.imageShare.controllersanduseditforregisteringallthe
controllers.TheHomeControllerdefinesascopemodeluserwhichholdsthenicknameoftheuser.It
alsodefinesascopefunctionnext()whichtakesthenickname,putsitinaservicesothatit'savailable
throughouttheappandfinallyproceedstothenextstatechat.
AsyounoticeUSERserviceholdsthenameofthecurrentuser.So,wecreatethisinside/image
share/www/js/services.jsasfollowing:

angular.module('com.htmlxprs.imageShare.services',[]).value('USER',{}).value('SOCKET_URL','localhost:8000');

IhavealsodefinedanothervalueserviceSOCKET_URLwhichholdsthesocket.ioserverIP.Inourcase
thebackendsocket.ioappisrunningonlocalhost:8000.
Nowlet'scometothetemplateUrlofthestatehomewhichpointsto/image
share/www/views/home.html.
<ionviewtitle="JustaMinute">
<ioncontenthasheader="true"padding="true">
<divclass="card">
<divclass="itemitemdivider">
Enteranickname
</div>
<divclass="itemitemtextwrap">
<inputtype="text"placeholder="Selectnickname"ngmodel="user.name"/>
</div>
</div>
<divclass="contentpadding">
<buttonclass="buttonbuttonpositivebuttonblock"ngclick="next()">
Next
</button>
</div>
</ioncontent>
</ionview>

Theimportantpieceis<inputtype="text"placeholder="Selectnickname"ngmodel="user.name"/>.
Itattachesamodeluser.nametotheinputfieldwhichstoresthenicknameoftheuser.Finally,when
theuserclicksthebuttonourfunctionscope.next()executesandtakestheusertostatechat.
So,thisishowourhomescreenlookslike:

Designin
ngChatScrreen
Nowthatyouaredonewithfirstsccreenlet'sdeefinetheseco
ondstatewh
hichischat.Thiswillallow
w
userstosshareimagesandseetheimagesshareedbyothers.So,westartwithdefiningthestateass
following:

.config(['$sttateProvider',fu
unction($statePrrovider){
$stateP
Provider.state('h
home',{
url:'//home',
controller:'HomeController',
temp
plateUrl:'views//home.html'
}).statee('chat',{
url:'//chat',
controller:'ChatConttroller',
temp
plateUrl:'views//chat.html'
});
}]);

TheChatC
Controllergo
oesinto/imageshare/ww
ww/js/contro
ollers.js.Currrentlyitdoessn'tdoanythiing,
butlaterwecanputsomethinginitifneeded.Hereishowtthecontrolleerisdefined:

angular.module('com.htmlxprs.imageSharre.controllers').ccontroller('ChatController',['$sccope','$rootScope',function($scope,$
rootScope){{

}]);

NowthettemplateUrlpointstovie
ews/chat.htm
mlwhichlookkslikefollowing:
<ionviewtitle="Let'sshare
esomepics">
<ionconttenthasheaderr="true"padding="true">
<browssefile></browsefile><!Direcctive1>
<chatlist></chatlist><!Directive2>
</ioncon
ntent>
</ionview>>

Youcansseethetwod
directivesbro
owseFileand chatList.Don
n'tworry!Thesearecusto
omdirectivessand
wewillgeettothemsh
hortly.WithaalittlebitofC
CSSour2ndsscreennowlo
ookslikefollo
owing:

Nowlet'sseehowthe
edirectivesw
work!

browseFFileDirectivve
Thisdirecctiveallowstheuserstoseelectanimaggefromtheirrgalleryandb
broadcastsan
eventeve
ent:file:selecttedoncethefileisselecteed.Italsosen
ndsadatawiththeeventwhichhastw
wo
propertiees:
1. se
ender:Theniicknameofttheuserwhosendstheim
mage.
2. im
mage:ThedaataURLoftheeselectedim
mage.
Thedirectiveisdefine
edinside/imaageshare/ww
ww/js/directiives.jsandth
hecontentisasfollowing:
angular.module('com.htmlxprs.imageSharre.directives',[]).directive('brow
wseFile',['$rootSScope','USER',function($rootSco
ope,US
ER){
return{
scope:{{

},
replacee:true,
restrictt:'AE',
link:fun
nction(scope,ele
em,attrs){

scop
pe.browseFile=fu
unction(){
do
ocument.getElem
mentById('brow
wseBtn').click();
}

angu
ular.element(document.getElem
mentById('brow
wseBtn')).on('chaange',function(ee){

varrfile=e.target.files[0];

anggular.element(d
document.getEleementById('bro
owseBtn')).val(''));


varfileReader=newFileReader();

fileReader.onload=function(event){
$rootScope.$broadcast('event:file:selected',{image:event.target.result,sender:USER.name})
}i

fileReader.readAsDataURL(file);
});

},
templateUrl:'views/browsefile.html'
}
}]);

AndthetemplateUrlofthedirectivepointstoviews/browsefile.html:

<divclass="contentpadding">
<inputtype="file"id="browseBtn"accept="image/*"/>
<buttonclass="buttonbuttonblockbuttoncalmionpaperclipuploadButton"ngclick="browseFile()">
SelectFile
</button>
</div>

Nobodylikesanuglyuploadbutton,right?So,wecreateafileinputandkeepithidden.Nextwe
createacustombuttonandstyleitthewaywelike.Whensomeoneclicksonthisbuttonwe
programmaticallytriggerclickonfileinputandtheuserisaskedtoselectafile.

Note:Whiletheaboveworksinbrowsers,itdoesn'tworkinAndroid(wellatleastnotinAndroid4.3).
So,thetrickusedhereissettingtheheightofthefileinputto0andmakingtheoverflowauto.

Next,weattachngclicktoourcustombutton.Whensomeoneclicksit,
thescopefunctionbrowseFile()runs.Ifyouseethedirectivecodeaboveyouwillseethatthis
functiondoesnothingbuttriggeringaclickonfileinput.

document.getElementById('browseBtn').click();

browseBtnistheidofthefileinput.

Next,weattachachangeeventlistenertobrowseBtnwhichgetscalledwheneverafileisselected.
Weobtainthefileobjectthroughthefollowingcode:

varfile=e.target.files[0];

Next,wesetthevalueofthefileinputtoemptysothatachangeeventwillbefirediftheuserselects
thesameimageagain.Thefollowingcodedoesthat.

angular.element(document.getElementById('browseBtn')).val('');

NowweinstantiateaFileReaderandreadthefileasdataURL.Thisdatawillbesenttothesocket.io
serverwhichwillbesubsequentlybroadcastedtootherusers.
7


varfileReader=newFileReader();

fileReader.onload=function(event){
$rootScope.$broadcast('event:file:selected'{image:event.target.result,sender:USER.name});
}

fileReader.readAsDataURL(file);

Webroadcastaneventevent:file:selectedon$rootScopeandpassadata.Thiseventishandled
bychatListdirectiveandsenttosocket.ioserver.That'sallaboutthisdirectivewhichisthenused
inchat.htmlasfollowing:

<browsefile></browsefile>

chatListDirective
ThisdirectiveusestheSocket.ioclientlibrarywedownloadedearliertocommunicatewiththe
backend.So,thedirectivelookslikefollowing:
angular.module('com.htmlxprs.imageShare.directives').directive('chatList',['$rootScope','SOCKET_URL',function($rootScope,
SOCKET_URL){
return{
replace:true,
restrict:'AE',
scope:{

},
link:function(scope,elem,attrs){

varsocket=io(SOCKET_URL);

scope.messages=[];

socket.on('event:incoming:image',function(data){

scope.$apply(function(){
scope.messages.unshift(data);
});

});

$rootScope.$on('event:file:selected',function(event,data){

socket.emit('event:new:image',data);

scope.$apply(function(){
scope.messages.unshift(data);
});

});
},
templateUrl:'views/chatlist.html'
}

}]);

ThetemplateUrlpointstoviews/chatlist.htmlwhichusesngrepeattorendertheimages:

<divclass="list">
<aclass="item"href="#"ngrepeat="messageinmessagestrackby$index">
<h2>{{message.sender}}</h2>
<imgngsrc="{{message.image}}">
</a>
</div>

Nowlet'scheckoutthelinkfunctionofthedirective.Thefirstlinevar
socket=io(SOCKET_URL)establishesawebsocketconnectiontothebackendSocket.ioserver.The
nextlinedefinesascopemodelmessageswhichholdsallthemessages.

AsyoualreadyknowbrowseFilebroadcastsaneventevent:file:selectedon$rootScopewhenafileis
selected.So,thisdirectivessubscribestothiseventandemitsaneventevent:new:imagetothe
Socket.ioserverwiththeimagedata.Alsoitaddstheimagetothescope.messagesarraysothatit
canberenderedontheview.Wealsousescope.$apply()sothatthechangesinthemodelwillbe
reflectedintheview.Ifyouremember,inthebackendwelistentoeventevent:new:imageand
broadcasttheimagetoallconnectedusers.Whenithappensclientswillreceivean
eventevent:incoming:image.So,ourdirectivelistenstothiseventandupdatestheviewsothatthe
usercanseewhatothersaresharing.Thefollowingcodedoesit:

socket.on('event:incoming:image',function(data){

scope.$apply(function(){
scope.messages.unshift(data);
});

});

That'sit!Wehaveourdirectivereadywhichisusedinchat.htmlas:

<chatlist></chatlist>

Makesureinyourapp.jsyoulisttherequiredmoduledependencies:

angular.module('imageShare',
['ionic','com.htmlxprs.imageShare.controllers','com.htmlxprs.imageShare.services','com.htmlxprs.imageShare.directives'])

Whentheapprunswewanttoshowthehomescreen.So,insidetherunblockyouneedtoloadthe
statehomeasfollowing:

$state.go('home');

Finally,theindex.html(/imageshare/www/index.html)isasfollowing:

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf8">
<metaname="viewport"content="initialscale=1,maximumscale=1,userscalable=no,width=devicewidth">
<title></title>

<linkhref="lib/ionic/css/ionic.css"rel="stylesheet">
<linkhref="css/style.css"rel="stylesheet">

<!IFusingSass(rungulpsassfirst),thenuncommentbelowandremovetheCSSincludesabove
<linkhref="css/ionic.app.css"rel="stylesheet">
>

<!ionic/angularjsjs>
<scriptsrc="lib/ionic/js/ionic.bundle.js"></script>

<!cordovascript(thiswillbea404duringdevelopment)>
<scriptsrc="cordova.js"></script>

<!yourapp'sjs>
<scriptsrc="js/app.js"></script>
<scriptsrc="js/controllers.js"></script>
<scriptsrc="js/services.js"></script>
<scriptsrc="js/directives.js"></script>

<!Socket.ioclientScript>
<scriptsrc="lib/socket.io1.0.6.js"></script>

</head>
<bodyngapp="imageShare">
<ionnavbarclass="barpositive">
<ionnavbackbuttonclass="buttonbuttoniconiconionios7arrowleft">Back</ionnavbackbutton>
</ionnavbar>
<ionnavviewanimation="slideleftright"></ionnavview>
</body>
</html>

Itloadsthesocket.ioclientlibraryandotherrequiredmodulescripts.

SeeingTheAppLive
ToexperiencetherealtimenatureoftheappyoucanopenuptwobrowserinstancesjustlikeIdidin
thevideoandstartsharingpictures.Justgototherootofionicprojectandtypeionicserve.Thiswill
startaserveratlocalhost:8100.UsethesameURLintwobrowserwindows.

PackagingAsMobileApp
Ifyourcomputerandmobileareinsamenetworkyoucandeploytheapptoyoursmartphoneand
shareimagesbetweenthecomputerandphone.Currentlyweare
usinglocalhost:8000asSOCKET_URLinAngularJS.Beforeyoudeployyourapptomobileyouneedto

10

changethistorealIPofyourmachinebecauselocalhostwon'tberesolvedtooursocket.ioserverina
mobileapp.

Whenyoutypeionicserveyoucanseeitstartsaserverathttp://your_ip_here:8100.Youcanjust
copyyour_ip_hereportion,combineitwithport8000anduseitasSOCKET_URLvalueservice.Wedo
thisbecauseionicstartsaserveratport8100whichisusedbyourAngularJSapp.Butoursocket.io
serverrunsonport8000.
IfyouhavetroubleyoucanusethefollowingcommandstogettheIPdependingonyourOS.
IfyouareonMacyoucanusethefollowingcommandfromterminaltogettheIPofyourmachine:
ifconfig|grep"inet"|grepv127.0.0.1

Hitenter.JustcopytheIPlistednexttoinetanduseitastheAngularJSvalueserviceSOCKET_URL.
Don'tforgettouseport8000withtheIP.Forexample,iftheIPis127.0.0.1yourvalueservicewillbe:

.value('SOCKET_URL','http://127.0.0.1:8000');

OnWindowsyoucanrunthecommandipconfigtoobtaintheIP.
Todeploytheapptoyoursmartphoneyoucanusethefollowingcommandsfromtherootofyour
Ionicproject:
Android:
ionicplatformaddandroid
ionicbuildandroid
ionicrunandroid
MakesureyouhaveyourAndroiddeviceconnectedtoyourmachineandtheUSBdebugginginON.
iOS:
Repeatthesamesteps,asabovereplacing'android'with'ios'.YouneedanAppledeveloperaccount
topublishtheapptoiPhoneandtest.Ifyoudon'thaveanaccountyoucantesttheappontheiOS
emulator.Todothatyoucanusethefollowingcommandafterionicbuildios:
ionicemulateios
Makesureyouhaveiossiminstalledinyoursystem.
ThingsToImprove
Currentlytheusersdon'tgetfeedbackonhowmuchdata(in%)isuploaded.Forexample,ifyoushare
alargeimageitwilltakesometimetobeuploadedtoserver.Whatyoucandoisuploadthefilein
chunkstoSocket.ioandprovidefeedbacktotheuser.Hereisanexcellenttutorialontuts+covering
howtocreatearesumablefileuploaderusingSocket.ioandHTML5FileAPI.
Letusknowyourexperience/questionsthroughcomments.Feedbackisalwayswelcome.Lastlydon't
forgettosubscribetoournewslettertogetmoresuchupdatesdeliveredtoyourinbox.

11