Using Parse with AngularJS

This article is part of our Startup Insight series, a weekly digest for early and growth stage entrepreneurs. You're welcome to subscribe here. 

This article is part of our Startup Insight series, a weekly digest for early and growth stage entrepreneurs. You're welcome to subscribe here

With all the variety and flavours of application frameworks and platforms currently in the market, deciding on which one(s) to use when you’re starting a new project can be a challenging task. You must take into consideration your project’s requirements and your team’s expertise, but also the popularity and support each framework has.

For our presentation software Slidebean, we decided to use two of the hottest app frameworks and components out there: AngularJS as our javascript MVC framework, and Parse as our back-end and cloud storage solution. If you haven’t heard about them, I highly encourage you to do so now, as they’re becoming more and more popular every day. By the way, if you haven’t tried out Slidebean, do so now!

There are few kinks here and there when it comes to using Parse with AngularJS. These are few tips I’ve discovered along the way to iron them out. Hope it saves you some time.

1. Define getters and setters for each of your Parse class fields

Parse lets you get and set custom fields in your classes using – you guessed it – the get and set methods. On Slidebean, users create Presentations, which have a title property. So let’s say we have a presentation variable in a controller’s $scope, and we want to display its title. We would do something like this on our html view:

<div>{{ presentation.get("title") }}</div>

AngularJS, however, uses plain javascript object properties to read and update the values. So if we wanted to update the presentation title using an input field, the ng-model directive is not compatible:

<!-- This doesn't make sense, but just for the sake of the example: -->
<input type="text" ng-model="presentation.set('title')">

One approach I read about here, is to forget about using the Parse Javascript SDK and rely on their REST API to handle models. But there’s another much simpler solution, and that is to define javascript getters and setters for each of your Parse classes.

So using AngularJS, we use factory to define a model class called Presentation. The trick is that, inside, we also define a getter and setter per property. In this example, we only have one called title:

angular.module('SlidebeanModels').
factory('Presentation', function() {

var Presentation = Parse.Object.extend("Presentation", {
// Instance methods
}, {
// Class methods
}
});

// Title property
Presentation.prototype.__defineGetter__("title", function() {
return this.get("title");
});
Presentation.prototype.__defineSetter__("title", function(aValue) {
return this.set("title", aValue);
});

return Presentation;
});

Now, we have a model class that works seamlessly with ng-bind. And so, to update the presentation title, we just do this:

<input type="text" ng-bind="presentation.title">

2. Store your current Parse user in the $rootScope

I’ve read that you should almost never set variables in your AngularJS app$rootScope. Parse provides the very convenient Parse.User.current() method, which returns the current active session user. Thing is, changes made to this user variable happen outside of the AngularJS world, and therefore are hard to keep track of and be digested by Angular.

I find it convenient, then, to store the current Parse user in a $rootScopevariable called sessionUser (or whatever you wish to call it), thus making it accessible to every controller $scope. This var is initialized right after the AngularJS app starts running:

 

angular.module('SlidebeanApp')
.config(function ($routeProvider, $locationProvider) {
// Config goes here
})
.run(function($rootScope) {

Parse.initialize("parse app", "parse credentials");

$rootScope.sessionUser = Parse.User.current();

});

To keep things in order, we created a singleton called SessionService, and we made it so that this is the only place where $rootScope.sessionUser is manipulated. This service handles log-ins and log-outs, and updates the variable accordingly.

For example, a navigation bar controller that displays the current user can just react to this var, and modify its looks and functionality accordingly:

<!-- Show Log In and Sign Up buttons when there's no session -->
<ul ng-show="sessionUser == null">
<li><button type="button" ng-click="logIn()">Log In</button></li>
<li><button type="button" ng-click="signUp()">Sign Up</button></li>
</ul>

<!-- Display the current user when there's a session -->
<ul ng-show="sessionUser != null">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{{ sessionUser.name }}
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a ng-href="/profile">Profile</a></li>
<li><a ng-href="#" ng-click="signOut()">Sign Out</a></li>
</ul>
</li>
</ul>

3. Extend Parse.User and include it from the get-go

I found the documentation for extending Parse.User not clear. But basically, if you want to extend the base Parse.User class, it’s just as easy as extending any other Parse.Object. The caveat is: make sure you include your custom class before calling Parse.User.current(), so that you receive an instance of your class.

In Slidebean, we wanted to have a custom getImage function for users, where we could abstract away the complexity of retrieving the user’s avatar either from Facebook or from Gravatar. So we extended Parse.User like this:

 

angular.module('SlidebeanModels').
factory('SlidebeanUser', function() {

var User = Parse.User.extend({

getImage : function() {
// return the appropriate facebook image url or gravatar image url
}

}, {
// Class methods
});

return User;
});

Then, we just made sure to include our SlidebeanUser class as a dependency for the app’s run method:

.run(function($rootScope, $location, SlidebeanUser) {
Parse.initialize("parse app", "parse credentials");

// This is now an instance of our SlidebeanUser class <img src="http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif?m=1129645325g" alt=":)" class="wp-smiley">
$rootScope.sessionUser = Parse.User.current();

// and this would work (if there's a user signed in, of course):
var imageUrl = $rootScope.sessionUser.getImage();
})

4. Delay the Facebook SDK initialization until your AngularJS app has begun running

If you allow users to sign in using Facebook to your app, it’s a good idea to load Facebook SDK until after you have initialized Parse. And it’s a good idea to initialize Parse after your AngularJS app begins running. In general, then, it’s a good idea to have all your application initialization code in one place, and a good place to do that is in your AngularJS app’s run method.

In our Slidebean app (and I’d suspect in most apps), the initialization order goes like this:

  1. AngularJS
  2. Parse
  3. Facebook

and to achieve this, our app run method looks like this. Notice how the Facebook SDK async load is placed in here, instead of placed somewhere in the HTML body where it’s normally suggested to.

 

.run(function($rootScope, $location, SlidebeanUser) {

// 1) AngularJS app is now running

// 2) Initialize Parse and set the current user to the $rootScope
Parse.initialize("parse app", "parse credentials");

$rootScope.sessionUser = Parse.User.current();

// 3) Finally, init Facebook
window.fbAsyncInit = function() {
Parse.FacebookUtils.init({
appId: 'facebook app id',
channelUrl : '//www.slidebean.com/fbchannel.html',
status: true,
cookie: true,
xfbml: true
});
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
});

Don’t forget to add this in your index.html though, which the Facebook SDK needs:

<div id="fb-root"></div>

5. Wrap Parse async calls inside $q promises

You’ll find that it’s a good idea to wrap Parse asynchronous calls inside AngularJS promises, instead of just performing Parse queries directly in your models or controllers. One of the benefits is that your data changes will be digested by AngularJS automatically.

Let’s say on Slidebean we wanted to retrieve all the presentations belonging to the current user. We first defined the Presentation model class and added a static method to retrieve presentations by a given owner. Inside this method, we wrap the async query using AngularJS’ $q promises:

angular.module('SlidebeanModels').
factory('Presentation', function($q) {

var Presentation = Parse.Object.extend("Presentation", {
// Instance methods
}, {
// Class methods

listByUser : function(aUser) {
var defer = $q.defer();

var query = new Parse.Query(this);
query.equalTo("owner", aUser);
query.find({
success : function(aPresentations) {
defer.resolve(aPresentations);
},
error : function(aError) {
defer.reject(aError);
}
});

return defer.promise;
}
});

// Properties
Presentation.prototype.__defineGetter__("owner", function() {
return this.get("owner");
});
Presentation.prototype.__defineSetter__("owner", function(aValue) {
return this.set("owner", aValue);
});
Presentation.prototype.__defineGetter__("title", function() {
return this.get("title");
});
Presentation.prototype.__defineSetter__("title", function(aValue) {
return this.set("title", aValue);
});

return Presentation;
});

Then, inside of any given controller, if we want to fetch the presentations, we would do something like this:

 

angular.module('SlidebeanApp')
.controller('DashboardCtrl', function($scope, Presentation) {

Presentation.listByUser($scope.sessionUser).then(function(aPresentations) {
$scope.presentations = aPresentations;
}, function(aError) {
// Something went wrong, handle the error
});
});

And that’s it. Now the list of presentations can be displayed in HTML using ng-repeat.

That’s a wrap, for now. If you have any more tips, by all means post them in the comments and I’ll keep adding them in the post. Feel free to post any questions too. Gotta prepare a presentation? Try Slidebean! We support adding code blocks so you can show off your examples, and other great features.

Happy coding ^___^