تعاملنا حتّى الآن مع نوعٍ واحد من مكوّنات JavaScript المعدّلة في Angular، وهو المتحكّم. المتحكّمات هي نوعٌ مخصّص من المكوّنات، وكذلك سنجد المرشحات والتوجيهات، التي سنغطّيها قريبًا. تدعم Angular مكوّنًا غير مخصّص، يُسمّى بالخدمات، تعريف الخدمات في Angular فضفاضٌ نوعًا ما، ولكنّ السّمة المميّزة لها هي عدم اقترانها مباشرةً بالقالب، مما يفترض قرابةً بينها وبين نمط طبقة الخدمة في الهيكليّة التّقليديّة للمشاريع.
تتضمّن وحدة القلب 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.