30يوليو

( تعلم Angular ) – المتحكمات (Controllers) في AngularJS

طلبت منك في الفصل السابق (المبادئ) أن تكبح جماح JavaScript بداخلك ريثما نستكشف القيمة الحقيقيةّ التي تقدّمها Angular، وهي تقديم امتدادات قويّة لـHTML لمطوّر النّهاية الأمامية front-end.

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

أكثر الطرق شيوعًا لتعديل العرض في Angular باستخدام JavaScript هي عن طريق استخدام متحّكم، وأبسط طريقة لكتابة متحكّم هي باستخدام تابع بناء (constructor) بسيط.

لنبدأ بمثالٍ بسيط لنفهم تمامًا ما الذي يحدث، المثال التّالي لا يقوم بأي شيء، ولا يطبع “!Hello World” حتّى.

ها هو متحكّمنا البسيط.

function EmptyController() {

};

سنحتاج إلى تضمين مكتبة Angular ضمن الصفحة لتتم معالجة خصائص المجال التي تم تحضيرها عن طريق المتحكّم، ولذا يجب نسخ الشّيفرة التّالية ولصقها داخل العنصر head في كلّ الأمثلة.

<html>
    <head>
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>
    </head>
    <body ng-app="app">
        <!-- كل الأمثلة توضع هنا -->
    </body>
</html>

يجب علينا ضبط إعدادات تطبيق Angular داخل مستند HTML ليقوم بتحميل الوحدة app، فقد تعرفّنا في الوحدة الأولى المبادئ على التّوجيه ng-app الذي نضيفه لأيّ عنصر في المستند لتقوم Angular بمعالجة ما بداخله، انظر الآن إلى استخدام ng-app في المثال التّالي، هل يمكنك الانتباه إلى الإضافة الجديدة؟

<body ng-app="app">

</body>

يُمكن تعريف المتحكّمات كتوابع بناء معرّفة في المجال العام (global scope)، وهذه هي الطّريقة التي سنستخدمها في أمثلتنا في هذا الفصل.

كان الشكل البسيط للمتحكّمات مدعومًا في Angular حتى النسخة 1.3 لتسهيل انطلاق المطوّرين مع المكتبة، ولكنّ ذلك يتطلب الآن إنشاء وتسمية وحدة للتطبيق.

سنناقش تفاصيل استخدام angular.module في الفصول القادمة الوحدات، حقن التابعة، والخدمات.

يمكنك التعامل مع المثال التّالي على أنّه شيفرة معياريّة مطلوبة:

angular.module('app', []);
angular.module('app').config(['$controllerProvider', function($controllerProvider) {
    $controllerProvider.allowGlobals();
}]);

والآن بعد كتابة هذه الشّيفرة الخارجة عن سياق الحديث، لنعد إلى متحكّمنا عديم الفائدة.

ng-controller

كالعادة، الطّريقة التي سنتّبعها للقيام بأيّ شيء، هي استخدام الموجّهات، سنستخدم الموجه ng-controller الذي يقوم بالعثور على التّابع الذي يتمّ تمرير اسمه إليه ثم استدعائه.

<p ng-controller="EmptyController">

</p>

وكما توقّعتَ، لن يقوم هذا التّابع بأيّ شيء، والآن إذًا ما هي هذه المتحكّمات وكيف نستخدمها؟

بناء النموذج

ينصّ المرجع الرسمي في النظرة العامة للمفهوم على أنّ وظيفة المتحكّم هي “الكشف عن المتغيرات والوظائفيّة للعبارات والتّوجيهات” وتشير كلمة “الوظائفية” إلى توابع الاستدعاء الخلفي (callback)، وسنتطرق إليها قريبًا، أمّا الآن فسنتكلّم عن تهيئة “المتغيّرات”، أو بعبارة أخرى تحضير النّموذج.

أرجو منك أن تبقي في ذهنك أنّ وحدة Angular ليست سوى JavaScript عاديّة يمكن الوصول إليها ضمن مجال عبارة ما، وبذلك سيكون تحضير النّموذج أمرًا سهلًا للغاية.

سنتمكّن من كتابة JavaScript عاديّة دون أيّ قيود، لنقم بإضافة عنصر إلى المتحكّم الفارغ، وليكن هذا العنصر سلسلة نصّيّة.

function MessageController() {
    this.message = "This is a model.";
}

أمرٌ بسيط، أليس كذلك؟ هل قمنا الآن بإنشاء وحدة؟ ماذا تتوقّع؟
الإجابة هي “تقريبًا”، فعندما يكون بإمكاننا الوصول إلى العنصر message ضمن مجال العرض، ستكون هذه وحدةً بالفعل.

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

Controller as propertyName

يشرح توثيق المكتبة بالنّسبة للتّوجيه ng-controller بأنه يمكنك تمرير عبارة Controller as propertyName كوسيط للتّوجيه ng-controller، وهذه الميزة متاحة في النسخة 1.2 فما فوق.

<p ng-controller="MessageController as controller">
    {{controller.message}}
</p>

الناتج

This is a model.

رائع، لقد قمنا للتّو بتحضير بيانات النّموذج باستخدام متحكّم.

صحيحٌ بأنّ هذه الطّريقة في ربط الخاصّية بالمرجع this مباشرة وليس فيها الكثير من التّعقيد، إلّا أنها تعطي انطباعًا خاطئًا بأنّ المتحكّم جزءٌ من النّموذج، وهذا غير صحيح، فالمتحكّم في الحقيقة يقوم بـتحضير النّموذج فقط.

سيكون اختبار الشّيفرة وتتبّع أخطائها أكثر سهولةً عندما يتم الحدّ من الوصول إلى البيانات بمقدار ما يحتاج إليه العمل لا أكثر، كما أنّ الطّريقة السّابقة تضيف بعض الضجّة إلى شيفرة العرض، فجميع العناصر سيتم الوصول إليها عبر مرجع (reference) للمتحكّم.

شيفرة العرض هي المكان الأكثر حساسيةً للضجيج في تطبيق ويب، فهي أوّل مكانٍ قد نبدأ فيه بفقد القدرة على فهم الهدف من الشّيفرة بلمحة سريعة.

أخيرًا، المتحكّمات تعمل يدًا بيد مع المجال، لذا ولأهدافٍ تعليميّة سيكون من الأفضل رؤية مرجع (reference) للمجال وامتلاك القدرة على التّحكم به، ومع متحكّم كهذا سيكون بإمكاننا الكشف عن العناصر التي نحتاجها في العرض فقط، بدل الكشف عن المتحكّم بما فيه، وسنتّبع في هذا الكتاب هذه الطّريقة بالكشف الصّريح عن العناصر المستخدمة في العرض داخل المتحكّم.

أوّل سؤالٍ يتبادر إلى الذّهن الآن هو: “كيف سنحصل على مرجعٍ للمجال؟”، هل سننشئ واحدًا عن طريق new أم سنطلب الحصول على واحدٍ بطريقة ما؟

حقن التابعية

سنحصل عليه عن طريق حقن التابعية، ربما تكون قد لاحظت أيضًا أنني لم أكتب أي شيفرة لإنشاء المتحكّم مثل:

var controller = new MessageController();

مثلًا، فمن الذي يقوم بإنشاء المتحكّم المستخدم في العرض إذًا؟

Angular هي من يقوم بذلك طبعًا، فـAngular هي حاوية لتبادل التحكم Inversion of Control تدير دورة حياة مكونات التّطبيق، فعندما نحتاج إلى متحكّم جديد أو إلى مكوّنٍ آخر، ستقوم Angular ببنائه تلقائيًّا، وهذا يوفر الجهد، ولكنّ الأمر الأكثر أهمية هو أنه يسمح لـAngular بـحقن المصادر، أو التّبعيّات إلى المكوّن الجديد.

scope$

يحتاج متحكّمنا فقط إلى مُعامل يُسمّى scope$ وذلك بفضل إطار العمل الذي يُدار عن طريق حقن التّابعية، كما أنّ تسمية هذا المُعامل هامّة جدًّا.
ويجب عليك أن تعرف خطورة تقصير شفرة JavaScript داخل مشروعك، فهذا قد يؤدي إلى تدمير آلية عمل حقن التّابعية. (ولكن لا تقلق، فهناك حلّ التفافيّ لهذه المشكلة، سنتطرّق إليه في فصل حقن التابعية).

function MessageController($scope) {
   $scope.message = "This is a model.";
}

بإضافتنا للمعامل scope$ إلى تابع البناء نكون قد أخبرنا Angular بحاجتنا إلى مرجع لمجال العرض، والآن بدلًا من تعريف العنصر message داخل this (أي داخل المتحكّم)، قمنا بتعريفه مباشرةً داخل المجال.

في المثال التّالي، قمنا بإزالة as controller من التّوجيه ng-controller، كما أنّ العبارة controller.message أصبحت فقط message وهي العنصر الوحيد الذي قمنا بربطه بالمجال.

<p ng-controller="MessageController">
    {{message}}
</p>

الناتج

This is a model.

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

النموذج-العرض-المتحكم MVC

يشير تعريف المتحكّم في المرجع الرسمي إلى أنّه يكشف “الوظائفيّة” للعرض، وقبل أن ننتقل إلى هذا الجزء يجدر بنا الحديث عن الفرق بين متحكّمات Angular وبين نموذج MVC التقليدي.(قد ترغب في تجاوز هذه الفقرة).

تنصّ ويكيبيديا في مقال النّموذج-العرض-المتحكّم (MVC) على أنّ المتحكّم “يستلم المدخلات ويحوّلها إلى أوامر للنموذج أو للعرض.”، قد تكون عبارة “أوامر للنّموذج أو للعرض” صعبة الفهم في سياق الحديث عن Angular، فـAngular قامت بتبسيط نموذج MVC الخاص بها، باستخدام النّماذج الضّعيفة (anemic models) التي لا تحوي منطقًا برمجيًّا فيها.

النّمط السّائد في Angular هذه الأيّام هو وضع كل منطق العمل، أو ما يُعرف أيضًا بمنطق النّطاق (domain logic) داخل المتحكّم.
بعبارةٍ أخرى، تتّجه Angular نحو نموذج نحيل ومتحكّم سمين.

إذا كنت تألف بعض الأنماط مثل نموذج النطاق الغنيّ (rich domain model) فربّما تجد طريقة Angular متخلّفةً بعض الشيء، أما أنا فأرى الأمر مجرّد طريقة لترتيب الأولويّات.

فعند برمجة طرف المستخدم يكون الفرق الأكثر أهمّية هو بين شيفرة العرض التّريحية الموجّهة نحو المستند من جهة، وبين شيفرة JavaScript التي تتبع أسلوب الأوامر والمقادة بالبيانات والتي تعالج منطق العمل والبنية التحتية للتطبيق من جهة أخرى.

أنا سعيدٌ لأن Angular ركّزت على ذلك فهذا انتصارٌ كبيرٌ لها، وقد يصبح ذات يومٍ فصل منطق العمل عن باقي مسؤوليات المتحكّم هو الأمر الأكثر أولويّة، وقد نرى توجّهات نحو نموذج أغنى.

التوابع

لنقم بكتابة متحكّم يكشف عن تابع بسيطٍ جدًّا، فقط لنختبر إمكانية استدعاء التوابع داخل العرض.

لا بد من أنّك تذكر من الفصل الماضي أن Angular لا تسمح بتعريف التوابع داخل العرض.

المتحكّم التّالي يقوم بإسناد تابع بسيط إلى عنصرٍ في العرض.

function CountController($scope) {
    $scope.count = function() { return 12; }
}

ويمكننا استدعاء التّابع في العرض بكتابة شيفرة JavaScript عاديّة، اسم العنصر مع أقواس، فقط.

<p ng-controller="CountController">
    There are {{count()}} months in a year.
</p>

الناتج

There are 12 months in a year.

لاحظ أن التّابع لم يتم استدعاؤه ونسيانه بعد ذلك ببساطة، بل تمّ ربطه بالنّموذج.

ما الذي يعنيه ذلك؟ ربّما بدأت بالتسليم بأن البنية التحتية في Angular هي الربط (binding)، ومرّة أخرى سأكرّر: لا تقوم Angular باستدعاء التّابع فقط عندما يتم إخراج العرض للمرة الأولى فقط، بل في أيّ وقتٍ يتم فيه تغيير النّموذج المرتبط، لنتمكّن من رؤية ذلك أثناء العمل سيتوجّب علينا كتابة تابع يستخدم عنصرًا من النّموذج.

يستخدم التّابع add في المثال التّالي عنصرين في النّموذج، تم تعريفهما في المجال، وهما operand1 و operand1.
ستقوم Angular باستدعاء التّابع وإخراج النتيجة كلّما تغيّر أحد العنصرين أو كليهما.

function AdditionController($scope) {
  $scope.operand1 = 0;
  $scope.operand2 = 0;
  $scope.add = function() {
    return $scope.operand1 + $scope.operand2;
  }
  $scope.options = [0,1,2,3,4];
}

لقد قمنا في الفصل الماضي بتغطية العديد من الأمثلة عن التّوجيه input في Angular، لذا سنستخدم الآن التّوجيه select لتغيير قيم النّموذج.
لا بدّ من أنك لاحظت السّطر الأخير في الشّيفرة السابقة، حيث تمّ فيه تحضير نموذج خيارات options، وسنستخدم هذا النّموذج لبناء سلسلة داخل وسيط التّوجيه ng-options.
سنستخدم العبارة x for x في بناء السّلسلة، ورغم أنها قد تبدو بلا فائدة لأنها تعيد عناصر السلسلة الأصلية كما هي، إلا أن التّوجيه يحتاج إلى كتابتها على أي حال.(عندما يكون النّموذج مكوّنًا من مصفوفة كائنات objects وهو الأكثر شيوعًا، يمكنك كتابة بانٍ للسّلسلة يقوم بإخراج العنصر name من الخيار المحدد، وذلك باستخدام x.name for x.

<p ng-controller="AdditionController">
  <select ng-model="operand1" ng-options="x for x in options"></select>
  + <select ng-model="operand2" ng-options="x for x in options"></select>
  = {{add()}}
</p>

إنه يعمل بشكل جيّد، ولكن بإمكاننا تحسين تصميم التّابع add عن طريق التّعميم، الذي سيكون مفيدًا إن أردنا استخدام التّابع مع وسطاء غير operand1 و operand2.

فلنبدأ إذا بتعديل الشفرة ولنقم باستخراج الوسطاء من التابع.

function AdditionController($scope) {
  $scope.number = 2;
  $scope.add = function(operand1, operand2) {
    return operand1 + operand2;
  }
}

يمكنك تمرير العناصر والقيم الفوريّة داخل العبارات، كما في JavaScript المعتادة.

<p ng-controller="AdditionController">
  {{add(number, 2)}} is not the same as {{add(number, "2")}}
  <br>
  2 + 2 + 2 + 2 = {{add(2, add(2, add(2, 2)))}}
</p>

الناتج

4 is not the same as 22 
2 + 2 + 2 + 2 = 8

لنقم الآن بالكشف عن تابع استدعاء خلفي (callback) يمكنه معالجة عملٍ ما.

الاستدعاءات الخلفية (Callbacks)

في الفصل السابق، قمنا بتمرير عبارة إلى التّوجيه ng-click واستخدمناها للتّقليب بين قيمتين بوليانيتين للعنصر authorized، حيث قمنا بتهيئة القيمة الابتدائية للنموذج authorized باستخدام التّوجيه ng-init ثم تلاعبنا بقيمته عن طريق استدعاء خلفيّ سطريّ، فكتبنا “ng-click=”authorized = !authorized، لنقم الآن بتعديل المثال عن طريق نقل التهيئة ووضع المتغيّر في مكانه الصحيح، في المتحكّم.

function AuthController($scope) {
  $scope.authorized = true;
  $scope.toggle = function() {
    $scope.authorized = !$scope.authorized
  };
}

والآن صار التّابع toggle متاحًا للاستخدام داخل المجال، والوسيط الذي سيتمّ تمريره للتوجيه ng-click سيبدو كأنه استدعاء للتابع: ()toggle ولكنه ليس كذلك كما ذكرنا سابقًا، إنها فقط سلسلة نصّيّة سيتم معالجتها لاحقًا عندما يقوم المستخدم بالنقر على الزر.

<div ng-controller="AuthController">
  <p>
    The secret code is
    <span ng-show="authorized">0123</span>
    <span ng-hide="authorized">not for you to see</span>
  </p>
  <input class="btn btn-xs btn-default" type="button" value="toggle" ng-click="toggle()">
</div>

لا يزال المثال يعمل، والآن صار منطق التّابع toggle البسيط في مكانٍ أفضل.

تشرح الكتب عادةً تقنيّاتٍ لإدارة التعقيدات باستخدام أمثلة شديدة البساطة لإيصال الإحساس العام بفائدة هذه التقنية، وقد قمنا بذلك هنا أيضًا.

لقد طلبت إليك في الفصل الماضي أن تؤجّل حكمك على استخدام العبارات داخل نصوص HTML، فإذا كنت الآن قد أحببت طريقة Angular في تحسين نصوص HTML مع سلوك تفاعليّ، فقد تتساءل لم هذا التعقيد الزائد في المتحكّمات.

أحد الأسباب التّقليدية لنقل الشّيفرة من سياقٍ معقّد(كالقالب) إلى واحدٍ أبسط (كالمتحكّم) هو تسهيل الاختبار وتصحيح الأخطاء لاحقًا.

تتضح الفائدة من تسليم التعامل مع سلوك المستخدم إلى المتحكّم عندما نحتاج إلى كتابة شيفرات معقّدة، مثل مزامنة بيانات النّموذج مع المخدّم البعيد.

خلاصة

تعرفنا في هذا الفصل على JavaScript بداخل Angular، مكتوبة على شكل متحكّمات، والتي تتحمل مسؤولية تحضير البيانات للعرض كما ينصّ نمط MVC.
تجعل المتحكّمات البيانات متاحةً للعرض عن طريق التصريح عن العناصر في كائن المجال scope$.

في الفصل القادم سنأخذ نظرة أقرب لكائنات المجال، وسنتعرّف على كيفيّة تنظيمها في بنيةٍ هرميّة تتبع تقريبًا بنية الجزء الذي نعالجة في تطبيقنا ضمن مستند.

شارك التدوينة !

عن هيثم النعيمي

مرحبا انا هيثم مدون عراقي انشأت موقع شخصي انشر به ما يعجبني من شعر واخبار و مواضيع عامة وغير ذلك
© Copyright 2014, All Rights Reserved For Haitham.me