Hello AngularJS – Building a Tweet Reader

Single Page applications using client side JavaScript frameworks are getting popular and with the latest Web Update for Visual Studio 2012, quite a few SPA templates have been released by Microsoft and prominent Community members like John Papa. We have covered these templates in the past over here.

However these SPA templates are rather opinionated on the usage of the underlying frameworks and may not be the best starting point if we are trying to understand the framework under the hood. So today, we will look at AngularJS in a plain vanilla ASP.NET MVC app. We’ll start with an empty project and go ground up.

What is Angular JS?

Angular JS is (another) client side MVC framework in JavaScript. It was created by a group of developers at Google when they realized that their project (Feedback) had become too unwieldy and needed a cleanup. The three weeks effort at that cleanup lay seed to what we now know as AngularJS. Of course AngularJS was thrown open to community and since then had garnered a pretty vibrant community and a boat load of features.

Other things that make AngularJS popular is its focus on testability by having principles of Dependency Injection and Inversion of control built into the framework.

Okay, enough talk when do we see code?

Well today we’ll take a small step towards learning AngularJS by learning how to do basic data binding and retrieving data via AJAX. We’ll do it the way we have done it around here – Using a code walkthrough.

Demo – The Premise

I could have done the code walkthrough without any server side component and some hardcoded client side data, but that’s too far away from the real world. So we’ll get some data from the ethereal stream of data that is Twitter and display it in our Application demonstrating the concept of model binding and directives in AngularJS.

To connect to Twitter, we’ll use the excellent Linq2Twitter library on the server side. Once we have logged in we’ll retrieve and display data using AngularJS.

So first part of this article is setting up the infrastructure or plumbing to get LinqToTwitter hooked up. As with all Twitter apps, we have to get a CustomerKey and a CustomerSecret from developers.twitter.com.

Update: The second part of this article can be read at Using AngularJS Modules and Services Feature in an ASP.NET MVC application .

The third part of the article can be read at AngularJS – Post data using the $resource Service in an ASP.NET MVC app .

The Fourth part at Using Custom Directive in AngularJS to create reusable JavaScript components for your ASP.NET MVC app and

The fifth one at Angular JS: Routing to a SPA mode using ASP.NET MVC

Getting Started with ASP.NET MVC & AngularJS Plumbing

1. We create a MVC 4 project with the Empty template as we want to get started with the bare bones today.

2. We add the LinqToTwitter from Nuget

PM> install-package linqtotwitter

3. We install Twitter Bootstrap CSS and JavaScript files using the following command

PM> install-package Twitter.Bootstrap

4. We add an Empty HomeController in the Controllers folder. The Index action method calls the Authorization function and if the current user is not Authorized, it redirects to Twitter’s Authorization page. Once Authorized, it navigates back to Index page.

Very briefly, the ConsumerKey and the ConsumerSecret that we got while creating Twitter app is in the AppSettings. These two along with the OAuth token returned by Twitter, complete the Authorization part.

Note: We have not taken any measure to hide our Auth token once it returns from the Server. IT should not be this unprotected in a production application.

public class HomeController : Controller
{
private IOAuthCredentials credentials = new SessionStateCredentials();
private MvcAuthorizer auth;
private TwitterContext twitterCtx;     


public ActionResult Index()
{
  var unAuthorized = Authorize();
  if (unAuthorized == null)
  {
   return View("Index");
  }
  else
  {
   return unAuthorized;
  }
}
     
private ActionResult Authorize()
{
  if (credentials.ConsumerKey == null || credentials.ConsumerSecret == null)
  {
   credentials.ConsumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"];
   credentials.ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"];
  }
  auth = new MvcAuthorizer
  {
   Credentials = credentials
  };
  auth.CompleteAuthorization(Request.Url);
  if (!auth.IsAuthorized)
  {
   Uri specialUri = new Uri(Request.Url.ToString());
   return auth.BeginAuthorization(specialUri);
  }
  ViewBag.User = auth.Credentials.ScreenName;
  return null;
}


5. Finally we add a method that returns a JsonResult containing list of Latest Tweets from the logged in user

[HttpGet]
public JsonResult GetTweets()
{
    Authorize();
    string screenName = ViewBag.User;
    IEnumerable<TweetViewModel> friendTweets = new List<TweetViewModel>();
    if (string.IsNullOrEmpty(screenName))
    {
        return Json(friendTweets, JsonRequestBehavior.AllowGet);
    }
    twitterCtx = new TwitterContext(auth);
    friendTweets =
        (from tweet in twitterCtx.Status
         where tweet.Type == StatusType.Home &&
               tweet.ScreenName == screenName &&
               tweet.IncludeEntities == true
         select new TweetViewModel
         {
             ImageUrl = tweet.User.ProfileImageUrl,
             ScreenName = tweet.User.Identifier.ScreenName,
             MediaUrl = GetTweetMediaUrl(tweet),
             Tweet = tweet.Text
         })
        .ToList();
    return Json(friendTweets, JsonRequestBehavior.AllowGet);
}     


private string GetTweetMediaUrl(Status status)
{
    if (status.Entities != null && status.Entities.MediaEntities.Count > 0)
    {
        return status.Entities.MediaEntities[0].MediaUrlHttps;
    }
    return "";
}


6. The ViewModel used to encapsulate a Tweet is as follows

public class TweetViewModel
{
public string ImageUrl { get; set; }
public string ScreenName { get; set; }
public string MediaUrl { get; set; }
public string Tweet { get; set; }
}

7. Next we setup the View skeleton using BootStrap:

a. Add _ViewStart.cshtml in the Views folder. It’s content is as follows

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}


b. Add Views\Shared\_Layout.cshtml folder that will serve as our Master Page

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Hello AngularJS</title>
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="navbar navbar-inverse">
  <div class="navbar-inner">
   <div class="brand">
    @ViewBag.Title
   </div>
   @if (ViewBag.User != null)
   {
    <div class="button-group pull-right">
     <div class="btn">
      <i class="icon-user"></i>
       @ViewBag.User
     </div>
    </div>
   }
   </div>
</div>
<div class="container-fluid">
<div class="row-fluid">
  @RenderBody()
</div>
</div>
<script src="~/Scripts/jquery-1.9.1.min.js"></script>
<script src="~/Scripts/bootstrap.min.js"></script>
</body>
</html>


c. Finally we add Index.cshtml that for now has only the Title

@{
    ViewBag.Title = "Hello AngularJS";
}


d. If we run the application at this point, it will ask user to Authorize our app with Twitter, and once authorized, we’ll be redirected to the home page as follows

authorize-twitter-app

That completes our ground work and we are ready to dive into AngularJS now.

Introducing AngularJS

Thanks to the community, AngularJS is easy to get started with, using Nuget.

PM> install-package angularjs

Once installed we add a reference to it in _Layout.cshtml

<script src="~/Scripts/angular.js"></script>

The Angular App

First point of difference from libraries like KO is that Angular needs an ng-app attribute to declared wherever we want the Scope of Angular to start. We will add it in our Index.cshtml by adding a <div> as follows.

<div ng-app>
<!-- More Goodness Coming -->
</div>


The AngularJS Client Side Controller and ViewModel

We will add a hello-angular.js in the Scripts folder. We start off with a Controller that is defined as follows:

var indexController = function ($scope)
{

}

In Angular the indexController object is regarded as Controller simply if someone pulls in the $scope parameters.

What is $scope?

$scope is the client side version of ViewBag as we know from MVC as in it is used for sharing data between the Angular Controller and the View.

How to we use $scope?

1. As we mentioned above, we use Scope as a ViewBag, so to start off with we’ll add a couple of Json objects in it.

var indexController = function ($scope)
{
$scope.tweets = [
  { screenName: "Sumit", tweetText: "Test 1" },
  { screenName: "Sumit", tweetText: "Test 2" }];
}


Using Scope and Data Binding in AngularJS

1. Next we go back to Index.cshtml and add the following markup.

<!-- More Goodness Coming -->
<div ng-controller="indexController">
<ul>
  <li ng-repeat="item in tweets">
   {{item.screenName}}, {{item.tweetText}}
  </li>
</ul>
</div>

Angular Directives

There is a lot of stuff going on here.

a. In the outermost div, we have specified the directive ng-controller=”indexController”. This specifies what is the scope of the $scope object.

b. Note the <ul> doesn’t have anything special, so data binding to collections is by default container less.

c. The <li> element finally defines the ng-repeat directive and sets it to tweets (as defined in $scope earlier). Note the syntax is like C#’s foreach.

d. Finally we have the {{ … }} handlebar syntax to define the placeholders.

Those who are familiar with KO’s data binding will note how Angular doesn’t use data- attributes rather it uses AngularJS Specific attributes that it modifies at runtime. If we run this app now, we’ll see the following:

dom-manipulation

We can see how the DOM has been manipulated at runtime to get the output.

Just to recap, the $scope that was passed into the controller was actually instantiated and passed by Angular, those familiar with constructor injection will realize that Angular actually injected the $scope without us having to worry about it. This is the uniqueness and strength of Angular.

The Pluralizer

Directives in AngularJS are special keywords that have built in functionality. They can apply to elements attributes, classes or comments. Let’s put in a directive to see the total number of tweets.
In the view, we add the following markup inside the scope of the ng-controller

<h3>
<ng-pluralize count="tweets.length" when="newTweets"></ng-pluralize>
</h3>


The ng-pluralize directive can be read as ‘check for the count in tweets.length and based on it, evaluate the switch case in the newTweets function.’ Actually newTweets is an object in our indexController that we define as follows:

$scope.newTweets = {
0: "No new Tweets",
other: "{} new Tweets"
}


So now our ng-pluralize delegate reads like – “check of the count in tweets.length and if there are no tweets put the text “No new Tweets” else put the text ‘n new Tweets’ where n is the count”.

If we run the application as is, we’ll get the following

angularjs-twitter-app

Nice. Now let’s move on and see how we can get data from the Server.

Promises and Fetching Data from Server

Angular makes it really easy to get data from the server using AJAX. We’ll update our client controller (hello-angular.js) as follows

var indexController = function ($scope, $http)
{
    var resultPromise = $http.get("/Home/GetTweets");
    resultPromise.success(function (data)
    {
        $scope.tweets = data;
    });

    $scope.newTweets = {
        0: "No new Tweets",
        other: "{} new Tweets"
    }
}

Notice the additional parameter $http, getting injected into the controller. This is a wrapper for http functions as we can guess. We will use this to retrieve the Promise return by the $http.get(…) call. A promise is like a delegate or callback used when doing Async AJAX calls. Note we are calling the GetTweets method on our server controller. Once the Promise (delegate) completes successfully, we get the actual JSON data from the server.

Let’s update the Index.cshtml to create a nice list of the Tweets

<!-- More Goodness Coming -->
<div ng-controller="indexController">
<h3>
  <ng-pluralize count="tweets.length" when="newTweets"></ng-pluralize>
</h3>
<table class="table table-striped">
  <tr ng-repeat="item in tweets">
   <td>
    <img src="{{item.ImageUrl}}" />
   </td>
   <td>
    <strong>{{item.ScreenName}}</strong> <br />
    {{item.Tweet}}
   </td>
  </tr>
</table>
</div>

There are a couple of new things to note

a. We have moved the ng-repeat from the <li> to the <tr> tag.

b. We have added an <img> element and notice how the src is bound inplace to the ImageUrl property in our ViewMode.

Well with that we are all set. Run the Application and a view like the following comes up!

angularjs-tweets-mvc

Pretty neat eh!

With that we conclude today’s post. In future we will pick apart more features of AngularJS and keep getting deeper.

Conclusion

Angular provides a very nice Framework for data-binding, Templating and implementing observable behavior. However we have barely scratched the surface.

For devs familiar with data-binding in any form (no pun intended), be it WebForms, WPF, Silverlight etc., Angular’s binding philosophies match closely except for the fact that Angular’s magic happens on the browser.

Download the entire source code of this article (Github)




7 comments:

Alex said...

Thanks for sharing, very helpful!

Unknown said...

So, why are you using the server side controller to do a webservice call, when the client is more than capable of doing this?

Anonymous said...

Hi Dave,

If you are implying why I am calling Twitter from the server-side instead of client side, well I am treating Twitter as my data source, it could well have been a database call from the server, instead of calling Twitter.

Hope this clarifies.
-Sumit.

Agileload said...

great app example using angular.js. I also use bootstrap. Def. will check your others posts.

Anonymous said...

xzczxcxzcxzcxzc

Atul Verma said...

what does twitterConsumerKey and twitterConsumerSecret reference..as it is giving me unauthorized even after placing my twitter id and pwd respectively...

Unknown said...

I'm using twitterConsumerKey and twitterConsumerSecret created on my Twitter App but it throws Unauthorized...

is it something more to configure on Twitter App??