This is the third part of Building SPA using AngularJS Series. The topics we’ll cover are:
- Building SPA using AngularJS – Part 1
- Building SPA using AngularJS – Part 2
- Building SPA using AngularJS – Part 3 (This Post)
Update (2014-May-5) New post which covers adding GulpJS as Task Runner for our AngularJS application:
Update (2014-June-1) New post which covers Implementing AngularJS Token Authentication using ASP.NET Web API 2 and Owin middleware:
Building SPA using AngularJS – Part 3
In this post we’ll implement the last feature in our application where we’ll allow end users to bookmark their favorite places for future visits, so we need permanent repository to save this information. We’ll build our RESTful data service using Asp.Net Web API (any back end technology can be used to create this service, so if you are not familiar with Web API you can use another technology to build a service layer). To keep things simple we’ll save the bookmarked places information along with the username, so we’ll have single database table as the image below:
The end user will be asked to enter username in order to start bookmarking places, if there is available user in the context she can bookmark as many favorite places as she wants without asking for username. Username modal view will look like the below:
To view the saved bookmarked places which user has added, she can click on My Places menu on “index.html” page and a new view will be displayed containing list of all bookmarked places. View will be as the below:
So let’s start implementing those features.
Step 1: Building the RESTful service layer:
We’ll be using Entity Framework “Code First approach” to build our database, all this code is implemented in separate data access layer named “FoursquareAngularJS.Data”, I won’t dig into how to create the database model or use the factory pattern as I’ve blogged about them earlier, you can check this post for building the database model, and this post for creating the repository pattern. All you need to run this application locally is to have instance of SQLExpress or LocalDB, just configure the connection string and the database will be created automatically.
Our “FoursquareAngularJS.Web” project is configured to use Web API, we need to create two Web API Controllers one for Saving/Retrieving bookmarked places, and the other for checking if the username already exists in the database, I’ll not cover how we can configure Web API routes, you can check my previous post to follow along. You can view source code of “PlacesController” here, for “UsersController” here, and “WebApiConfig” class here.
To check the JSON response format for the the method responsible to return bookmarked places for user, we can issue HTTP Get request to the URL: http://explore4sq.azurewebsites.net/api/places/taiseerj note that “taiseerj” will be replaced by the username in our app.
Step 2: Adding service to communicate with our RESTful service:
Now we need to add new Angular service which is responsible to communicate with our RESTful service, so let’s add new file named “placesDataService.js” under “app–>services”, Now open the file and paste the code below :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
'use strict'; app.factory('placesDataService', function ($http, toaster) { var serviceBase = '/api/places/'; var placesDataFactory = {}; var userInContext = null; var _getUserInCtx = function () { return userInContext; }; var _setUserInCtx = function (userInCtx) { userInContext = userInCtx; }; var _savePlace = function (venue) { //process venue to take needed properties var miniVenue = { userName: userInContext, venueID: venue.id, venueName: venue.name, address: venue.location.address, category: venue.categories[0].shortName, rating: venue.rating }; return $http.post(serviceBase, miniVenue).then( function (results) { toaster.pop('success', "Bookmarked Successfully", "Place saved to your bookmark!"); }, function (results) { if (results.status == 304) { toaster.pop('note', "Already Bookmarked", "Already bookmarked for user: " + miniVenue.userName); } else { toaster.pop('error', "Faield to Bookmark", "Something went wrong while saving :-("); } return results; }); }; var _getUserPlaces = function (userName, pageIndex, pageSize) { return $http.get(serviceBase + userName, { params: { page: pageIndex, pageSize: pageSize } }).then(function (results) { return results; }); }; var _userExists = function (userName) { return $http.get("/api/users/" + userName).then(function (results) { return results.data; }); }; placesDataFactory.getUserInContext = _getUserInCtx; placesDataFactory.setUserInContext = _setUserInCtx; placesDataFactory.getUserPlaces = _getUserPlaces; placesDataFactory.userExists = _userExists; placesDataFactory.savePlace = _savePlace; return placesDataFactory }); |
So basically what we’ve implemented in this factory is the below:
- We’ve used the built-in Angular service $http instead of $resource, we can use the $resource as well, but I prefer to use $http to show you how we can implement both, there is no dependencies once you inject $http, so we can inject on any service directly.
- We’ve injected toaster third party service which is JavaScript non blocking notification service, this service will be used to display notification for end user once she bookmark a place. You can check how to configure this service here.
- As we mentioned before, Angular services are singleton objects, they live in the entire application, so we’ve defined variable named userInContext in this service, we’ll depend on this variable to decide if there is a user to bookmark places or not, Note: this is very naive implementation and should not be used in real world application, I’ve used it here to keep things simple in the demo. If you hit F5 and refreshed the shell page, this variable will be flushed.
- We’ve added function named savePlace which is responsible to issue HTTP Post request to our RESTful API, the request payload body will contain JSON object matches the data model we defined in our API.
- We’re using .then promise to manipulate and access the returned response directly in the service, so if the response is 200 OK we are displaying success notification message, if the response is not 200 OK, for example 304 Not modified, then we are returning a notification that place already bookmarked for this user. Note: displaying a notification on the service level is not the right place to do, we should do this in controller, but I preferred to put it here because we’re going to call the save function from two different controllers and we want to display the same notification on both controllers, this falls more into sharing code.
- We’ve implemented a function which retrieves a list of bookmarked places for specific user as well another function to check if the username has been used before.
Step 3: Implementing bookmark places feature
Now we’ll get back to the controller “placesExplorerController” and inject the newly created service “placesDataService” then add new function named “bookmarkPlace()” as the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$scope.bookmarkPlace = function (venue) { if (!placesDataService.getUserInContext()) { var modalInstance = $modal.open({ templateUrl: 'app/views/userprofile.html', controller: 'userContextController', resolve: { venue: function () { return venue; } } }); } else { placesDataService.savePlace(venue).then( function (results) { // Do nothing as toaster showing from service }, function (results) { // Do nothing as toaster showing from service }); } }; |
This function checks if the variable named “userInContext” in service “placesDataService” is not null, if this is the case, then we save the place directly by calling placesDataService.savePlace(venue), if it is null, then we’ll display new modal named “userProfile.html” for the user asking her to provide a username, this modal has new controller named “userContextController”. Showing the modal is identical to what we did with the modal used to display places photos, you can check the view source html here, and the controller source code here.
Step 4: Implementing My Places Feature
This is last feature in our application, where the user can view all bookmarked places, to implement it we need to add a new controller named “myPlacesController”, so add new JS file “myPlacesController.js” under “app–>controllers”, open the file and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
'use strict'; app.controller('myPlacesController', function ($scope, placesDataService) { $scope.myPlaces = []; //paging $scope.totalRecordsCount = 0; $scope.pageSize = 10; $scope.currentPage = 1; init(); function init() { getUserPlaces(); } function getUserPlaces() { var userInCtx = placesDataService.getUserInContext(); if (userInCtx) { placesDataService.getUserPlaces(userInCtx, $scope.currentPage - 1, $scope.pageSize).then(function (results) { $scope.myPlaces = results.data; var paginationHeader = angular.fromJson(results.headers("x-pagination")); $scope.totalRecordsCount = paginationHeader.TotalCount; }, function (error) { alert(error.message); }); } } $scope.pageChanged = function (page) { $scope.currentPage = page; getUserPlaces(); }; }); |
What we’ve implemented here is simple, we’ve just injected the “placesDataService” so we’ll be able to load user saved places if there is username in the variable “userInContext” by calling method “getUserPlaces”, then we fill the returned data into myPlaces model.
Now we need to add new view named “myplaces.html” to work with this controller, the source code for this view can be found here, Do not forget to configure $routeProvider so Angular will be aware that the view “myplaces.html” is mapped to the controller “myPlacesController” when the user request the URL “/places”, this code should be added in app.js file as the code below:
1 2 3 4 5 6 7 8 |
app.config(function ($routeProvider) { $routeProvider.when("/places", { controller: "myPlacesController", templateUrl: "/app/views/myplaces.html" }); }); |
That’s all for now!
I hope you liked this tutorial, I tried my best to explain all important parts of this application during this tutorial, you can always get back to the complete running source code on github, or try the application using the live demo.
Please feel free to drop any comment or suggestion to enhance this application.
woo…. amazing AngularJS, anyway thanks for the tutorial i really learnt alot from your tutorial that cleared my knowledge in line with AngularJS thanx ….
You welcome Johny, glad you liked the tutorial. Good luck in learning AngularJS.
Hi i have a problem when run my project it always show
“Webpage Error”
Error: ‘app’ is undefined,
But its already defined as a global variable in the app.js script please HELP OUT!!
Hi Josh, did you reference your app.js file in index.html page, as well the order of JS files reference is important, this needs to be on top before any controller or service? Please fork my rgithub repo and compare the order of JS files reference.
Please, make an example using eTags.
I will soon, most probably next week.
Awesome tuts, waiting for eTags me too. Thanks.
Thank you, working on the API cashing post.
Hello Rokoczi,
I’ve posted part 11 of web api tutorial covering caching and using eTags.
Awesome! Comprehensive and clear! I must say thanks to you tjoudeh.
This is the best tutorial I have seen so far after i have digged for 2 weeks to find learning materials. (yes, even better than official one, no kidding 🙂 )
appreciate it, glad that was very useful for your learning purposes 🙂 keep tuned as I’m planing to build another tutorial using angular, phone gap, windows azure mobile services, kind of end to end application.
Please do that! This would be amazing! Specially the phone gap (or cordova) integration =-D. This tutorial was excellent, I’ve learned a lot by using it. Thanks a ton!
You are welcome, I will do my best to implement it 🙂 thanks for your comment.
I like that your service also holds the code for handling the request. I think it belongs here and not in the controller. But you allow the service to respond with toaster popups. it seems wrong to let the service control any viewable feedback.
It should be left to the controller to act on the data, right?
Best
Jesper
Hello Jesper,
Thanks for your feedback, you are absolutely right, as well I’ve mentioned that it is not the right implementation to control UI behavior in the service as quoted here:”displaying a notification on the service level is not the right place to do, we should do this in controller, but I preferred to put it here because we’re going to call the save function from two different controllers and we want to display the same notification on both controllers, this falls more into sharing code.”
Great series of posts btw.
Thank you Jesper, glad you like them 🙂
It’s awesome to visit this site and reading the views of all friends concerning this post, while I
am also zealous of getting experience.
nice.
how you implement authorization and access level?
Hello Rodrigo,
I’ll send you soon a sample project I worked on which covers authentication, what is the back-end you are using?
Hi tjoudeh
I’m use Asp.net MVC4
Send in my email please
Thanks
Check your inbox now.
I’m also interested about authentication part, I’m using Web API 2. Can you send me? Thanks.
Hello Migu, I’ll soon
Thank you so much for your helpful articles. It’s helping me so much in learning AngularJS from a novice user. Thanks again and keep up your good works.
You are welcome TramTi, glad you like it!
Hello Taiseer Joudeh,
Thank you so much.Its really awesome tutorial,I learn a lot from this and waiting for your next article.
Hello Ammy,
Glad you liked this tutorial, currently I’m working on nice another tutorial using AngularJS for building Hybrid Mobile apps, so keep tuned!
I am using ASP.Net MVC4, could you please send me authentication code.
I like the way you deliver the toturial step by step and making necessary points what to look out for and reference injection. Great post to learn Angular. The best one on spa with angular andwebapi till now !
Thank you lokeshsp for your nice comments, glad it was helpful to learn from 🙂 subscribe to my blog to keep tuned as I’m working on another tutorial covering Hybrid mobile app solution.
This is amazing!!!,i feel like an angularjs guru
Glad you like it AngularJS Guru 🙂
Thanks Taiseer for a comprehensive, cohesive and informative step by step AngularJS tutorial.
Most welcome Aram, glad you liked it 🙂
Wow excellent, I learnt a lot from yours tuts, You are my lifesaver. Thanks a lot.
You are most welcome, glad that posts were useful
Great post. Thank you.
You are welcome, happy to help.
how it’s adding…?
i didn’t fount….
value=”{{!ctrl.user.id ? ‘Add’ : ‘Update’}}”
Nice series of post but I’ve a question that in a single page application what is the best approach of having controllers. Like we should have one controller for the whole app or should we have multiple small controllers??