Socket.IO के साथ AngularJS ऐप्लिकेशन लिखना

परिचय

AngularJS एक बेहतरीन JavaScript फ़्रेमवर्क है. यह आपको दो-तरफ़ा डेटा बाइंडिंग देता है, जो इस्तेमाल में आसान और तेज़ है. साथ ही, यह एक बेहतरीन डायरेक्टिव सिस्टम है, जिसकी मदद से दोबारा इस्तेमाल किए जा सकने वाले कस्टम कॉम्पोनेंट बनाए जा सकते हैं. इसके अलावा, इसमें और भी बहुत कुछ है. Socket.IO, वेबसोकेट के लिए क्रॉस-ब्राउज़र रैपर और पॉलीफ़िल है. इससे रीयल-टाइम ऐप्लिकेशन डेवलप करना आसान हो जाता है. असल में, ये दोनों एक साथ काफ़ी अच्छा काम करते हैं!

मैंने पहले Express के साथ AngularJS ऐप्लिकेशन लिखने के बारे में लिखा था. हालांकि, इस बार मैं AngularJS ऐप्लिकेशन में रीयल-टाइम सुविधाएं जोड़ने के लिए, Socket.IO को इंटिग्रेट करने के तरीके के बारे में लिखूंगा. इस ट्यूटोरियल में, मैं इंस्टैंट मैसेजिंग ऐप्लिकेशन बनाने के बारे में बताऊंगा. यह ट्यूटोरियल, मेरे पिछले ट्यूटोरियल पर आधारित है. इसमें, सर्वर पर एक जैसे node.js स्टैक का इस्तेमाल किया गया है. इसलिए, अगर आपको Node.js या Express के बारे में जानकारी नहीं है, तो हमारा सुझाव है कि आप पहले उस ट्यूटोरियल को देखें.

डेमो खोलना

हमेशा की तरह, GitHub पर पूरा प्रॉडक्ट पाया जा सकता है.

ज़रूरी शर्तें

Socket.IO को सेट अप करने और Express के साथ इंटिग्रेट करने के लिए, कुछ बोइलरप्लेट की ज़रूरत होती है. इसलिए, मैंने Angular Socket.IO सीड बनाया है.

शुरू करने के लिए, GitHub से angular-node-seed repo को क्लोन करें:

git clone git://github.com/btford/angular-socket-io-seed my-project

या उसे zip फ़ाइल के तौर पर डाउनलोड करें.

सीड मिलने के बाद, आपको npm की मदद से कुछ डिपेंडेंसी डाउनलोड करनी होंगी. सीड वाली डायरेक्ट्री में टर्मिनल खोलें और यह चलाएं:

npm install

इन डिपेंडेंसी को इंस्टॉल करने के बाद, स्केलेटन ऐप्लिकेशन को चलाया जा सकता है:

node app.js

और इसे अपने ब्राउज़र में http://localhost:3000 पर देखें, ताकि यह पक्का किया जा सके कि सीड उम्मीद के मुताबिक काम कर रहा है.

ऐप्लिकेशन की सुविधाएं तय करना

चैट ऐप्लिकेशन बनाने के कई तरीके हैं. इसलिए, आइए हम उन कम से कम सुविधाओं के बारे में बताएं जो हमारे ऐप्लिकेशन में होंगी. सिर्फ़ एक चैट रूम होगा, जिसमें सभी उपयोगकर्ता शामिल होंगे. उपयोगकर्ता अपना नाम चुन सकते हैं और उसे बदल सकते हैं. हालांकि, नाम यूनीक होने चाहिए. सर्वर इस यूनीकनेस को लागू करेगा और जब उपयोगकर्ता अपने नाम बदलेंगे, तब इसकी सूचना देगा. क्लाइंट को मैसेज की सूची और चैट रूम में मौजूद उपयोगकर्ताओं की सूची दिखानी चाहिए.

आसान फ़्रंट एंड

इस खास जानकारी की मदद से, हम Jade की मदद से एक आसान फ़्रंट एंड बना सकते हैं, जो ज़रूरी यूज़र इंटरफ़ेस (यूआई) एलिमेंट उपलब्ध कराता है. views/index.jade खोलें और block body में यह जोड़ें:

div(ng-controller='AppCtrl')
.col
  h3 Messages
  .overflowable
    p(ng-repeat='message in messages') : 

.col
  h3 Users
  .overflowable
    p(ng-repeat='user in users') 

.clr
  form(ng-submit='sendMessage()')
    | Message: 
    input(size='60', ng-model='message')
    input(type='submit', value='Send')

.clr
  h3 Change your name
  p Your current user name is 
  form(ng-submit='changeName()')
    input(ng-model='newName')
    input(type='submit', value='Change Name')

public/css/app.css खोलें और कॉलम और ओवरफ़्लो देने के लिए सीएसएस जोड़ें:

/* app css stylesheet */

.overflowable {
  height: 240px;
  overflow-y: auto;
  border: 1px solid #000;
}

.overflowable p {
  margin: 0;
}

/* poor man's grid system */
.col {
  float: left;
  width: 350px;
}

.clr {
  clear: both;
}

Socket.IO के साथ इंटरैक्ट करना

Socket.IO, window पर io वैरिएबल को एक्सपोज़ करता है. हालांकि, इसे AngularJS के Dependency Injection सिस्टम में शामिल करना बेहतर होता है. इसलिए, हम Socket.IO से मिले socket ऑब्जेक्ट को रैप करने के लिए, एक सेवा लिखकर शुरू करेंगे. यह बहुत बढ़िया है, क्योंकि इससे बाद में हमारे कंट्रोलर को टेस्ट करना काफ़ी आसान हो जाएगा. public/js/services.js खोलें और कॉन्टेंट को इनसे बदलें:

app.factory('socket', function ($rootScope) {
  var socket = io.connect();
  return {
    on: function (eventName, callback) {
      socket.on(eventName, function () {  
        var args = arguments;
        $rootScope.$apply(function () {
          callback.apply(socket, args);
        });
      });
    },
    emit: function (eventName, data, callback) {
      socket.emit(eventName, data, function () {
        var args = arguments;
        $rootScope.$apply(function () {
          if (callback) {
            callback.apply(socket, args);
          }
        });
      })
    }
  };
});

ध्यान दें कि हम हर सॉकेट कॉलबैक को $scope.$apply में रैप करते हैं. इससे AngularJS को पता चलता है कि उसे ऐप्लिकेशन की स्थिति की जांच करनी है और टेंप्लेट को अपडेट करना है. ऐसा तब करना होगा, जब उसे पास किया गया कॉलबैक चलाने के बाद कोई बदलाव हुआ हो. अंदरूनी तौर पर, $http उसी तरह काम करता है; कुछ XHR के वापस आने के बाद, यह $scope.$apply को कॉल करता है, ताकि AngularJS अपने व्यू को उसी हिसाब से अपडेट कर सके.

ध्यान दें कि यह सेवा, Socket.IO API को पूरी तरह रैप नहीं करती है. इसे रैप करना, पाठकों के लिए एक टास्क है ;P . हालांकि, इस ट्यूटोरियल में इस्तेमाल किए गए तरीके इसमें शामिल हैं. अगर आपको इस बारे में ज़्यादा जानकारी चाहिए, तो यह सेवा आपको सही दिशा दिखाएगी. मैं पूरा रैपर लिखने के बारे में फिर से बताऊंगा, लेकिन यह इस ट्यूटोरियल के दायरे से बाहर है.

अब अपने कंट्रोलर में, socket ऑब्जेक्ट के लिए वैसे ही अनुरोध किया जा सकता है जैसे $http के लिए किया जाता है:

function AppCtrl($scope, socket) {
  /* Controller logic */
}

कंट्रोलर में, मैसेज भेजने और पाने के लिए लॉजिक जोड़ें. js/public/controllers.js खोलें और कॉन्टेंट को इनके साथ बदलें:

function AppCtrl($scope, socket) {

  // Socket listeners
  // ================

  socket.on('init', function (data) {
    $scope.name = data.name;
    $scope.users = data.users;
  });

  socket.on('send:message', function (message) {
    $scope.messages.push(message);
  });

  socket.on('change:name', function (data) {
    changeName(data.oldName, data.newName);
  });

  socket.on('user:join', function (data) {
    $scope.messages.push({
      user: 'chatroom',
      text: 'User ' + data.name + ' has joined.'
    });
    $scope.users.push(data.name);
  });

  // add a message to the conversation when a user disconnects or leaves the room
  socket.on('user:left', function (data) {
    $scope.messages.push({
      user: 'chatroom',
      text: 'User ' + data.name + ' has left.'
    });
    var i, user;
    for (i = 0; i < $scope.users.length; i++) {
      user = $scope.users[i];
      if (user === data.name) {
        $scope.users.splice(i, 1);
        break;
      }
    }
  });

  // Private helpers
  // ===============

  var changeName = function (oldName, newName) {
    // rename user in list of users
    var i;
    for (i = 0; i < $scope.users.length; i++) {
      if ($scope.users[i] === oldName) {
        $scope.users[i] = newName;
      }
    }

    $scope.messages.push({
      user: 'chatroom',
      text: 'User ' + oldName + ' is now known as ' + newName + '.'
    });
  }

  // Methods published to the scope
  // ==============================

  $scope.changeName = function () {
    socket.emit('change:name', {
      name: $scope.newName
    }, function (result) {
      if (!result) {
        alert('There was an error changing your name');
      } else {

        changeName($scope.name, $scope.newName);

        $scope.name = $scope.newName;
        $scope.newName = '';
      }
    });
  };

  $scope.sendMessage = function () {
    socket.emit('send:message', {
      message: $scope.message
    });

    // add the message to our model locally
    $scope.messages.push({
      user: $scope.name,
      text: $scope.message
    });

    // clear message box
    $scope.message = '';
  };
}

इस ऐप्लिकेशन में सिर्फ़ एक व्यू होगा. इसलिए, हम public/js/app.js से रूटिंग हटा सकते हैं और इसे आसान बना सकते हैं:

// Declare app level module which depends on filters, and services
var app = angular.module('myApp', ['myApp.filters', 'myApp.directives']);

सर्वर लिखना

routes/socket.js खोलें. हमें सर्वर की स्थिति को बनाए रखने के लिए, एक ऑब्जेक्ट तय करना होगा, ताकि उपयोगकर्ता के नाम यूनीक हों.

// Keep track of which names are used so that there are no duplicates
var userNames = (function () {
  var names = {};

  var claim = function (name) {
    if (!name || userNames[name]) {
      return false;
    } else {
      userNames[name] = true;
      return true;
    }
  };

  // find the lowest unused "guest" name and claim it
  var getGuestName = function () {
    var name,
      nextUserId = 1;

    do {
      name = 'Guest ' + nextUserId;
      nextUserId += 1;
    } while (!claim(name));

    return name;
  };

  // serialize claimed names as an array
  var get = function () {
    var res = [];
    for (user in userNames) {
      res.push(user);
    }

    return res;
  };

  var free = function (name) {
    if (userNames[name]) {
      delete userNames[name];
    }
  };

  return {
    claim: claim,
    free: free,
    get: get,
    getGuestName: getGuestName
  };
}());

यह मुख्य रूप से नामों के एक सेट को तय करता है, लेकिन ऐसे एपीआई के साथ जो चैट सर्वर के डोमेन के लिए ज़्यादा काम के होते हैं. हमारे क्लाइंट के कॉल का जवाब देने के लिए, इसे सर्वर के सॉकेट से जोड़ें:

// export function for listening to the socket
module.exports = function (socket) {
  var name = userNames.getGuestName();

  // send the new user their name and a list of users
  socket.emit('init', {
    name: name,
    users: userNames.get()
  });

  // notify other clients that a new user has joined
  socket.broadcast.emit('user:join', {
    name: name
  });

  // broadcast a user's message to other users
  socket.on('send:message', function (data) {
    socket.broadcast.emit('send:message', {
      user: name,
      text: data.message
    });
  });

  // validate a user's name change, and broadcast it on success
  socket.on('change:name', function (data, fn) {
    if (userNames.claim(data.name)) {
      var oldName = name;
      userNames.free(oldName);

      name = data.name;

      socket.broadcast.emit('change:name', {
        oldName: oldName,
        newName: name
      });

      fn(true);
    } else {
      fn(false);
    }
  });

  // clean up when a user leaves, and broadcast it to other users
  socket.on('disconnect', function () {
    socket.broadcast.emit('user:left', {
      name: name
    });
    userNames.free(name);
  });
};

इसके बाद, आवेदन पूरा हो जाएगा. इसे आज़माने के लिए, node app.js चलाएं. Socket.IO की मदद से, ऐप्लिकेशन रीयल-टाइम में अपडेट होना चाहिए.

नतीजा

इस इंस्टैंट मैसेजिंग ऐप्लिकेशन में और भी बहुत कुछ जोड़ा जा सकता है. उदाहरण के लिए, खाली मैसेज सबमिट किए जा सकते हैं. क्लाइंट साइड पर इस समस्या को रोकने के लिए, ng-valid का इस्तेमाल किया जा सकता है. साथ ही, सर्वर पर भी इसकी जांच की जा सकती है. हो सकता है कि ऐप्लिकेशन में शामिल होने वाले नए उपयोगकर्ताओं के फ़ायदे के लिए, सर्वर मैसेज का हालिया इतिहास सेव रखे.

अन्य लाइब्रेरी का इस्तेमाल करने वाले AngularJS ऐप्लिकेशन लिखना आसान है. इसके लिए, आपको उन्हें किसी सेवा में रैप करने और Angular को यह सूचना देने का तरीका समझना होगा कि मॉडल बदल गया है. अगले लेख में, मैं विज़ुअलाइज़ेशन की लोकप्रिय लाइब्रेरी D3.js के साथ AngularJS का इस्तेमाल करने के बारे में बताऊंगा.

रेफ़रंस

Angular Socket.IO Seed इंस्टैंट मैसेजिंग ऐप्लिकेशन AngularJS Express Socket.IO`