اذهب إلى المحتوى

الخدمات (Services) في AngularJS


Mostafa Ata العايش

تعاملنا حتّى الآن مع نوعٍ واحد من مكوّنات JavaScript المعدّلة في Angular، وهو المتحكّم. المتحكّمات هي نوعٌ مخصّص من المكوّنات، وكذلك سنجد المرشحات والتوجيهات، التي سنغطّيها قريبًا. تدعم Angular مكوّنًا غير مخصّص، يُسمّى بالخدمات، تعريف الخدمات في Angular فضفاضٌ نوعًا ما، ولكنّ السّمة المميّزة لها هي عدم اقترانها مباشرةً بالقالب، مما يفترض قرابةً بينها وبين نمط طبقة الخدمة في الهيكليّة التّقليديّة للمشاريع.

angular-services.thumb.png.ec4b3c5e183f2

تتضمّن وحدة القلب ng في Angular عددًا من الخدمات المدمجة، ونذكر الخدمات location$ وlog$ وq$ وwindow$ على سبيل المثال، وسنستكشف الخدمة http$ في الفصل الأخير من هذه السلسلة، فصل HTTP.

في تطبيقٍ نموذجيٍّ ستحتاج إلى تعريف خدماتك الخاصّة لأيّ سلوكٍ مشترك بين مكوّنات JavaScript المخصّصة التي تنشئها، مثل المتحكّمات. يُمكن للخدمات أن تكون متابعةً للحالة (stateful) إن احتجت إلى ذلك، فمثلًا، إن أردت مكانًا لتخزين نقاط اللاعب في لعبةٍ ما، يُمكنك إنشاء خدمة score لتتمكّن من جلب وعرض النّقاط الحاليّة في عدّة أماكن ضمن تطبيقك. جميع الخدمات متفرّدة (singletons) أي أنه لا يوجد غير نسخةٍ واحدةٍ من خدمةٍ معيّنة طوال دورة حياة تطبيق Angular.

الخدمة في Angular يُمكن أن تكون كائنًا، تابعًا، أو حتّى قيمةً أوّليّة (كالأعداد مثلًا)، أنت من يحدّد ذلك. في الواقع، هذا الفصل لن يشرح إلا القليل من الأمثلة عن الخدمات، وذلك لأنّ الخدمات يمكن أن تحوي أيّ شيفرة من شيفرات JavaScript العاديّة. ما سيُغطّيه هذا الفصل هو كيفيّة تسجيل (register) الخدمات في Angular. نقوم بذلك عن طريق توابع معرّفة بواسطة الخدمة provide$، ومغلّفة بواسطة angular.Module. لنتمكّن من تسجيل الخدمة نحتاج إلى مرجعٍ (reference) إلى وحدةٍ ما. لنقُم بتعريف وحدةٍ جذريّة لتطبيقنا الآن.

angular.module('app', []);

كما تمّ الشّرح في فصل الوحدات، نقوم بتحميل وحدة الجذر في تطبيقنا عن طريق تمرير اسمها إلى التّوجيه ng-app ضمن مستند HTML.

<body ng-app="app">
  <!-- الأمثلة توضع هنا -->
</body>

جيّد، أصبحت وحدتنا جاهزة.

تُقدّم الخدمة provide$ خمس توابع مختلفةً للقيام بتسجيل الخدمة، يُمكننا تصنيف هذه التّوابع إلى صنفين، أوّلهما سهلٌ جدًّا والثّاني أصعبُ قليلًا.

إنشاء خدمات دون استخدام حقن التبعية

قد لا تحتاج إلى حقن أيّ تبعيّاتٍ إلى خدمتك. (قُمنا بتغطية حقن التّبعيّة في الفصل الماضي. وكمثالٍ على تبعيّةٍ محقونة، فالخدمة المدمجة http$ يُمكن لخدمتك أن تستخدمها لجلب بياناتٍ من النّهاية الخلفيّة (backend) البعيدة.) ولكن حتّى لو لم تكن تريد حقن تبعيّاتٍ في خدمتك، ستحتاج إلى وضعها ضمن وحدةٍ لتجعلها متاحةً للحقن ضمن مكوِّناتٍ أخرى. إنّ جعل الخدمة متاحةً للحقن هو ما يجعلها خدمةً في Angular، وإلّا ستكون مجرّد شيفرة JavaScript عاديّة.

هناك تابعان خدميّان يسمحان بتسجيل الخدمات من دون تبعيّات: التّابع value والتّابع constant. الفرق بينهما في Angular معقّدٌ قليلًا ويخُصّ المحترفين، فـالثّوابت تكون متاحًةً لتطبيق Angular أثناء الـbootstrapping، أما القِيمة فلا. لن نقوم بتغطية constant في هذه السلسلة.

التابع value

لنفترض أنّنا نريد كتابة شيفرةٍ للعبة حيث يحصل فيها اللاعب على نقاطٍ تبدأ من الصفر. يمكننا باستخدام التّابع value من الخدمة provide$ أن نقوم بتسجيل (register) قيمةٍ عدديّةٍ أوّليّة باسم 'score' وستكون متاحةً للمتحكّمات، الخدمات، ومكوّناتٍ أخرى.

angular.module('app')
  .value('score', 0);

يُمكننا حقن خدمتنا score عن طريق وضعها ضمن قائمة الوسطاء للمتحكّم، وقد شرحنا ذلك بالتّفصيل في فصل حقن التّبعيّة.

angular.module('app')
  .controller('ScoreController', function($scope, score) {
    $scope.score = score;
    $scope.increment = function() {
      $scope.score++;
    };
  });

يستخدم قالب هذا المثال نُسختين من المتحكّم ScoreController لإثبات الطّبيعيّة المتفرّدة (singleton) للخدمة score.

<p ng-controller="ScoreController">
  Score: {{score}}
</p>
<p ng-controller="ScoreController">
  <button ng-click="increment()">Increment</button>
</p>

أعتذر، لقد تعمّدت إفشال المثال السّابق، فعند النّقر على زرّ Increment لن يتمّ تغيير قيمة المتغيّر score المعروضة في النّسخة الأولى من المتحكّم. هذه ليست مشكلة مجالات، ولكنّها بسبب الطّبيعة الثّابتة (immutable) للخدمات التي تمثّل قيمًا أوّليّة. يُمكننا إصلاح المثال السّابق بتغيير الخدمة من قيمةٍ أوّليّة إلى كائنٍ يحوي العنصر points.

angular.module('app')
  .value('score', {points: 0});

سنستخدم نفس جسم المتحكّم تقريبًا، إلّا أنّنا سنغيّر ++scope.score$ إلى ++scope.score.points$.

angular.module('app')
  .controller('ScoreController', function($scope, score) {
    $scope.score = score;
    $scope.increment = function() {
      $scope.score.points++;
    };
  });

بطريقةٍ مماثلة، سنغيّر في القالب العبارة score إلى score.points.

<p ng-controller="ScoreController">
  Score: {{score.points}}
</p>
<p ng-controller="ScoreController">
  <button ng-click="increment()">Increment</button>
</p>

لقد استخدمنا للتّو خدمةً تشارك بياناتٍ متغيّرة (mutable) ضمن تطبيقنا، هذا رائع. إضافةً إلى القيمة الأوّليّة والكائنات، يمكن لخدمات Angular أن تمثّل توابعًا. لنفترض أنّنا بحاجةٍ إلى خدمةٍ ثابتةٍ (stateless) تُعيد عدًدا عشوائيًّا بين 1 و 10.

angular.module('app')
  .value('randomScore', function() {
     return Math.ceil(Math.random() * 10);
  });

إنّ حقن هذه الخدمة أمرٌ سهل، سنقوم ببساطةٍ بإضافة اسمها randomScore إلى قائمة الوسطاء في متحكّمنا.

angular.module('app')
  .controller('ScoreController', function($scope, score, randomScore) {
    $scope.score = score;
    $scope.increment = function() {
      $scope.score.points += randomScore();
    };
  });

لقد تعلّمنا الآن كيفيّة تعريف الخدمات وحقنها ضمن المتحكّم كتبعيّات. ولكن ماذا لو أردنا حقن تبعيّاتٍ إلى خدماتنا؟

إنشاء خدمات باستخدام حقن التبعية

لعبتنا تبدأ دومًا بنقاطٍ قيمتها صفر، هذا منطقيٌّ، ولكن لنفترض أننا نريد أن تكون القيمة البدائيّة عددًا عشوائيًّا. كيف يمكننا جلب مرجعٍ للخدمة randomScore عندما ننشئ الكائن score للمرّة الأولى؟ إنّ التّابع value الذي كُنّا نستخدمه بسيط جدًّا، فأيّ شيءٍ نقوم بتمريره إليه سيكون هو القيمة أو التّابع أو الكائن الكامل والنّهائي الذي ستقوم Angular بحقنه لاحقًا، وهذا يعني بأنّنا لن نحظى بأيّ فرصةٍ لحقن أيّ تبعيّاتٍ لاحقًا.

تُقدّم Angular عدّة حلول لهذه المشكلة، وسنبدأ بالحلّ الأوّل، التّابع service كائنيُّ التّوجّه.

التابع service

تدعم JavaScript أسلوب البرمجة كائنيّة التّوجّه، ولذلك يمكننا كتابة خدمة في Angular تقبل التّبعيّات عن طريق الحقن بواسطة الباني (constructor injection). كُلّ ما نحتاج إليه هو كتابة التّابع الباني لخدمتنا score، بدلّا من تهيئتها بقيمةٍ بدائيّة عن طريق مهيِّئ الكائن ({}) الذي استخدمناه قبل قليل.

يجب أن يكون أوّل حرفٍ من اسم الباني في JavaScript حرفًا كبيرًا، ولذلك سنُسمّي الباني بالاسم Score، وسنقوم بوضع الخدمة randomScore ضمن قائمة الوسطاء لهذا الباني.

function Score(randomScore) {
  this.points = randomScore();
}

التّابع service يحتاج إلى تمرير الباني الخاصّ بالخدمة بدلًا من تمرير الخدمة ذاتها، وعندما تقوم Angular باستدعاء الباني عن طريق العمليّة new ستقوم آليّة حقن التّابعيّة بإسناد الخدمة randomScore إلى وسيط الباني.

angular.module('app')
  .service('score', Score);

توصف طريقة إنشاء نُسخة الخدمة في Angular بأنّها كسولة، أي أنّ النّسخة ستُنشأ فقط عندما تُشكِّل تبعيّةً لأحد المكوّنات التي يتمّ إخراجها في القالب.

التابع factory

إن كانت خدمتك شيئًا آخر غير الكائن، أو إن أردت الحصول على مرونةٍ أكبر من طريقة إنشاء بانٍ للكائن، عندها يُمكنك استخدام التّابع factory بدلًا من service، حيث نقوم بتمرير تابع استدعاءٍ خلفيٍّ (callback) وسيتمّ حقنه لاحقًا بالتّبعيّات التي نكتب أسماءها في قائمة الوسطاء. يُمكنك كتابة ما تشاء داخل هذا التّابع، ولكن يجب عليك في النّهاية أن تعيد قيمةً، تابعًا أو كائنًا يُمثّل الخدمة. يُطلق على هذا النّوع من الإنشاء عن طريق الاستدعاء الخلفيّ في المرجع الرّسميّ اسمُ الوصفة(recipe).

angular.module('app')
  .factory('score', function(randomScore) {
     return  {points: randomScore()};
  });

المثال السّابق مكافئٌ للمثال الذي يستخدم تركيبة الباني مع التّابع service الذي رأيناه في الفقرة الماضية.

التابع decorator

إن كنت تريد تعديل خدمةٍ موجودةٍ سابقًا، يُمكنك استخدام التّابع decorator الّذي تُوفّره الخدمة provide$. هذا التّابع ليس مُغلّفًا باستخدام angular.Module كحال بقيّة التّوابع، لذا يجب أن يتمّ استدعاؤه مباشرةً من الخدمة provide$. يُمكنك الحصول على مرجعٍ للخدمة provide$ عن طريق تسجيل تابع استدعاءٍ خلفيٍّ (callback) باستخدام التّابع config.

angular.module('app')
  .config(function($provide) {
    $provide.decorator('score', function($delegate) {
      $delegate.points = 1000000;
      return $delegate;
    });
  });

يُمكن لتابع decorator أن يقوم بإعادة الخدمة الأصليّة التي تمّ تمريها ضمن الوسيط ذي علامات الاقتباس ' ' أو أن يقوم بإعادة نُسخةٍ (instance) من خدمةٍ جديدةٍ كُلّيًّا. ولكن عليك بالحذر من التّأثيرات الجانبيّة غير المرغوبة.

التغليف (Encapsulation)

في الفقرة السّابقة أسأنا استخدام التّابع decorator، ونتيجةً لذلك تمّ تقييد الوصول إلى العنصر points. لحسن الحظّ، يُقدّم التّغليف الخاصّ بحقن التّابع factory مجالًا مُغلقًا (closure)، ممّا يسمح لنا بإخفاء بعض المعلومات، أو بعبارةٍ أخرى، التّغليف (encapsulation).

سنقوم باستبدال العنصر points المرئيّ في المجال العام بالمتغيّر المحلّي points المحدود الرؤية ضمن التّابع الّذي يغلّفه، وهذا سيسمح لنا بحمايته من التّعديل خارج التّابع. والآن سنقوم بجعل كائن الخدمة يكشف عن تابعٍ للوصول إلى قيمة المتغيّر، getPoints، وعن تابعٍ يقيَّد التعديل فيه فقط، increment.

angular.module('app')
  .factory('score', function(randomScore) {
    var points = randomScore();
    return {
       increment: function() {
         return ++points;
       },
       getPoints: function() {
         return points;
       }
    };
  });

سنحتاج إلى تغيير بسيطٍ في المتحكّم كي نسمح له باستدعاء التّابع increment من الخدمة.

angular.module('app')
  .controller('ScoreController', function($scope, score) {
    $scope.score = score;
    $scope.increment = function() {
      $scope.score.increment();
    };
  });

وسنغيّر أيضًا القالب، ليكون مرتبطًا بتابع الوصول ()score.getPoints بدلًا من الوصول إلى العنصر points مباشرةً.

<p ng-controller="ScoreController">
  Score: {{score.getPoints()}}
</p>
<p ng-controller="ScoreController">
  <button ng-click="increment()">Increment</button>
</p>

بما أنّ التابع increment يقوم أيضًا بإعادة قيمة المتغيّر points بعد التّعديل، إذًا بإمكاننا الكشف (expose) عنها ضمن العرض (view) في عبارة. قد تُفاجئك النّتيجة، قُم باستبدال الاستدعاء في السّطر الثاني، وضع ()score.increment بدلًا من ()score.getPoints، ثُمّ قُم بنقر الزّر عدّة مرّات. هل يُمكنك معرفة سبب زيادة القيمة بسرعةٍ كبيرة؟ هذا صحيح: تقوم Angular غالبًا باستدعاء العناصر والتّوابع المرتبطة بالقالب عدّة مرّات قبل أن تنتهي دورة الإخراج. هذه معلومةٌ هامّة يجب معرفتها لنفهم التّأثير الجانبيّ لها كالمثال السّابق، وأيضًا لتحسين كفاءة التّطبيق.

خاتمة

وصلنا إلى ختام الفصول الثّلاثة التي تتحدّث عن دعم Angular للبرمجة باستخدام الوحدات. بدأنا مع فصل الوحدات، وتابعنا مع الفصل القصير عن حقن التّبعيّة، وختمنا هذه الثّلاثيّة بهذا الفصل عن الخدمات. قد تستغرب إفراد الخدمات بفصلٍ مستقلٍّ عندما ترى أنّها ليست سوى JavaScript المعتادة ليس إلّا، ولكنّنا لم نقم بتغطيةٍ كاملة لما يجب معرفته، فإضافةً إلى التّوابع constant وvalue و service وfactory و decorator لا يزال هناك تابعٌ منخفض المستوى هو التّابع provider الذي يقدّم تعلّقات دورة الحياة (lifecycle hooks) لإعداد خدماتك بطريقةٍ متقدّمة.

إن كنت تتساءل فيما إذا كنت تحتاج إلى مساعدة Angular في هذا المجال بالفعل، فلتبقِ في ذهنك بأنّك لست مضطرًّا لإدارة كلّ شيفراتك باستخدام Angular، ورغم ذلك فإنّ Angular 2.0 ستنتقل إلى نظام وحدات ES6 كما أشار العرض التّقديمي "RIP angular.module" الّذي قُدّم في خطاب فريق التّطوير في ng-europe 2014. وإلى أن يتمّ ذلك فإنّك تحتاج بالفعل إلى أن تضع شيفراتك داخل وحدات Angular عندما تحتاج إلى استخدام مكوّناتٍ مدمجة في Angular (أو مكوّناتٍ طوّرها طرفٌ ثالث) يتمُّ الوصول إليها عن طريق حقن التّبعيّة. لقد قمتُ بعرض بديلٍ لحقن التّبعيّة في نهاية الفصل الماضي. فقط أبقِ في ذهنك أنّ دعم اختبار الوحدة (unit testing support) في Angular قد عزّز من أهمّيّة استخدام حقن التّبعيّة، لذا عليك أن تهتمّ باستخدامك له أيضًا.

هناك أيضًا بعض المكوِّنات المخصّصة التي يجب عليك تسجيلها باستخدام نظام الوحدات في Angular، لتجعلها متوفّرة ضمن القالب. أحد هذه المكوّنات هو المُرشّحات، وسنُغطّيها في الفصل القادم.

ترجمة وبتصرّف للفصل الثامن من كتاب: Angular Basics لصاحبه: Chris Smith.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...