Primer on AngularJS Services

What’s a service …

Much to my surprise, the Angular documentation provides a great definition of a service:

Angular services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.

Angular services are:

  1. Lazily instantiated – Angular only instantiates a service when an application component depends on it.
  2. Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.

Angular offers several useful services (like $http), but for most applications you’ll also want to create your own.

Services are powerful in that they help keep your code DRY by encapsulating functionality. From an architecture standpoint alone, services help separate out concerns, ensuring that each object is responsible for a single piece of functionality. For example, it’s common for beginners to put all of their app’s functionality into the controller. This is fine for smaller apps, but just know that it’s not a good practice and your controller will balloon quickly as your app scales.

Get in the habit early on to separate concerns. If your controller is handling more than just defining the scope or initial state of your app, connecting your models and views, then it’s are probably doing too much.

We are all (err, I am) guilty of this. Let’s look at a very simple app …

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!doctype html>
<html lang="en" ng-app='myApp'>
<head>
  <meta charset="UTF-8">
  <title>Angular Boilerplate</title>
  <!-- styles -->
  <link href="http://netdna.bootstrapcdn.com/bootswatch/3.1.1/yeti/bootstrap.min.css" rel="stylesheet" media="screen">
  <link href="main.css" rel="stylesheet" media="screen">
</head>
  <body>
    <div class="container">
      <div ng-controller="myController">
        <h1>Enter Quantity:</h1>
        <input type="number" ng-model="quantity"></p>
        <h2>Total Cost: </h2>
      </div>
    </div>
    <!-- scripts -->
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" type="text/javascript"></script>
    <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
    <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
    <script src="main.js" type="text/javascript"></script>
  </body>
</html>

Javascript:

1
2
3
4
5
6
7
8
var app = angular.module('myApp', [])

app.controller('myController', function($scope) {
  $scope.quantity = 100;
  $scope.calculate = function(number) {
    return number * 10;
  }
});

You can grab the code from this repo or from JSFiddle.

So, this just takes an input value (integer or floating point) and multiplies it by 10 in the calculate() function, which then updates the DOM. Not only is the controller defining scope - but it also calculates the total. Despite this being a small app, too much is happening in the controller. We should separate out the calculate function() into a separate service.

Creating a custom service

By moving the business logic out of the controller, abstracting much of the code, our controller becomes leaner. It’s a good practice to write fat services and lean controllers.

To do this, we are will use a service type called a factory, which is the most common type.

This is a good time to stop and learn the major service types - constants, values, services, providers, and decorators. Check out this excellent article for more on the various service types and how and when to use them. All are slightly different, but, in general, all are dependency injected modules of functionality.

Within the same JS file add the following code beneath the controller:

1
2
3
4
5
6
7
8
// Service
app.factory('calculateService', function(){
  return {
    calculate: function(number){
      return number * 10
    }
 }
});

This code creates a service called calculateService. You may be wondering why we have to use the factory() method for this instead of just a regular function. It’s simple: That method registers the service with Angular; and with Angular aware of its existence, it can be dependency injected into the controller, giving us access to the defined functions - e.g, calculate() within the controller. We can now use this in multiple places within our application, allowing for easy code reuse.

So, we have simply abstracted the logic of taking the user inputted number and multiplying it by 10.

Now update the controller:

1
2
3
4
5
6
app.controller('myController', function($scope, calculateService) {
  $scope.quantity = 100;
   $scope.calculate = function(number) {
    return calculateService.calculate(number);
  }
});

And you’re app should be working. Test it out. JSFiddle

Conclusion

Hopefully, you now have a better sense as to -

  • What a service is,
  • Why you should use them, and
  • How to use them.

Want some practice? Create separate services for each piece of functionality in this app’s controller. Remember: The controller is responsible for defining scope, all else should be moved out of the controller altogether.

If you need help, start by creating a service that handles the actual API calls. Perhaps use a service name of getData then set up functions for the different HTTP requests - i.e., readData() for a GET request and writeData() for a POST. Then when you use dependency injection to add this service to your controller, you can simply use the following syntax for accessing the readData() function in the controller:

1
getData.readData(some_argument)

Presumably you would pass in an argument supplied by the user. Now you can access that function from the controller without knowing anything about the actual service except for how you use it. The controller is cleaner because you abstracted out all the messy code for making API calls.

Good luck!

Adding a Captcha to Sinatra to Minimize Spam

Spam is irritating.

It’s been especially irritating on a blog I created for a Sinatra tutorial hosted on Heroku where the database was filling up so quickly I had to run a script to delete all rows once a week. Ugh.

So, let’s add a captcha to our blog in just five simple steps that will take less than five minutes element in order to help prevent so much spam.

Steps:

1. Add the following gem to your Gemfile:

1
gem 'sinatra-captcha'

2. Update your gems and their dependencies:

1
$ bundle install

3. Update app.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...

require 'sinatra/captcha'

...

post "/posts" do
  halt(401, "invalid captcha") unless captcha_pass?
  @post = Post.new(params[:post])
  if @post.save
    redirect "posts/#{@post.id}", :notice => 'Congrats! Love the new post. (This message will disapear in 4 seconds.)'
  else
    redirect "posts/create", :error => 'Something went wrong. Try again. (This message will disapear in 4 seconds.)'
  end
end

...

4. Update the form in the create.erb view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<form action="/posts" method="post"role="form">
<div class="form-group">
  <label for="post_title">Title:</label>
  <br>
  <input id="post_title" class="form-control" name="post[title]" type="text" value="<%= @post.title %>" style="width=90%"/>
</div>
<div class="form-group">
  <label for="post_body">Body:</label>
  <br>
  <textarea id="post_body" name="post[body]" class="form-control" rows="10"><%= @post.body %></textarea>
  <br>
  <div><%= captcha_image_tag %></div>
  <br>
  <label>Captcha:</label>
  <%= captcha_answer_tag %>
</div>
<button type="submit" class="btn btn-success">Submit</button>
<br>
</form>

5. Preview locally before updating Heroku:

1
$ ruby app.rb

Navigate to http://localhost:4567/posts/create and you should see:

sinatra_blog_captcha

Conclusion

From now on to post a new post, visitors have to complete the word verification. Keep in mind that this won’t completely halt all spam - but it will greatly reduce it.

Links:

Cheers!

Handling AJAX Calls With Node.js and Express (Part 5)

Articles in the series:

Last time we refactored our code to make it more modular as well as added some styles. This time we’ll add our next feature: The ability to save jobs so that users can apply to them later.

User Workflow

From an end user’s perspective, after logging in and then searching for jobs, one can simply click a button next to each job to save the job to a new Mongo collection. That job is then removed from the list of jobs retrieved from the search. Let’s start with that.

What do we need to do?

  1. Add a “save” button next to each job.
  2. Develop the necessary code to “grab” the job when the button is clicked, sending it to the server side.
  3. Create a new collection in the database.
  4. Insert the data in the newly created Mongo collection.
  5. Use jQuery to remove the job from the DOM and alert the user that job has been added.

Let’s get started.

Add a save button

Start by adding the “save” button to the Handlebars template:

1
2
3
4
5
6
7
8
9
10
<script id="search-results" type="text/x-handlebars-template">
    
      <li>
        <button type="button" class="btn btn-primary btn-xs save-btn">Save</button>
        <a href=></a><br>>
      </li>
    
    <br>
    </ul>
</script>

Moving right along …

Client Side Javascript

Next, let’s add an event handler to main.js that captures the button when clicked:

1
2
3
$('.save-btn').on('click', function() {
  console.log("whee!")
});

Your file should now look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$(function(){
  var source = $("#search-results").html();
  var dataTemplate = Handlebars.compile(source);
  $results = $('#results')

  $('#search').on('keyup', function(e){
    if(e.keyCode === 13) {
      var parameters = { search: $(this).val() };
      $.get('/searching', parameters, function(data){
        if (data instanceof Array) {
          $results.html(dataTemplate({resultsArray:data}));
        } else {
          $results.html(data);
        };
      });
    };
  });
  $('.save-btn').on('click', function() {
    console.log("whee!")
  });
});

Do a quick sanity check. Fire up the server. Login. Search for some jobs. You should see the “save” button next to each job. Open up your Javascript console so you can see the console log when it fires. Now try to click a button.

Nothing. Right? What’s going on? We have the right selector. The event is a click. It should be working.

The problem is fairly simple: On the initial loading of the DOM, those selectors - .save-btn - are not present. In fact, they only become present after we append all the jobs to the DOM. Since the selectors are not present to begin with though, our event handler in its current state won’t find them. Fortunately, this is an easy fix.

We can simply attach a listener to a parent element, then once the event is fired, it will search for the child selector, .save-btn. It will obviously only find that selector once it exists in the DOM.

This is called event delegation. If interested, check this article out for more info.

Update the code:

1
2
3
$('#results').on('click', '.save-btn', function() {
  console.log("whee!")
});

So, the listener is set to the #results selector, which when fired (by the button click), searches the DOM for the child selector, .save-btn. Test it out. It should work.

delegated-events

Next, instead of just outputting the text “whee!”, we need to grab the job title and URL by replacing the current console log with:

1
2
3
var jobTitle = $(this).next('a').text()
var jobURL = $(this).next('a').attr('href')
console.log(jobTitle, jobURL)

Notice the this keyword? It’s extremely powerful yet it can be difficult to use. In this case, it refers to the DOM element that the event handler is triggered on.

Don’t believe me? Test it out: update the console.log() to console.log($(this)). Test it out.

To learn more about this, check out the jQuery docs and Javascript docs.

Now what happens when you click the save button?

this-keyword

Finally, we need to pass the data to the server.

1
2
3
4
5
var parameters = { title: jobTitle, url: jobURL };
console.log(parameters)
$.get( '/save', parameters, function(data) {
  console.log("whee!")
});

You should remember how to do this, and understand what’s happening here. If not, review Part 1 of this series.

Server Side Javascript

On the server side, we need to setup a /save route. Again, if you have questions on this, check out Part 1.

Update app.js:

1
app.get('/save', ensureAuthenticated, routes.save)

Now update the routes file, index.js:

1
2
3
4
5
exports.save = function(req, res){
  var title = req.query.title;
  var url = req.query.url;
  console.log(title, url);
};

Test this out. You should see:

backend

Update Mongo

Now that we have the data in our possession, let’s add it to the database.

Add a new schema

Create a new file in the “models” directory called job.js, then add the following code to the file:

1
2
3
4
5
6
7
8
9
10
11
12
var mongoose = require('mongoose');
var config = require('../config');

console.log(config);

// create a job model
var userSchema = new mongoose.Schema({
  title: String,
  url: String,
});

module.exports = mongoose.model('Job', jobSchema);

Insert Data

With the schema set up, we can now add our data to the Mongo collection. Within your routes, add the following code to the /save route:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
exports.save = function(req, res){
  var title = req.query.title;
  var url = req.query.url;
  console.log(title, url);
  var newJob = new job();
  newJob.title = title;
  newJob.url = url;
  console.log(newJob);
  newJob.save(function(err){
    if(err){
      throw err;
    }
    console.log("New job, " + newJob.title + ", was added to mongo");
  });
};

Here, we are simply creating a new record assigned to the variable newJob, then adding the appropriate data, and finally saving the job to our job collection within Mongo.

Make sure to require the config and Mongoose schema files:

1
2
var config = require('../config');
var job = require('../models/job');

Test it out!

save_job_to_mongo

Now check out the results in Mongo:

saved_job_mongo

Before moving on, let’s add a line of code to search the Mongo collection to see if a job exists, then within a conditional we can setup logic for only adding a job if it doesn’t already exist in the collection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
exports.save = function(req, res){
  var title = req.query.title;
  var url = req.query.url;
  console.log(title, url);
  var newJob = new job();
  job.findOne({'title': title}, function (err, job) {
    if (job) {
      console.log('Job already in database.');
    } else {
      newJob.title = title;
      newJob.url = url;
      console.log(newJob);
      newJob.save(function(err){
        if(err){
          throw err;
        }
        console.log("New job, " + newJob.title + ", was added to mongo");
      });
    };
  });
};

So, we search the database for the job - job.findOne({'title': title} - then if it’s found we output a message to the console - console.log('Job already in database.');. And if it’s not found, we obviously add the data to the database. We should alert the user if a job is already in the database in a more direct way than just a message to the console. After all, how many users browse the Internet with their console open? We’ll address that in a bit. Right now, let’s finish with Mongo first.

One to Many Relationship

We need set up a one to many relationship (one user, many jobs) using document references within Mongo to associate a job to a user. This takes literally two lines of code.

Update the jobs schema:

1
2
3
4
5
6
7
var user = require('../models/user');

var jobSchema = new mongoose.Schema({
  title: String,
  url: String,
  user: {type: mongoose.Schema.Types.ObjectId, ref: user}
});

Then updated index.js so that when you add a job it includes the currently logged in user:

1
2
3
4
5
6
7
8
newJob.title = title;
newJob.url = url;
newJob.user = req.user._id
console.log(newJob);
newJob.save(function(err){
  if(err){
    throw err;
  }

Test this out, then check out the object in Mongo:

1
{ "user" : ObjectId("534cb94fd4b72d7618000001"), "url" : "http://sfbay.craigslist.org/sfc/eng/4423216760.html", "title" : "Principal Web Engineer", "_id" : ObjectId("5351f3a1cc6813119e000001"), "__v" : 0 }

The object now includes the user id.

Client Side Javascript Redux

Okay. Back on the client side, we need to do three things before we’re finally done:

  1. Remove the job the user saved
  2. Display messages from the server side, indicating whether the job was added to the database or not
  3. Display all saved jobs to the user

Remove job from the DOM

Add the following line of code to main.js right before we send the data to the server side:

1
$(this).parent().remove()

Updated code:

1
2
3
4
5
6
7
8
9
10
$('#results').on('click', '.save-btn', function() {
  var jobTitle = $(this).next('a').text()
  var jobURL = $(this).next('a').attr('href')
  var parameters = { title: jobTitle, url: jobURL };
  console.log(parameters)
  $(this).parent().remove()
  $.get( '/save', parameters, function(data) {
    console.log('test',data)
  });
});

Display Messages

First, within index.js update the following two lines of code.

From:

1
2
console.log('Job already in database.');
console.log("New job, " + newJob.title + ", was added to mongo");

To:

1
2
res.send('Job already in database.');
res.send("New job, " + newJob.title + ", was added to mongo");

Updated function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
exports.save = function(req, res){
  var title = req.query.title;
  var url = req.query.url;
  console.log(title, url);
  var newJob = new job();
  job.findOne({'title': title}, function (err, job) {
    if (job) {
      res.send('Job already in database.');
    } else {
      newJob.title = title;
      newJob.url = url;
      console.log(newJob);
      newJob.save(function(err){
        if(err){
          throw err;
        }
        res.send("New job, " + newJob.title + ", was added to mongo");
      });
    };
  });
};

The res.send() method is used to send a response back to the client side. You can read more here. Now, we need to capture that reponse and append the actual message to the DOM.

First, add a new element, p#alert, to search.jade where you want the message to go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extends layout

block content
    h1 Search SF Jobs
    .lead Welcome, #{user}
    form(METHOD="LINK", ACTION="logout")
        input(type="submit", value="Logout", class='btn btn-sm btn-primary')
    br
    br
    p#alert
    input#search(type="search", placeholder="search...")
    br
    br
    ul#results
    include template.html

    script(src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js")
    script(src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0/handlebars.min.js")
    script(src="/javascripts/main.js")

Next update main.js:

1
2
3
4
$.get( '/save', parameters, function(data) {
  $('#alert').html(data)
  console.log(data)
});

$('#alert').html(data) adds the message to the DOM between the <p> tags that have the id “results”.

Check it out live.

Display saved jobs

This is actually a fairly large task, so we’ll tackle this in the next part, along with re-organizing the entire search page and adding some more styles.

You can grab the code here.

See you next time!