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

ब्रायन फ़ोर्ड
ब्रायन फ़ोर्ड

शुरुआती जानकारी

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

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

डेमो खोलें

हमेशा की तरह, GitHub पर तैयार प्रॉडक्ट को फ़ेच किया जा सकता है.

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

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

शुरू करने के लिए, GitHub से कोणीय-नोड-सीड रेपो का क्लोन बनाएं:

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

या इसे ज़िप के रूप में डाउनलोड करें.

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

npm install

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

node app.js

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

ऐप्लिकेशन की सुविधाओं के बारे में फ़ैसला लेना

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

अ सिंपल फ़्रंट एंड

इस स्पेसिफ़िकेशन के साथ, हम जेड के साथ एक आसान फ़्रंट एंड बना सकते हैं, जो ज़रूरी यूज़र इंटरफ़ेस (यूआई) एलिमेंट को उपलब्ध कराता है. 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 के डिपेंडेंसी इंजेक्शन सिस्टम में एनकैप्सुलेट करना बेहतर होता है. इसलिए, हम 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 का इस्तेमाल करने के बारे में सोच रहा हूं.

References

Angular Socket.IO सीड तैयार इंस्टैंट मैसेजिंग ऐप्लिकेशन AngularJS एक्सप्रेशन Socket.IO`