‎الكاتب: هيثم النعيمي

مرحبا انا هيثم مدون عراقي انشأت موقع شخصي انشر به ما يعجبني من شعر واخبار و مواضيع عامة وغير ذلك
21فبراير

الدليل الشامل لمنصات تعلم البرمجة بشكل ذاتي ومجاني

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

ولكن هل يمكنك تعلم البرمجة بشكل ذاتي؟، وبطريقة مجانية، من دون دفع أي تكلفة تُذكر؟.. نعم يمكنك ذلك، فأنت لست في حاجة إلى دورات تعليمية، لكن عليك الاطلاع على أمثلة وبرامج مكتوبة بهذه اللغة، لفهم الفكرة العامة قبل البدء في تنفيذها.

وفيما يلي قائمة بأشهر وأهم وأبرز المنصات والمواقع الإلكترونية التفاعلية، التي تهتم بتعليم اللغات البرمجية المختلفة بشكل ذاتي ومجاني:-

منصات أجنبية

sololearn

منصة رائعة لتعلم مبادئ البرمجة، شبيهة تمامًا لمنصة Dulingo الخاصة بتعلم اللغات، من حيث الشكل والتصميم والخطة الممنهجة في التعلم. تسمح المنصة للمستخدمين بتعلم كيفية بناء صفحات الويب، من خلال دورات تدريبية مُصغرة حتى لا تمل منها.

كما تمنحهم مساحة عمل Workspace، للتدريب التفاعلي، وتجربة اﻷكواد التي قاموا بتعلمها، دون الحاجة ﻷي تطبيقات خارجية.

وذلك لتعلم لغات برمجية عديدة، مثل: HTML  و  CSS و  JavaScript و PHP، وغيرها من اللغات، بطريقة تفاعلية عبر التطبيق المُباشر والاختبارات المُتعاقبة والنقاشات بين المستخدمين، بدءًا من الأساسيات وحتى المستويات المُتقدمة بأسلوب سلس ومُمتع.


Swift Playgrounds

بالأساس Swift هي لغة برمجة مفتوحة المصدر، قامت شركة Apple بإصدارها قبل بضعة سنوات، ويستخدمها مطورون محترفون لإنشاء تطبيقات iOS، و MacOS، وtvOS، و Watch OS، فماذا لو تعلمتها، وكانت Apple هي المعلم؟.

تقدم لك Apple تطبيقًا أشبه بمنصة تعليمية لدورة تدريبية مُبسطة وسهلة؛ لتعليم المستخدمين بطريقة تفاعلية سلسة، وخطوة بخطوة المفاهيم الأساسية الخاصة بلغة Swift وبيئة العمل الخاصة بها.

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


Code

منصة تعليمية تفاعلية للغات البرمجة غير هادفة للربح، أطلقت عام 2013 برعاية كبار رواد الأعمال التقنيين في الولايات المتحدة، مثل بيل غيتس، ومارك زوكربيرج، وتحدث عنها الرئيس الأميركي السابق باراك أوباما في أحد خطاباته.

تأسست بهدف تقريب تعلم لغات البرمجة من الأشخاص حول العالم، خصوصًا الأطفال وطلاب المدارس. كما أن الكبار ممن يرغبون في بدء مشوار تعلم المجال التقني، يلجؤون للتعلم عن طريقه أيضًا، لما يحتوي عليه من مواد تعليمية سهلة الفهم واحترافية، من ضمنها درس لكيفية بناء لعبة مثل لعبة فلابي بيرد Flappy Bird الشهيرة.

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


Codecademy

منصة تعليمية مجانية تمامًا، تقوم بتعليم المستخدمين العديد من لغات البرمجة، مثل: Python و Ruby وHTML  وCSS  و JavaScript، بطريقة بسيطة وسهلة، خطوة بخطوة، فتأخذ بيدهم في رحلة ممتعة نحو عالم البرمجة، بطريقة تفاعلية رائعة، لينتهي بهم المطاف إلى برمجة تطبيقات معقدة خاصة بهم.

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

كما أنه يقدم بيئة تدريبية بداخل المواقع للتدرب على تأسيس موقع بإستخدام HTML او CSS قبل الانتقال لتعلم لغات مثل Ruby  أو Python.


Team Tree house

منصة شبيه بـCodecademy ، غير أنها تحتاج لاشتراك مدفوع، فالدورات مقسمة إلى وحدات أو مراحل مختلفة، بعد كل أول مرحلة المتعلم سيضطر لدفع رسوم اشتراك شهري قدرها 25 دولار للوصول للمراحل التالية.

غير أنها تحتوي على مكتبة محدثة لأكثر من 1000 مقطع مُميز من مقاطع فيديو تعليمية تم تصميمها بواسطة خبراء محترفون، ومنتدى للمناقشات بين المستخدمين، فضلًا عن فرصة مشاهدة المقابلات التي تجريها المنصة مع المبرمجين المحترفين، ومشاهدة الورش التدريبية.


Free Code Camp

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


LearnStreet

يقدم دورات تعليمية مجانية احترافية للمهتمين بتعلم لغات JavaScript، وRuby، و Python، يمكن لأي كان متابعتها، فبعد الدخول إلى المنصة تظهر مباشرةً الدورات، وتختار منها ما ترغب لتبدأ، وبعد إتمام الدورة تحصل على شهادة افتراضية.


Python

البرنامج التعليمي في الموقع الرسمي للغة Python، وعلى الرغم من أنه مكثف جدًا في الشرح لكنه جيد، ويمنحك فهم رائع لـ Python.


Python Challenge

عمر الموقع أكثر من 10 سنوات تقريبًا، وهو موقع مخصص لتعليم لغة البايثون بطريقة غير تقليدية وذلك من خلال طريقة الألغاز، حيث أن الألغاز البرمجية هي أسرع طريقة لتعلم لغة البرمجة‎.


KillerPHP

منصة تعليمية خاصة بتعلم لغة البرمجة PHP، وكل ما يخصها.


HTML5 Rocks

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


Dive into HTML5

سلسلة كتبها Mark Pilgrim سنتعلم من خلالها أساسيات HTML5 وكيفية الإنتقال إليها من إصدارات  HTML أقدم مع مراعاة دعم المتصفحات المختلفة.


HTML5 Doctor

كسابقة تمامًا، مُتخصص بلغة HTML5، كما أن هناك العديد مثله، كـ: HTML 5 Demos و HTML5TEST.


SQLZOO

موقع لتعلم لغة SQL الخاصة ببرمجة قواعد البيانات Data Base فبالأساس SQL هي لغة تم تصميمها لجعل تخزين البيانات وإسترجاعها من قاعدة البيانات أمرًا سهلًا، وهذه المنصة تسمح لمستخدميها بتعلم تلك اللغة مع شروحات احترافية، باهتمام وتفاعل مع واجهة مميزة.


Code Ranch

منصة تعليمية تقدم دروس بسيطة وسهلة الفهم، لكنها بنفس الوقت إحترافية، باستخدام واجهة تصميم جذابة، لتعلم أساسيات الجافا، وبرمجة الروبوت، وأنظمة التشغيل، وتصميم قواعد البيانات، وغيرها من اللغات البرمجية مثل:  PHP و Python.


Code School

يقدم دورات تعليمية مُتعمقة لعدد من لغات البرمجة، بالإضافة لدورات تعليمية خاصة بتطوير قواعد البيانات Database، وتطوير التطبيقات، ولكنه يتطلب دفع مبلغ 29 دولار أمريكي شهريًا للوصول لجميع الدورات التعليمية المتوفرة من خلاله، ولكنه يوفر العديد من الدروس التعليمية المجانية.


Code Avengers

منصة لطيفة لتعلم لغة HTML5 و CSS و JavaScript فقط من خلال دروس ممتعة تعتمد بالأساس على الألعاب، في طريقة كتابة الأكواد ومتابعة التغيير الطارئ على النتائج، وفي نهاية كل مرحلة توجد لعبة صغيرة للترفية عن المستخدمين.


Standford

منصة تعليمية رائعة مقدمة من جامعة ستانفورد، بهدف تقديم دورات تعليمية في البرمجة.


CodeHS

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


Sourcegraph

منصة مجانية، يمكن للمستخدمين الراغبين بتعلم لغات GO من جوجل، بايثون Python أو Node.JS الاستعانة بها، حيث توفر محرك بحث متخصص بهذه اللغات، وتقدم للمستخدمين شرحًا كاملًا مع أمثلة لأي دالة Function مع طريقة استخدامها في أحد هذه اللغات.


whatsnext

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


The Code Player

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


W3schools

يقدم هذا الموقع دروسًا تعليميةً سهلةً لمن يرغب في تعلم البرمجة من البداية وحتى الاحتراف، مع العديد من الشروحات والأمثلة، مع إتاحة الفرصة لتطبيق هذه الأمثلة، الموقع يوفر دروسًا في HTML وCSS  و JavaScript و PHP وjQuery  و SQL.


Coding for good

منصة تعلم مستخدميها البرمجة من البداية، عبر روابط مُختارة لمصادر إلكترونية مُختلفة، وفي نهاية كل جزئية هناك سيتعين عليهم القيام بالتطبيق العملي خطوة بخطوة.


Mozilla Developer Network

توفر Mozilla عبر منصتها التعليمية الرسمية، والمعروفة اختصارًا بـ MDN دروسًا لمن لديهم معرفة أولية بالبرمجة وآخري للمحترفين لتعلم تطوير المواقع وتطبيقات الويب.


Scratch 2.0

منصة تعليمية سهلة للغاية للمبتدئين، تسمح للمستخدمين بخلق وتحميل مشاريعهم ومشاركتها على الموقع، جيد في تعلم كيفية تفتيت المشاكل التي تواجه المبرمجين لمشاكل أصغر، يمكن حلها منطقيًا.


Dash

منصة تعليمية تفاعلية، مجانية تمامًا، تقدم للمستخدمين دروسًا تفاعليةً عن كيفية بناء/تطوير موقع شخصي.


PROGRAMMR

منصة تقدم دروس في جميع لغات البرمجة الشهيرة مثل java وPHP، و Ruby، و Ajax، و JS، و C++، وحتى دروس برمجة وتطوير الجوال، وبها العديد من التمارين والتطبيقات.


Udacity

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


 Codeconquest

منصة تعليمة آخرى لتعلم دروس في أساسيات البرمجة لتطوير مهاراتك البرمجية.


 منصات عربية

HTML.net

موقع مُتخصص للمهتمين بدروس HTML  و CSS و PHP من المستخدمين المبتدئين، ويوفر منتدى للنقاش بين المتعلمين عليه.


 ARCCN

موقع عربي، متخصص لدروس برمجة لغة PHP فقط من الألف إلى الياء.


برمجة

موقع عربي، يوفر مجموعة كبيرة من الدروس لتعليم البرمجة، وأساسيات التصميم، والأمن ،والشبكات مجانًا مع توفير شروحات بالفيديو.


مشروع مطوري الويب

يقدم مقاطع فيديو لدروس متنوعة حول أساسيات وأساليب تصميم وبرمجة المواقع بسهولة، باستخدام لغات HTML  و CSS و PHP  و JQuery.


شبكة عبدالله عيد

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


المعهد العربي لتعلّم لغات البرمجة

يقدم دروسًا، ومناهج، وأمثلة، ومراجع، واختبارات مجانية لجميع لغات البرمجة وباللغة العربية، ويمكن لأي شخص ليس لديه أي فكرة عن تصميم المواقع أن يبدأ معه إلى أن يصبح محترفًا.

    

كانت هذه قائمة بأشهر وأهم وأبرز المنصات والمواقع الإلكترونية التفاعلية الأجنبية والعربية، التي تهتم بتعليم اللغات البرمجية المختلفة.. هل تفكر في تعلم البرمجة بالفعل؟ لاحجة لك بعد الآن!

7أكتوبر

مهارات جانبية ضرورية عليك إكتسابها الى جانب البرمجة

في الغالب انت يا من يقرأ الأسطر التالية مبرمج ، و سأجزم ذلك كوني وضحت في بداية عنوان هذا الموضوع عبارة ” عزيزي المبرمج ” ، و البرمجة عزيزي من أهم و أفضل المهارات بالطبع التي يتسم بها كل من إحترف كتابة أكواد معقدة و تبسيطها للمستخدم البسيط ، و لا يمكنني ان انكر ان البرمجة من المهارات الأكثر طلبا في مجال التقنية المعلوميات و التطوير المعلوماتي و التكنولوجيات الحديثة ، فأنا اقرأ يوميا مجموعة من المواضيع و المقالات في هذا المجال ، و اجد دائما ان رأس القائمة تتطلب مبرمجين من أجل إستخلاص ذاك العمل ، و لكن ، إن تحدثنا من جانب آخر ، فالبرمجة ليست كافية لإنشاء و تطوير مشروع كامل متكامل ، لا تحقد علي و تبدأ بالسب و الرفض فور قراءتك لهذه الأسطر فقط ، بل ادعوك لقراءة الموضوع كاملا ، و سأوضح لك ذلك خلال هذا الموضوع .
 
 
نعم ، يمكنني ان اشهد بلساني ان البرمجة أخذت حصة الأسد لتطوير و نشأة مشروع ما – و بمشروع أقصد برنامج ، موقع ، تطبيق …. – ، و اعدك أيضا ان كان لك فريق عمل اخر من يتقن المهارات التالية القادمة في هذا الموضوع ، فسأخبرك ان تكتفي بالبرمجة فقط ، لكن كلما ثقفت نفسك في مجالات كثيرة كلما زادت قيمتك و قيمة عملك ، و كونك مبرمج ، فالبرمجة تتخذ الجزء الأكبر في عملك ، لكن المهارات التالية لا يجب نسيانها و تجاهلها كما لو كانت لا شيئ ، في الحقيقة فهي ضرورية و عليك ان تتسلح بها بداية من نهاية قراءتك لهذا الموضوع ، فدعني إذن أحدثك عن هذه المميزات و المهارات التي يجب عليك إكتسابها :

– النظرة الإستلهامية و التصميم المتناسق :
نعم نعم ، اعلم ما يدور بعقلك الآن ، للتصميم الإبداعي و الإبتكاري ناسه ، و كوني مبرمج لا احتاج الى الشكل الجذاب لبرنامجي ، يكفي ان يكون لون برنامج الرمادي و الأسود و أزرار كما لو بعثت من الويندوز Xp ، و إن فكرت يوما في تطوير برنامجي او موقعي او تطبيقي فسأقوم بتقديمه لأحد الديزاينرز و المصممين و هو سيتكلف بالأمر ، نعم ، ربما يجب ان نضع موضوع أيضا حول ” عزيزي المصمم .. عليك ان تتعلم البرمجة فهي مهارة ضرورية عليك إكتسابها الى جانب التصميم ” ، سأصارحك و أخبرك ان المصمم يمكنه ان يستغني عن البرمجة ، نعم ، يمكنه صناعة شكل موقع او برنامج على شكل UI و بيعه و هو مرتاح ، لكن بالنسبة لك انت كمبرمج ، فستحتاج الى ان تتعلم التصميم شئت ام ابيت ، خصوصا عندما يصبح التصميم هو نفسه البرمجة كما في برمجة تطبيقات الأندرويد ، فالماتيريال ديزاين في الأندرويد عبارة عن أكواد برمجة ، و أنت يا صديقي المبرمج عليك ان تتعلم الإستلهام و التصميم الإبداعي ، يمكنك الحصول عليه بالطبع بالعديد من الطرق ، اولا ان تلاحظ أعمال الأخرين الإبداعية و ان تكتسب و تحاول ان تدقق في أبسط التفاصيل من اجل فهم طريقة تصميمها ، و إن لم تكن لك أي دراية من قبل عن هذا المجال ، فمشاهدة أحد الكورسات من هنا الخاصة بالتصميم ، او البحث بنفسك في اليوتيوب او مواقع تقديم الكورسات مثل Udacity و Lynda .
و إن كنت صديقي المبرمج تعتبر التصميم و الذوق الإبداعي في تصميمك لا شيئ ، فربما يجب عليك ان تعيد التفكير و تعيد التفكير حتى تقتنع بعكس ذلك تماما ، لا نطلب منك أن تكون مصمما محنكا ، فقط ان تدرك مبادئ التصميم .

– السيو و أرشفة المواقع :
إن كنت مبرمج تطبيقات سطح المكتب او مبرمج تطبيقات موبايل ، او محلل قواعد بيانات ربما انت لا تحتاج الى إكتساب مهارة الأرشفة التي أنا بصدد شرحها ، لكن إن كنت مبرمج تطبيقات ويب و مواقع ، فدعني اقول لك انها مهارة لا يُستغنى عنها بالمرة اخي الكريم ، فقد تصنع و تطور موقعا لأحد العملاء ، و ترفعه له على إستضافة و يكون كل شيئ جيد و يدفع لك المال الخاص بك ، و سيراسلك بعد ثلاث أيام يخبرك انه عندما يبحث عن الموقع في جوجل لا يظهر له ، فستكون محرجا حينها و لن تستطيع ان تنبس ببنت شفتة و تخبره انه ليس من إختصاصك مثلا او حتى لا تعرف كيف لا يظهر ، في حين أنك أغفلت في اكوادك عن مبادئ السيو ، و نعم السيو له أكواده الخاصة مثل اكواد الMetatag او كود الملكية التي ستطلبها لك جوجل عند محاولة إدراج موقعك الى جانب المواقع الأخرى ، او حتى قد يخبرك انه يريد ان يظهر موقع في مقدمة محركات البحث ، و يريد ان يحصل ايضا على زوار من Bing و Baidu و Yandex ، ستخبره انك مبرمج ، و سيخبرك انك لست كذلك بل انت صانع مواقع و موقعي لم يتم صناعته بعد بالنسبة لمحركات البحث ، ليسكتك مجددا ، و ربما تدخل معه في مشاكل لاحقا قد تضطر فيها لإعادة امواله ، خصوصا ، لذلك صديقي ، يمكنك ربح أموال إضافة بعرض خدماتك للسيو و البرمجة في آن واحد للزبون ، و بذلك ، يكون الزبون راضيا و يكون عملك إحترافيا .
السيو له خبراءه و أتفق في هذا إتفاقا شديدا ، لكن معرفتك بهذا المجال و أخذ فكرة عنه و تعلم مبادئه سيفيديك اكثر مما قد لا يضرك .

– مهارة التسويق و البيع :
لا غنى عنها مهما كانت نوعيتك البرمجية ، سواء محترف تطبيقات او مواقع او كيفما كنت ، التسويق مهارة يجدر بك تعلمها منذ نعومة اظافرك ، فور ان تبدأ بفهم العالم ، ستفهم أيضا ان العالم يدور إجمالا حول طبقتين ، مستخدم و بائع ، البائع يبيع المنتوج ، و المستخدم يستخدم المنتوج ، و انت يا صديقي المبرمج ، عندما تبرمج شيئا ما فهو يكون عبارة عن منتوج ، و تحتاج إذن الى مستخدمين لذاك المنتوج حتى تحقق غايتك الربحية مما قمت ببرمجته ، لكن فن الإقناع لا يأتى على هوادة ، بل تحتاج الى خبرات في مجال الإقناع و تجميل منتوجك و اللعب على عقلية المستخدم / الشاري و نفسيته أيضا ، و ان تخلق حاجة المستخدم الى ذاك المنتج ، و كل هذه الأشياء قد تم تجميعها في مهارة واحدة تسمى التسويق ، و التسويق مجال جد موسع و خبراءه لا يمكنك ان تفوقهم خبرة بقرائتك لمقالات او مشاهدتك لكورسات ، فقد إكتسبو مهارة البيع و التسويق منذ الأزل ، و لن يمكنك التفوق عليهم ان قررت الدخول الى مجال التسويق ، لكن هذا لا يستثني اخي الكريم انك لن تستطيع بيع منتوجك ، بل تستطيع اخي و بكفاءة ، يكفي اولا ان تعقد العزم على تعلم فنون التسويق اولا ، ثم تبدأ بأخذ كورسات مجانية في هذا المجال ( راجع صفحتنا هذه للكورسات ) او إبحث عن كورسات في مواقع أخرى ، و إقرأ مقالات في مجال التسويق و إكتسب و تعلم ، ثم أحضر منتوجك – ما برمجته – امامك ، و حاول ان تختار احد الأشخاص سواء المقربين لك او البعيدين ، و حاول ان تطبق ما إكتسبته من فنون خلال كورساتك و مقالاتك ، و أطلق العنان لمهارتك الجديدة ، فإن لم تنجح في إقناع صديقك بالشراء ، فتعلم أكثر و تعلم حتى تصبح قادرا على بيع كل ما يقع بين يديك لأي شخص ، و بعدها ، أضمن لك ان كل ما تبرمجه و تصنعه ستجد من يشتريه .

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

30يوليو

( تعلم Angular ) – التوجيه Routing في AngularJS

تقتصر بعض تطبيقات Angular على كونها أدوات مساعدة في صفحة ويب تقليدية، إلا أنّ الأغلبية العُظمى لتطبيقاتها هي التطبيقات وحيدة الصفحة (single-page applications)، التي تستبدل تصفح الويب المعتمد على المتصفّح بواجهاتٍ وانتقالاتٍ مشابهة في صفحةٍ واحدة، مقدمة للمستخدم تجربة تفاعليّة رائعة. إلّا أنّ كتابة تطبيقات وحيدة الصفحة بطريقةٍ بسيطة يجعلنا نخسر شيئًا قيّمًا في التطبيقات المعتمدة على المتصفّح وهو الرّابط URL. ففي تطبيقٍ بسيطٍ وحيد الصفحة، سيكون هناك رابط URL واحد فقط، ولن تكون هناك طريقة لمشاركة روابطَ مخصصة لكلّ مورد (resource) على حدة، كتعليق محدّد على تدوينة ما على سبيل المثال. وعلى العكس، فسيقوم تطبيق تقليدي في طرف المخدّم يتبع نمط REST (تبادل الحالة ضمن رابط HTML) بإدارة الواجهات كبنية هرميّة من الروابط المنظّمة سهلة القراءة والتي تظهر حالة التّطبيق الحاليّة بوضوح. تُقدّم هذه الروابط طريقةً للمستخدم ليعود إلى حالةٍ من حالات التطبيق في وقتٍ لاحق، أو ليشارك هذه الحالة مع الآخرين.

يُعرَّف التوجيه في تطبيق ويب تقليديّ بأنّه صلة الوصل بين عمليّات HTTP مثل (GET وPOST وما إلى ذلك) وأنماط الروابط وبين المتحكّمات. إن لم تكن معتادًا على التوجيه من طرف المخدّم فسيقدّم لك دليل التوجيه السريع مقدّمةً سهلةً للتوجيه في JavaScript. على أي حال، فالتوجيه من طرف المستخدم يقلب الحالة رأسًا على عقب. فبدلًا من أن يتمّ التفاعل مع الأفعال التي ينقلها لنا المتصفّح، فسنحتاج إلى إبقاء المتصفّح متابعًا للتحديثات بينما يتفاعل التطبيق مباشرةً مع مدخلات المستخدم. كمثالٍ على ذلك، كيف يمكننا الإبقاء على شريط التّنقل محدّثًا بروابط تمثّل الحالة بدقّة، وفي نفس الوقت نستجيب لروابط تُدخل فيه أو تُحمّل من العلامات المرجعية؟ لحُسن الحظ، فقد تمّ إيجاد الوحدة ngRoute لتساعدنا في هذا العمل.

لنتمكّن من استخدام هذه الوحدة، سنحتاج إلى تحميل ملف angular-route.js إضافةً إلى ملف مكتبة Angular الرئيسي. كما سنقوم بتحميل بيئة Bootstrap لاستخدام أنماط CSS الخاصة بها.

<base href="/">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular-route.js"></script>

لاحظ تعريف العنصر base في السطر الأول في المثال السابق. يُعرِّف هذا العنصر رابط URL القاعديّ لتطبيق Angular الحالي، وهذا العنصر يطلبه دعم Angular لـتوابع HTML5 الخاصة بالتاريخ (سنتحدث عنها أدناه). إن كنت تتساءل لمَ تم إسناد الخاصية href إلى مسار الجذر، بدلًا من أن تكون مسندةً إلى مسار الصفحة، فهذا لأنّ أمثلة هذه السلسلة تعمل داخل أُطُر iframe خاصة لكل واحد منها.

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

بعد ذلك، سيكون علينا جلب ngRoute عند تهيئة الوحدة الجذر، وقد قمنا بتغطية هذه الفكرة بعمق في فصل الوحدات.

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

وهكذا نكون قد أتممنا تحميل الوحدة ngRoute في تطبيقنا.

routeProvider$

لنتمكن من استخدام ngRoute، سنحتاج إلى ربط مسار URL نسبي واحدٍ أو أكثر مع معالجاتٍ (handlers). يتيح لنا التابع module.config إعداد وحداتٍ مثل ngRoute بعد أن تقوم Angular بتحميلها. كل ما علينا هنا هو معرفة اسم الخدمة (service) أو المزوّد (provider) الذي سيقوم بإعداد الوحدة. في حالة ngRoute يُسمّى المزوِّد بـrouteProvider$. سنتمكن عند وضع هذا الاسم في تابع الاستدعاء الخلفي config من عمل سلسلةٍ من عمليّات ربط المسارات باستخدام التابع when. الوسيط الأول للتابع when هو المسار النسبي الخاص بوجهة التوجيه، أما الوسيط الثاني فهو كائن الإعدادات الذي يحدد الكيفية التي سيتم بها معالجة محتوى وجهة التوجيه.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        template: "<a href='#/about'>About</a>"
      })
      .when('/about', {
        template: "<a href='#/'>Home</a>"
      });
  });

يشبه الخيار template الذي يظهر في المثال السابق ذلك الذي استخدمناه في إعداد التوجيهات سابقًا. لا يحوي المثال السابق أي محتوىً متغيّر، فقط نص HTML ثابت. يُقدّم كلّ قالبٍ رابطًا إلى المسار النسبي للآخرين، لذا سنتمكن في المثال السابق من التبديل بين الاثنين. قد تتساءل أين سيتم وضع المحتوى المعالَج، حسنًا، تُقدّم الوحدة ngRoute توجيهًا مُميّزًا، هو ng-view، ويجب أن يكون هذا التوجيه موجودًا في وحدتنا لتعمل. تتكوّن القوالب الرئيسي في هذا الفصل كلّها من عنصرdiv خالٍ يوجد فيه التوجيه ng-view، وكلّ ما سوى ذلك سيقوم المعالج (handler) بمعالجته للمسار الحالي.

<div ng-view></div>

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

الخدمة location$

جميع أمثلة هذه السلسلة موضوعة داخل صناديق sandbox داخل أُطر iframe الخاصة بكل واحد منها. (يُمكنك إلقاء نظرة على الرابط التالي Codecademy/stuff.js إن كان لديك فضول لمعرفة طريقة القيام بذلك.) وهذا ممتازٌ لعزل البيئة البرمجية، إلا أنّه يمنعك من رؤية رابط الـURL الخاص بالأمثلة في شريط الانتقال. ومن حسن الحظ أنّه توجد طريقةٌ للتحايل على هذا الأمر، مع فائدةٍ إضافيّة في استكشاف خدمةٍ مفيدةٍ في Angular تسمح بمعاينة رابط URL الحالي، وهي الخدمة location$.

angular.module('app')
  .controller('LocationController', function($scope, $location) {
    $scope.location = $location.absUrl();
  });

يتم في المثال السابق التصريح عن متحكم بسيطٍ يستقبل الخدمة location$ عن طريق حقن التبعية، ويستخدمها للدخول إلى رابط URL المُطلق الحالي للتطبيق، حيث تكون القيمة هي دمجًا للرابط الثابت للجذر الخاص بالصفحة المغلّفة للمثال مع رابط الامتداد الذي تُديره الوحدة ngRoute.

المتحكم

يُمكننا أن نقوم بتحميل المتحكّم LocationController في المثال السابق بالطريقة المعيارية باستخدام التّوجيه ng-controller، إلّا أنّه بإمكاننا أيضًا أن نقوم بإعداد متحكّمٍ كخيار (option)، كما سنضيف خاصّيّة المجال location إلى قالبنا.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        controller: 'LocationController',
        template: '<div class="well well-sm" ng-bind="location"></div>\
                   <a href="#/about">About</a>'
      })
      .when('/about', {
        controller: 'LocationController',
        template: '<div class="well well-sm" ng-bind="location"></div>\
                   <a href="#/">Home</a>'
      });
  });

ها نحن ذا، صار لدينا الآن محاكٍ لشريط الانتقال لأمثلتنا، ولكن للأسف لن تتمكّن من تعديلها لرؤية الكيفية التي ستدير بها الوحدة ngRoute التغييرات بمعالجتها وإظهار محتوىً جديد، وسيكون عليك اختبار ذلك في تطبيقاتك الخاصة.

رمز Hashbang

تتوقّع وحدة ngRoute في الحالة الافتراضية أن تبدأ مسارات URL النسبيّة برمز hash (#)، ويُمكنك بسهولةٍ تغييره إلى البادئة hashbang متمثّلة بالرمز (!#) بإضافة إعداداتٍ إلى الخدمة locationProvider$ كما يبيّن المثال التالي. لاحظ أنّه يجب علينا أيضًا إضافة رمز ! إلى الروابط في قوالبنا.

angular.module('app')
  .config(function($locationProvider) {
    $locationProvider.hashPrefix('!');
  })
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        controller: 'LocationController',
        template: '<div class="well well-sm" ng-bind="location"></div>\
                   <a href="#!/about">About</a>'
      })
      .when('/about', {
        controller: 'LocationController',
        template: '<div class="well well-sm" ng-bind="location"></div>\
                   <a href="#!/">Home</a>'
      });
  });

لا يزال رمز البادئة hashbang مصادقًا عليه من Google في دليل Webmasters الخاص بها، وتحديدًا في المقطع Making AJAX Applications Crawlable، الذي ينص على أنّ “أقسام الـhash يجب أن تبدأ بعلامة تعجّب”، على أيّ حالٍ، طالما أنّك تقوم بالخطوات اللازمة للتّأكّد من ملاءمة موقعك لقواعد SEO فقد تجد أنّه من الأفضل أن يكون تطبيقك مخدومًا بأسلوبٍ خالٍ من البادئة، وهذا الأسلوب مفعّلٌ في HTML5 الآن.

الكائن History من HTML5

يُقدّم الكائن window.history تحكّمًا بالتّنقّل في المتصفّحات الحديثة، مما يقدّم تجربة مستخدمٍ سلسة.

angular.module('app')
  .config(function($locationProvider) {
    $locationProvider.html5Mode(true);
  });

يُمكننا كتابة قيم href الخاصة بنا كمسارٍ بسيط، بدون البادئة # ولا البادئة !#.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        controller: 'LocationController',
        template: '<div class="well well-sm" ng-bind="location"></div>\
                   <a href="/about">About</a>'
      })
      .when('/about', {
        controller: 'LocationController',
        template: '<div class="well well-sm" ng-bind="location"></div>\
                   <a href="/">Home</a>'
      });
  });

استخدام الكائن history من HTML5 وسيلةٌ ممتازةٌ إن كنت غير مضطرٍّ لدعم متصفحاتٍ قديمة في تطبيقك.

العنصر templateUrl

لنقم الآن بتحليل تطبيقنا البسيط إلى مكوناته الأولية. كما قمنا سابقًا مع التوجيهات بجعل شيفراتنا البرمجية أكثر قابلية للإدارة عن طريق استخراج قوالبنا (حتى الصغيرة منها) إلى ملفاتٍ مستقلة، فسيكون علينا للقيام بذلك فقط استبدال العناصر template في إعداداتنا بالعناصر templateUrl.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        controller: 'LocationController',
        templateUrl: '/views/index.html'
      })
      .when('/about', {
        controller: 'LocationController',
        templateUrl: '/views/about.html'
      });
  });

دون تغييرٍ على صفحة About التي سنضعها في مجلد views.

<div class="well well-sm" ng-bind="location"></div>
<a href="/">Home</a>
<h4>About</h4>

سنتكلّم في الفقرات التالية عن فكرة التعامل مع روابط URL غير الصالحة، لذا لنقم بإضافة واحدٍ منها الآن.

<div class="well well-sm" ng-bind="location"></div>
<a href="/about">About</a> |
<a href="/bad-path">Bad path</a>
<h4>Home</h4>

جرّب النقر على Bad path في المثال السابق، ستنتهي التّسلية للأسف، وستضطر لإعادة إنعاش الصفحة لاستعادتها.

التابع otherwise

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

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

<div class="well well-sm" ng-bind="location"></div>
<a href="/">Home</a>
<h4 class="text-danger">404 - Not Found</h4>

كلّ ما نحتاجه الآن هو عبارة when أخرى لإضافة المسار 404/. ثم لربط أي رابط غير متوافق مع الروابط الموجودة بالمسار 404/، وسنقوم بذلك بإضافة استدعاءٍ للتابع otherwise الذي يقوم بإضافة المسار إلى العنصر redirectTo فيه. في المثال التالي، سيتم تشغيل الاستدعاء للتابع config إضافةً إلى كل الإعدادات السابقة. (يُمكنك تسجيل عددٍ غير محدودٍ من الاستدعاءات الخلفية (callbacks) للتابع config في المزوِّد (provider))

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/404', {
        controller: 'LocationController',
        templateUrl: '/views/404.html'

      })
      .otherwise({
        redirectTo: '/404'
      });
  });

جّرب النقر الآن على Bad path. صحيحٌ أنّ بيئة الأمثلة لا تسمح لك بإدخال مسارٍ عشوائيّ في شريط التصفح، إلّا أنّه يمكنك تعديل “href=”/bad-path في القالب السابق إلى أيّ قيمة تريدها لترى كيف سيتم التعامل معها. ربما يجب عليك دومًا إضافة معالجٍ (handler) ليتعامل مع كل الاحتمالات الباقية في إعدادات التوجيه الخاصة بك.

الأحداث (Events)

تُقدّم Angular تطبيقًا للرسائل من نمط (publish-subscribe) لأحداث التطبيق، وهي تعتمد على ثلاث توابع: emit$ وbroadcast$ وon$، حيث يقوم التابع emit$ و broadcast$ بتفعيل نشر أحداثٍ مخصصة، أما التابع on$ فيقوم بتسجيل معالجات (handlers) للأحداث التي تقوم بنشرها الوحدة ngRoute. فمثلًا، عندما يتمّ تغيير المسار بنجاح سينتج عن ذلك نشر الأحداث التالية:

هذه الأحداث مرتبةٌ في القائمة بنفس ترتيب ظهورها أثناء دورة حياة عملية تغيير المسار، لنقُم بإثبات ذلك.

تسجيل معالج للحدث (event handler)

لنستخدم التابع on$ لتسجيل معالجٍ بسيط لأحداث المسار، حيث سيقوم هذا المعالج بجمع أسماء الأحداث في مصفوفة، ثم سنقوم لاحقًا بطباعة أسماء الأحداث في الصفحة بنفس الترتيب الذي حُفظت به.

angular.module('app')
  .value('eventsLog', [])
  .factory('logEvent', function(eventsLog) {
    return function(event) {
      eventsLog.push(event.name);
    };
  });

في هذا المثال، سنقوم بتسجيل الأحداث أثناء انتقالنا من مسار الجذر (والمسؤول عنه هو المتحكم HomeController) إلى المسار events/ (والمسؤول عنه المتحكم EventsController). سنقوم أيضًا بجعل المتحكّم يضيف اسمه إلى القائمة أيضًا لنعرف وقت استدعاء هذا المتحكم.

angular.module('app')
  .controller('HomeController', function($scope, $location, $rootScope, eventsLog, logEvent) {
    $scope.location = $location.absUrl();
    $scope.link = {path: '/events', title: 'Events'};
    eventsLog.push("HomeController: " + $location.path());

    $rootScope.$on('$routeChangeStart', logEvent);
    $rootScope.$on('$locationChangeStart', logEvent);
    $rootScope.$on('$locationChangeSuccess', logEvent);
    $rootScope.$on('$routeChangeSuccess', logEvent);

    $scope.eventsLog = eventsLog;
  });

يقوم المتحكم EventsController بإضافة اسمه إلى المصفوفة eventsLog ثم يقوم بكشف (expose) السجل للمجال بحيث يمكننا رؤيته في الصفحة.

angular.module('app')
  .controller('EventsController', function($scope, eventsLog, $location) {
    $scope.location = $location.absUrl();
    $scope.link = {path: '/', title: 'Home'};

    eventsLog.push("EventsController: " + $location.path());
    $scope.eventsLog = eventsLog;
  });

نفس الواجهة ستعمل في كلا المتحكّمين. وسيتم عرض شريط انتقالٍ متغيّر إضافةً إلى محتويات السجل باستخدام التوجيه ng-repeat.

<div class="well well-sm" ng-bind="location"></div>
<a ng-href="{{link.path}}">{{link.title}}</a>
<ol>
  <li ng-repeat="event in eventsLog track by $index">
     {{event}}
  </li>
</ol>

كملاحظةٍ جانبية، يُعدّ المرشح json المبني في Angular مفيدًا في التنقيح وإنشاء السجلات، فإن أردنا رؤية ما هو أكثر من اسم الحدث، فيُمكننا استخدامه لعرض كامل المحتويات لكائن الحدث.

سيكون علينا لإتمام المثال أن نكتب إعداداتٍ مباشرةً للمسارين.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        controller: 'HomeController',
        templateUrl: '/views/events/index.html'
      })
      .when('/events', {
        controller: 'EventsController',
        templateUrl: '/views/events/index.html'
      });
  });

انقر على الرابط في المثال السابق لرؤية الأحداث المسجلة في الصفحة.

الموارد التي تتبع أسلوب REST

يُقدّم أسلوب REST في تطوير تطبيقات الويب مجموعةً من المعايير لتنظيم عمليات CRUD (إنشاء، قراءة، تحديث و حذف) على مجموعات الموارد والموارد المفردة أيضًا. لن تحتاج عند استخدامك لـ Angular أن تتّبع أسلوب REST في التوجيه إلا إذا قمت بتحميل الوحدة ngResource التي لم نقم بتغطيتها في السلسلة. على أيّ حال، سنختم هذا الفصل بالقليل من التوجيه المتّبع لأسلوب REST لتحصل على خبرةٍ في بعض الأمور العمليّة في التوجيه.

سنحتاج إلى مجموعة موارد لنتمكّن من البدء، وسنعرض في الفصل التالي HTTP كيف نقوم بتحميل البيانات من مخدّمٍ في النهاية الخلفية (backend). أما الآن فسنقوم فقط بحقن مصفوفةٍ من كائناتٍ من نوع item في داخل المتحكّم.

angular.module('app')
  .factory('Item', function(filterFilter) {

    var items = [
      {id: 1, name: 'Item 1', color: 'red'},
      {id: 2, name: 'Item 2', color: 'blue'},
      {id: 3, name: 'Item 3', color: 'red'},
      {id: 4, name: 'Item 4', color: 'white'}
    ];

    return {
      query: function(params) {
        return filterFilter(items, params);
      },
      get: function(params) {
        return this.query(params)[0];
      }
    };
  });

لكلّ عنصرٍ في المصفوفة قيمة فريدة للخاصية id فيه، مما يسمح لنا بالتعامل معه كموردٍ منفرد. تقوم الخدمة التي يُنشئها التابع factory بتغليف الوصول إلى المصفوفة items بتابعين مفيدين هما: query لمجموعة الموارد، وget للموارد المنفردة. يستخدم التابع query المرشّح ذا الاسم سيء الحظ، filter (محقونٍ مع filterFilter) لتنفيذ جلبٍ حسب المثال باستخدام الوسيط params، أمّا التابع get فيقوم باستدعاء التابع query ليقوم بإعادة موردٍ واحد. (راجع فصل المرشحات إن كنت قد تجاوزته، لمعلوماتٍ إضافيّة عن المرشّحات في Angular.)

أوّل شيءٍ نحتاجه في تطبيقٍ يتبع أسلوب REST هو دليل (index) أو مسارٌ للقائمة ليتم كشف المجموعة items كاملةً. وكلّ ما على المتحكم القيام به هو كشف كامل المحتويات للمصفوفة items باستدعاء التابع query دون وُسطاء.

angular.module('app')
  .controller('ItemsController', function($scope, $location, Item) {
    $scope.location = $location.absUrl();
    $scope.items = Item.query();
  });

واجهة هذا المتحكم سهلةٌ أيضًا، وسنستخدم التوجيه ng-repeat لإظهار العناصر.

<div class="well well-sm" ng-bind="location"></div>
<a ng-href="/">Home</a>
<ol>
  <li ng-repeat="item in items">
     {{item.name}} - {{item.color}}
  </li>
</ol>

في هذا المثال الأساسيّ، ستربط إعدادات التوجيه بين مسار الجذر (/) وبين واجهة المجموعة view.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        controller: 'ItemsController',
        templateUrl: '/views/items/index.html'
      });
  });

لا يُحقّق المثال السّابق أسلوب REST فعلًا، وذلك لأنّ المسار ليس اسم مجموعة الموارد (items/). المشكلة هي أننا نحتاج إلى ننشئ شيئًا ما في مسار الجذر ليكون المكان الافتراضي الذي يتم تحميل تطبيق Angular منه. سنرى الحل في المثال التالي، وهو إعادة التوجيه الفوري من المسار الجذر إلى المسار items/.

العنصر redirectTo

لنتمكّن من القيام بإعادة توجيهٍ تلقائيّة من مسارٍ لآخر، سيكون علينا استخدام العنصر redirectTo بدلًا من متحكّم وقالب.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        redirectTo: '/items'
      })
      .when('/items', {
        controller: 'ItemsController',
        templateUrl: '/views/items/index.html'
      });
  });

كما يُمكنك أن ترى في شريط الموضع في المثال السابق، فالمجموعة items يتم عرضها في المسار items/. ومهما كان عدد المرات التي تنقر بها على الرابط Home، فلن تتمكن من الانتقال إلى المسار الجذر / وسيتم دومًا إعادة توجيهك إلى المسار items/. هذا استخدامٌ بسيط للخيار redirectTo. إن كنت تريد تطبيق بعض العمليات على إعادة التوجيه، فيُمكنك توفير تابعٍ بدلًا من مسارٍ نصّيّ.

التابع search

كما ناقشنا في بداية هذا الفصل، تقدّم روابط URL النصّية تمثيلًا لحالة التطبيق (مثل عملية ترشيحٍ للموارد) يُمكن أن يتم حفظها ومشاركتها بسهولة. كيف يُمكننا معالجة نصّ طلب (query) يطلب فقط الحصول على العناصر التي تكون قيمة العنصر color فيها هو لونًا معيّنًا؟

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        controller: 'LocationController',
        template: '<div class="well well-sm" ng-bind="location"></div>\
                   <a ng-href="/items?color=red">Red items</a>'
      })
      .when('/items', {
        controller: 'ItemsController',
        templateUrl: '/views/items/index.html'
      });
  });

في هذا المثال، تحتوي واجهة مسار الجذر الخاص بالتطبيق على رابط التّنقّل الحاوي على نصّ الطلب color=red. يُمكننا جلب قيمة هذا الوسيط بسهولةٍ باستخدام التابع search للخدمة location$.

angular.module('app')
  .controller('ItemsController', function($scope, $location, Item) {
    $scope.location = $location.absUrl();
    $scope.items = Item.query({color: $location.search().color});
  });

عند النّقر على الرابط Red items في المثال السابق، سترى كيف استخدم التابع query (الذي قُمنا بتعريفه سابقًا في الخدمة items) المُرشّح filter للقيام بعملية جلبٍ حسب المثال.

الخدمة routeParams$

يعتمد أسلوب REST نموذجيًّا على إلحاق معرّف المورد الفريد في نهاية المسار بدلًا من وضعه في نصّ الطلب. كيف يُمكننا القيام بمعالجةٍ صحيحة للمسار بحيث يحوي المعرّف الفريد؟ أو بعبارةٍ أدقّ، كيف يُمكننا استخراج المعرّف الفريد 3 من المسار items/3/ على سبيل المثال؟

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

<div class="well well-sm" ng-bind="location"></div>
<p class="lead">Items</p>
<ol>
  <li ng-repeat="item in items">
    <a ng-href="/items/{{item.id}}">
      {{item.name}}
    </a>
  </li>
</ol>

تُقدّم الخدمة routeParams$ وصولًا ملائمًا للعناصر في المسار، حيث يقوم بكشفها (expose) كعناصر مُسمّاة. يُمثّل القسم الأخير من المسار المعرّف الفريد لموردٍ وحيد يتبع أسلوب REST. فمثلًا، يجب أن يعيد المسار items/3/ تمثيلًا للمورد Item بالمعرّف الفريد 3.

في إعدادات التوجيه الخاصة بنا، يُمكننا استخدام البادئة الخاصّة : لتعريف متغيّراتٍ ذات أسماءٍ متغيرّة قابلةٍ للاستخراج من المسار. تمّ الاتّفاق على تسمية الوسيط المعرّف للمورد بالاسم id:، لذا سيكون نصّ المسار هو items/:id/.

angular.module('app')
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        redirectTo: '/items'
      })
      .when('/items', {
        controller: 'ItemsController',
        templateUrl: '/views/items/linked-index.html'
      })
      .when('/items/:id', {
        controller: 'ItemController',
        templateUrl: '/views/items/show.html'
      });
  });

تقوم الوحدة بوضع الوسطاء المستخرجة من المسار في الخدمة routeParams$ التي يجب أن نقوم بحقنها في المتحكّم ItemController جنبًا إلى جنبٍ مع الخدمة Item التي أنشأناها.

angular.module('app')
  .controller('ItemController', function($scope, $location, Item, $routeParams) {
    $scope.location = $location.absUrl();
    $scope.item = Item.get({id: $routeParams.id});
  });

سنقوم باستدعاء التّابع Item.get مستخدمين القيمة التي تمّ إسنادها في routeParams.id$، وهذا التّابع يستخدم المرشّح filter لإيجاد موقع النموذج الصحيح. (يُمكن للتابع ()Array.prototype.find الموجود في المكتبة المركزيّة أن يُقدّم طريقةً أفضل للقيام بذلك عندما ينتشر هذا التابع على نطاقٍ واسع.)

طريقة عرض المورد item المفرد سهلةٌ ومباشرة، سنقوم بتضمين رابطٍ للعودة إلى المسار items/ بحيث نتمكّن من العودة إلى واجهة عرض القائمة.

<div class="well well-sm" ng-bind="location"></div>
<a ng-href="/items">Items</a>
<p class="lead">{{item.name}}</p>
<p>Color: {{item.color}}</p>

صحيحٌ أنّ الخدمة routeParams$ والتابع ()location.search$ كلاهما سهل الاستخدام نوعًا ما، إلّا أنّهما معًا يقدّمان حجر بناءٍ هامًّا في أيّ عمليّة توجيه متضمّنةٍ لأسلوب REST.

خاتمة

تعلّمنا في هذا الفصل أساسيات طريقة Angular الخاصة بها في التوجيه. ورغم أنّ ngRoute لا تُقدّم بعض الميزات المعقّدة للتوجيه، كدعم الموارد المتداخلة، أو آلة الحالة من الدرجة الأولى، أو توليد مسارات URL، إلا أنّها تعطيك مقدّمةً ممتازةً للتوجيه في Angular. ستجد أنّ العديد من التطبيقات الحقيقية تستخدم UI-Router من مشروع AngularUI بدلًا من ذلك، ولن نتطرّق إليها في هذه السلسلة، بل سيكون الفصل الأخير مقدّمةً لتحميل البيانات من المخدّم في النهاية الخلفية (backend) باستخدام الخدمة http$ في Angular.

30يوليو

( تعلم Angular ) – مجالات التوجيه (Directive Scopes) في AngularJS

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

والآن بعد أن اعتدنا على التّوجيهات المخصّصة، فقد حان الوقت لنتعلّم كيفيّة إدارة حالة التّوجيه (directive state) بطريقةٍ مغلّفة، مما يسمح بإعادة استخدامٍ آمنٍ للتّوجيه المخصّص.

عزل المجال

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

عزل المجال يعني فقط إعطاء التّوجيه مجالًا خاصًّا به بحيث لا يكون موروثًا من المجال الحالي.

قبل أن نبدأ، نحتاج إلى الاهتمام بالإعدادات المُعتادة. أوّلًا، لنقُم بإضافة Angular وBootstrap إلى الصّفحة.

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>

لابدّ من تعريف وحدةٍ للتطبيق.

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

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

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

والآن بعد أن أصبحت Angular ووحدة الجذر مُعدّتين جيّدًا، لنقُم بتعريف متحكّمٍ يقوم بالكشف (expose) عن ثلاث عناصر لتخزين سلاسل نصّيّة، وكلّ عنصرٍ سيُخزّن اسم المكوِّن الذي سيستخدمه في العرض. السلسلة النصية الأولى تُعرّف عن المتحكّم نفسه، والسلسلتان النصيتان الباقيتان لنسختين منفصلتين من توجيهٍ سنقوم بتعريفه لاحقًا.

angular.module('app')
  .controller('MessageController', function($scope) {
    $scope.message  = "the controller";
    $scope.message1  = "directive 1";
    $scope.message2  = "directive 2";
  });

وفيما يلي شيفرة التّوجيه، وفيها نقوم أيضًا بإنشاء عنصرٍ اسمه message في نسخة عنصر المجال الذي يتم تمريره إلى تابع الرّبط.

angular.module('app')
  .directive('message', function() {       
    return {
      template: "<p>hello, from {{message}}</p>",
      link: function(scope, element, attrs) {
        scope.message = scope.$eval(attrs.message);
      }  
    };
  });

لقد ناقشنا في الفصل السابق استخدام scope.$eval للوصول إلى عنصرٍ في المجال تمّ تمريره كقيمة نصّيّة لوسيط التّوجيه، وما يهمّنا تحديدًا في هذا المثال هو ما سيحدث للعنصر scope.message الموجود في المتحّكم ونسختي التّوجيه. سنضيف خانات إدخالٍ مرتبطةً بعناصر المجالات الثلاثة التي تمّ إنشاؤها في المتحكّم.

قبل أن تنظر إلى استخدام المثال ومخرجاته، ألقِ نظرةً سريعةً مرّةً أخرى على المتحكّم والتّوجيه السابقين. هل ترى أيّ إمكانيّةٍ للتضارب بينهما؟

<div ng-controller="MessageController">       
  <h4>hello, from {{message}}</h4>
  <input class="form-control" ng-model="message"> 
  <input class="form-control" ng-model="message1"> 
  <input class="form-control" ng-model="message2">
  <br>
  <div class="well" message="message1"></div>     
  <div class="well" message="message2"></div>     
</div>

خلال الوقت الذي أنهى فيه الاستخدام الثّاني للتّوجيه عمله، تمّ إسناد القيمة “2 directive” للعنصر message في مجال المتحكّم. إن قمتَ بكتابة شيءٍ داخل خانة الإدخال العُلويّة، والتي ترتبط بالعنصر message، فسترى أنّ التغيير أدّى إلى تحديث مخرجات نسختي التّوجيه معًا. في هذا المثال، تمّت مشاركة مجال المتحكّم ضمن النّسخ الثّلاثة، مما أدّى إلى حدوث هذه المشكلة، فنحن لا نريد أن تقوم أيّ نسخةٍ من نُسخ التّوجيه بتعديل المجال الذي يُشاركه المتحكّم MessageController مع نُسخ التّوجيه الأخرى. إذًا، كيف يُمكننا إعطاء كلّ نسخةٍ للتّوجيه مجالها الخاصّ المعزول؟ هل يُمكنك اكتشاف الحلّ بنفسك؟

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

angular.module('app')
  .directive('message', function() {       
    return {
      template: "<p>hello, from {{message}}</p>",
      link: function(scope, element, attrs) {
        scope.message = scope.$eval(attrs.message);
      },
      scope: true  
    };
  });

لقد استطعنا حلّ مشكلة الحالة الابتدائيّة لكلّ العناصر. ولكن هل انتهينا؟ كلّا. جرّب الكتابة في أيّ خانةٍ من خانات الدّخل.

إذا أردنا أن تؤدّي التحديثات للعنصرين message1 وmessage2 التّابعين لمجال المتحكّم إلى تحديثات في التّوجيه، فسيكون علينا القيام بأكثر مما قمنا به حتّى الآن، لأنّ تابع الرّبط لا يتمّ تشغيله إلّا مرّةً واحدة، عندما يتمّ إخراج (render) التّوجيه للمرّة الأولى، وعند استخدام eval$ لتغيير قيمة عنصرٍ في التّوجيه فلن يكون هذا التّغيير دائمًا ولن يعمل الرّبط ثنائيّ الاتّجاه بين مُخرجات التّوجيه والنّموذج الأصليّ. إن لم تكن قد جرّبت تغيير القيَم في خانات الدّخل بعد، فأنصحك بشدّةٍ بتجربة ذلك لتتأكّد بنفسك من أنّ القيم لا يتمّ تحديثها.

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

طرق ربط المجال المعزول

يتمّ التصريح عن طرق ربط المجال المعزول باستخدام الرّموز التّالية:

  • الرّمز = للرّبط ثنائيّ الاتّجاه (two-way binding)
  • الرّمز & للرّبط وحيد الاتّجاه (one-way binding)
  • الرّمز @ للرّبط النّصّي (text binding)

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

الربط ثنائي الاتجاه (=)

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

angular.module('app')
  .directive('message', function() {       
    return {
      template: "<p>hello, from {{message}}</p>",
      scope: {
        message: '='
      }
    };
  });

لاحظ بأنّ قيمة العنصر scope أصبحت الآن كائنًا بدلًا من القيمة true البوليانيّة الأوّليّة (primitive). ما قمنا به يؤدّي إلى قطع العلاقة الوراثيّة بين المجالات. ولنتمكّن من رؤية ذلك عمليًّا، يُمكنك استبدال scope: true في المثال الأسبق، بـ {} :scope. لا يعمل تابع الرّبط في ذلك المثال دون الوصول إلى العناصر في المجال الأب.

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

الربط وحيد الاتّجاه (&)

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

angular.module('app')
  .controller('ClickController', function($scope) {
    $scope.message = "Waiting...";
    $scope.kaboom = function() {
      $scope.message = "Kaboom!";
    }
  });

يعرض المثال التالي كيف يُمكن استخدام المتحكّم ClickController، حيث يمكن استخدامه مع التّوجيه العام bootstrap-button الّذي يُمكن أن يتمّ إعداده ليقوم باستدعاء التّابع kaboom عندما يتم نقر الزّر.

<div ng-controller="ClickController">
  <h4>
    {{message}}
  </h4>
  <bootstrap-button the-callback="kaboom()"></bootstrap-button>
</div>

لنقُم الآن بكتابة شيفرة التّوجيه bootstrap-button، في داخل قالبه البسيط، سنستخدم التّوجيه ng-click المدمج في Angular لربط حدث النّقر بتابع الاستدعاء الخلفي.

views/button.html

<button type="button" class="btn btn-default" ng-click="theCallback()">
  Submit
</button>

بما أنّ التّوجيه العام الذي سنكتبه لا يُمكنه معرفة اسم تابع الاستدعاء الخلفيّ الحقيقي الذي سيتم تضمينه، سنستخدم اسمًا عشوائيًّا theCallback كاسمٍ للتّابع. حسنًا، والآن كيف سنقوم بربط theCallback (في الواقع هي عنصرٌ في المجال) مع kaboom؟

ليست هذه بمشكلةٍ، فلدينا الرّبط وحيد الاتّجاه.

angular.module('app')
  .directive('bootstrapButton', function() {
    return {
      restrict: 'E',
      scope: {
        theCallback: '&'
      },
      templateUrl: '/views/button.html'
    }
  });

يقوم الرّمز (&) بعمليّة الرّبط المطلوبة.

والآن ماذا عن خيار الإعداد الأخير، @، المُسمّى بخيار الرّبط النّصّيّ؟ سنحتاج إلى الانتقال مؤقّتًا إلى مثالٍ آخر من المصطلحات الصّعبة في Angular، وذلك كي نتمكّن من تبيان فائدة هذا الخيار بأفضل ما يمكن.

الاستبدال Transclusion

لابدّ من وجود اسمٍ آخر أفضل من Transclusion، فمُستخدموا Ruby on Rails سيلاحظون تشابه آلية عمل الـTransclusion مع yield، وهذا المصطلح أسهل للفهم.

تعني عمليّة الاستبدال transclude لعنصرٍ ما، أن نقوم بالتّحكّم بإعادة إنتاج المكوّن الذي قام بتضمين التّوجيه، وفي حالتنا يكون هذا المكوّن هو القالب الذي يتضمّن التّوجيه. إن لم تكن هذه العبارة واضحةً لك، فيُمكنك التّفكير بأنّ الاستبدال (transclusion) يعطي الأمر التّالي: “قم بقصّ محتويات القالب الموجود داخل شيفرة التّوجيه، ثمّ ألصقها هنا“. ويعتمد مكان اللصق على التّوجيه المدمج ng-transclude الذي يُمكننا وضعها في مكانٍ ما داخل القالب الموجود في التّوجيه المخصّص.

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

<div box>
  This <strong>content</strong> will be <em>wrapped</em>.
</div>

ما سيقوم به التّوجيه box هو تغليف المحتوى الدّاخليّ بعنصر p مع بعض التّنسيق من Bootstrap. هناك جزآن يجب الاهتمام بهما عند استخدام الاستبدال (transclusion)، أوّلهما، يجب أن نقوم بإضافة transclude: true إلى إعدادات التّوجيه، وثانيهما، لابُدّ من استخدام التّوجيه ng-transclude في مكانٍ ما من القالب الخاصّ بالتّوجيه، وذلك لاختيار المكان الذي نريد أن نقوم فيه بإضافة المحتوى الجديد.

angular.module('app')
  .directive('box', function() {
    return {
      template: '<p class="bg-primary text-center" ng-transclude></p>',
      transclude: true
    };
  });

ألم يكن هذا سهلًا؟ بالطّبع.

يُمكننا جعل المثال السابق أسهل من ذلك بعدم استخدام التّوجيه من الأساس، وإضافة تنسيق CSS للعنصر المغلِّف مباشرةً. ولذلك سنقوم باستخدام الاستبدال (transclusion) في مثالٍ أكثر واقعيّة لتغليف التّعقيدات الخاصة بتنسيق Bootstrap لـلوحةٍ مع ترويسة. سيسمح التّوجيه panel لمستخدمه بتحديد النّص الخاص بشريط العنوان في رأس اللوحة، وأيضًا بتحديد المحتوى المطلوب إدخاله في جسم اللوحة. بالنّسبة لمحتوى اللوحة، سيتمّ توظيف الاستبدال (transclusion) تمامًا كما في المثال السّابق، أمّا بالنّسبة لنصّ العنوان، فسنستخدم الخيار المتبقّي لربط المجال المعزول الذي تركناه قبل قليل.

الربط النصي (@)

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

views/panel.html

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">
      {{title}}
      <small>
        Static text in the directive template
      </small>
    </h3>
  </div>
  <div class="panel-body" ng-transclude></div>
</div>

وفي إعدادات التّوجيه الخاصّ بنا، سنقوم بالحدّ من استخدامه للعناصر، وسنصرّح عن طريقة ربط الخاصّيّة title مع العنصر title في مجاله.

angular.module('app')
  .directive('panel', function() {
    return {
      restrict: 'E',
      templateUrl: '/views/panel.html',
      scope: {
        title: '@'
      },
      transclude: true
    };
  });

المثال التّالي يوضّح طريقة استخدام التّوجيه panel لكتابة النّصّين الثّابتين (static) في العنوان، وذلك في الوقت نفسه الذي يتمّ فيه استخدام الرّبط ثنائيّ الاتّجاه في المحتوى المُستبدل (transcluded).

<div ng-controller="MessageController">
  <panel title="Static text in the client template">
    <h3>
      <em>Hello</em>, from <strong>{{message}}</strong>
    </h3>
    <input class="form-control" ng-model="message"> 
  </panel>
</div>

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

خاتمة

بناءً على خبرتك الحاليّة، فقد يكون هذا الفصل هو الأصعب ضمن هذه السلسلة، لذا أُهنّئُك على إنهائك له. يُفترض بك الآن أن تكون قادرًا على تعريف توجيهاتٍ مخصّصة يُمكن استخدامها دون الخوف من التّأثيرات الجانبيّة النّاتجة عن الحالة المشتركة، وهذه خطوةٌ كبيرة في طريقك نحو إكمال سيطرتك على التّوجيهات في Angular. في هذه النّقطة، سنحوّل تركيزنا عن الفروقات الدّقيقة في استخدام التّوجيهات، وسنتوجّه إلى مفهومٍ أساسيٍّ في مجال تطوير الويب من طرف المُستخدم: التسيير (routing) و الـURLs.

30يوليو

( تعلم Angular ) – التوجيهات (Directives) في AngularJS

لَعِبت التّوجيهات المبنيّة داخل Angular مثل ng-bind وng-model وinput دور النّجوميّة في هذه السلسلة منذ الفصل الأوّل، المبادئ، حتّى أنّ فصل المجموعات كان يركّز كلّيًّا على استخدام توجيهٍ واحدٍ (قويٍّ إلى حدّ كبير) هو التّوجيه ng-repeat. قد تظنّ بعد ذلك أن قائمة التّوجيهات المبنيّة في داخل Angular تحوي حلولًا لجميع الحالات، ولكنّ هذا غير صحيحٍ بالطّبع. لقد تصوّر مصمّموا Angular بأنّها ستكون طريقةً للسّماح لغير المبرمجين ببناء صفحاتٍ تفاعليّة، إلّا أنّها تطوّرت لتصبح منصّةً تعطي مطوّري الويب القوّة لإنشاء امتداداتٍ وتخصيص HTML العاديّة، وهذا يعني إنشاء التّوجيهات الخاصّة بك، وهذا تمامًا ما سنقوم به في هذا الفصل.

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

التابع directive

نستخدم التّابع directive لتسجيل توجيهٍ مخصّص، وهذا التّابع ذو الاسم المناسب لوظيفته جزءٌ من الواجهة البرمجيّة للوحدات، لذا سنحتاج إلى إنشاء وحدةٍ جذرٍ لتطبيقنا.

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

ثمّ نقوم بتحميل الوحدة الجذر عن طريق تمرير اسم التّطبيق إلى التّوجيه ng-app.

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

وبذلك أصبحنا جاهزين لإنشاء توجيه مخصّص، ولنبدأ بتوجيهٍ بسيطٍ جدًّا اسمه hello.

angular.module('app')
  .directive('hello', function() {
    return function(scope, element) {
      element.text("hello");
    };
  });

للقيام بأيّ شيءٍ مفيدٍ سنحتاج إلى استخدام الوسيط الثّاني لتابع الرّبط، والّذي يكون غلافًا (wrapper) لعنصرٍ من المستند نجلبه من مكتبة jqLite الخاصّة بـAngular أو من مكتبة jQuery حسب الوسيط الذي قمت بتضمينه. في هذا المثال، قمنا باستبدال المحتوى السّابق للعنصر بالعبارة hello.

The message is <span hello></span>.

الناتج:

The message is hello.

أليس هذا رائعًا؟ لقد أنشأنا لتوّنا خاصّيّة HTML جديدة خاصّةً بنا.

الوسيط scope

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

angular.module('app')
  .controller('MessageController', function($scope) {
    $scope.message = 'hello';
  });

والآن لنقم باستخدام هذا العنصر في التّوجيه، وسنُسمّيه message أيضًا.

angular.module('app')
  .directive('message', function() {
    return function(scope, element) {
      element.text(scope.message);
    };
  });

من الواضح أنّه يتوجّب علينا استخدام هذا التّوجيه في المكان داخل المستند الذي يكون تابعًا للمتحكّم MessageController.

<p ng-controller="MessageController">
  The message is <span message></span>.
</p>

الناتج:

The message is hello.

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

الوسطاء

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

angular.module('app')
  .directive('welcomeMessage', function() {
    return function(scope, element, attrs) {
      element.text(attrs.welcomeMessage);
    };
  });
The message is <em welcome-message="bonjour"></em>.

الناتج:

The message is bonjour.

لاحظ أنّه عندما نستخدم أكثر من كلمةٍ واحدةٍ في كتابة اسم التّوجيه بأنّنا نستخدم نمط camel case حيث نكتب أوّل حرفٍ في الكلمة الثّانية بحرفٍ كبير، وهذا هو النّمط المتعارف عليه في JavaScript، ولكنّ النمط المستخدم في HTML هو فصل الكلمات باستخدام إشارة (-) وهو ما يُدعى hyphenated style، وخلاصة الكلام هي أنّك عندما تريد استخدام welcome-message لتضمين التّوجيه الخاص بك في القالب، سيكون عليك استخدام الاسم welcomeMessage عندما تقوم بتعريفها في JavaScript.

قد تكون لاحظت بأنّ بعض التّوجيهات المدمجة في Angular تقوم بمعالجة (evaluate) العبارات، إلّا أنّ هذا السلوك ليس هو السلوك الافتراضي.

The message is <em welcome-message="'bon' + 'jour'"></em>.

الناتج:

The message is 'bon' + 'jour'.

بما أنّنا نعمل داخل قالب Angular، سيكون بإمكاننا دومًا تمرير نتيجة عبارةٍ معالجَة (evaluated).

The message is <em welcome-message="{{'bon' + 'jour'}}"></em>.

الناتج:

The message is bonjour.

يُمكنك أيضًا إدخال سلسلةٍ نصّيّة معرّفة مسبقًا داخل السلسلة النّصّية العاديّة.

<p ng-init="greeting = 'bonjour'">
  The message is <span welcome-message="I say {{greeting}}, you say hello"></span>.
</p>

الناتج:

The message is I say bonjour, you say hello.

لتتمكّن من معالجة العبارة الممرّرة إلى التّوجيه كسلسلةٍ نظاميّة، ستحتاج إلى استخدام التّابع eval$.

التّابع scope.$eval

بالرغم من أنّ Angular لا تعتبر أنّ الوسيط المُمرّر إلى التّوجيه عبارةٌ تحتاج إلى معالجة، إلّا أنّه يمكننا ببساطةٍ أن نعالج هذه العبارة باستخدام التّابع eval$، ولأخذ العلم، فهذه الطّريقة لا تؤدّي إلى إعادة تحديث العرض (view) عند تغيُّر النّموذج، وهذا مفيدٌ فقط في حالات الاستخدام الأساسيّة.

angular.module('app')
  .directive('welcomeMessage', function() {
    return function(scope, element, attrs) {
      var result = scope.$eval(attrs.welcomeMessage);
      element.text(result);
    };
  });

لنقم بتجربة حالة استخدامٍ بسيطة نقوم فيها بوصل سلسلتين نصّيّتين.

The message is <em welcome-message="'bon' + 'jour'"></em>.

الناتج:

The message is bonjour.

يُمكننا أيضًا باستخدام scope.$eval أن نقوم بتمرير كائنات، مّما يتيح لنا طريقةً لتلقّي العديد من الوسطاء معًا، وسنلقي نظرةً في فقرةٍ لاحقةٍ من هذا الفصل علىتوجيهات العناصر، والتي تقبل أيضًا العديد من الوسطاء المُسمّاة عن طريقة إتاحة وجود العديد من الخصائص للعنصر، أمّا الآن فسنرى في المثال التالي معالجةً لكائنٍ من نوع options، حيث نقوم بتمرير العديد من الوسطاء إلى التّوجيه.

angular.module('app')
  .directive('welcomeMessage', function() {
    return function(scope, element, attrs) {
      var options = scope.$eval(attrs.welcomeMessage);
      var result = options.emoticon + ' ' + options.message + ' ' + options.emoticon;
      element.text(result);
    };
  });

وبهذه الطّريقة استطعنا إدخال وسيطين بفعّاليّة، message وemoticon، واستطعنا إنجاز الوصل بينهما داخل التّوجيه.

The message is <em welcome-message="{message: 'bonjour', emoticon: '\u263a'}"></em>.

الناتج:

The message is  bonjour ☺.

صِرنا الآن نفهم كيفيّة استخدام توجيهاتٍ مخصّصةٍ مع المجالات والمتحكّمات. ولكن ماذا لو أردنا استخدام خدماتٍ أخرى داخل التّوجيه؟

حقن التبعية

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

على سبيل المثال، لنفترض بأنّنا نريد توجيهًا يُمكنه تطبيق حسمٍ على السّعر وأيضًا القيام بتنسيقٍ للعملة لقيمة المتغيّر price في المجال. سيحتاج هذا التّوجيه للوصول إلى الخدمة المخصّصة calculateDiscount المُعرّفة كما يلي.

angular.module('app')
  .value('discountRate', 0.8)
  .factory('calculateDiscount', function(discountRate) {
    return function(amount) {
      return amount * discountRate;
    };
  });

والآن سنستخدم تابع الوصفة (recipe) التّغليفيّ لحقن الخدمة calculateDiscount مع المُرشّح currency أيضًا.

angular.module('app')
  .directive('discount', function(calculateDiscount, currencyFilter) {
    return function(scope, element, attrs) {
      var price = scope.$eval(attrs.discount);
      var discountPrice = calculateDiscount(price);
      element.html(currencyFilter(discountPrice));
    };
  });

لاحظ بأنّنا قمنا بتمرير العنصر price كقيمةٍ لوسيط التّوجيه في القالب، ويُمكننا الحصول على هذه القيمة للوسيط عن طريق الوسيط attrs حيث نصل إلى الخاصّيّة (discount) وهي اسم التّوجيه الخاصّ بنا. في المثال التّالي، سنقوم بإسناد قيمة هذا العنصر في المجال (الّتي نحصل عليها عن طريق scope.$eval) إلى المتغيّر المحلّي المُسمّى price.

<table ng-init="price = 100">
  <tr>
    <td>Full price:</td>
    <td ng-bind="price | currency"></td>
  </tr>
  <tr>
    <td>Sale price:</td>
    <td discount="price"></td>
  </tr>
</table>

الناتج:

Full price:	$100.00
Sale price:	$80.00

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

القوالب

كُلّ العمليّات التي قمنا بتنفيذها في الأمثلة السّابقة على الوسيط element كانت سهلةً إلى حدٍّ ما، ولكن ماذا لو أردنا إضافة قطعةٍ متغيّرة الحجم من محتوىً جديدٍ إلى المستند؟

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

The message is <span strong-message="a strong hello!"></span>

يُمكننا دومًا استخدام عمليّة وصل السّلاسل (concatenation).

angular.module('app')
  .directive('strongMessage', function() {
    return function(scope, element, attrs) {
      element.html('<strong>' + attrs.strongMessage + '</strong>');
    };
  });

الناتج:

The message is a strong hello!

أو يُمكننا بناء المحتوى بطريقةٍ برمجيّة باستخدام jqLite أو jQuery.

angular.module('app')
  .directive('strongMessage', function() {
    return function(scope, element, attrs) {
      element.html('<strong>');
      element.find('strong').text(attrs.strongMessage);
    };
  });

الناتج:

The message is a strong hello!

عمليّة وصل السّلاسل والتّغييرات البرمجيّة على المستند طريقةٌ جيّدةٌ عند استخدامها للأمور الصّغيرة كعنصرٍ واحدٍ مثلًا، ولكن ماذا سنفعل عندما يكون المحتوى يتضمّن العديد من العناصر المتداخلة؟ أو عندما يكون لدينا قائمةٌ متغيّرة الحجم؟ سيجيبك عن هذا السّؤال أيّ خبيرٍ في تطوير النهاية الأماميّة (front-end) بأنّ مكتبة القوالب ستسهّل الأمر كثيرًا، ولحسن الحظّ، فـAngular مكتبة قوالب، وستجعل الأمر أكثر سهولة.

يُمكن لـAngular أن تقوم بإخراج (render) قوالب متداخلةٍ يقدّمها التّوجيه الخاصّ بنا، وذلك في الوقت نفسه الّذي تقوم فيه بإخراج قالب التّطبيق الخاصّ بنا.

لإضافة قالبٍ للتّوجيه، سنحتاج إلى تغيير أسلوبنا السّابق، فبدلًا من إعادة (return) تابع الرّبط فقط، سنقوم بإعادة كائنٍ يحوي تابع الربط ومعه قالب. هذا تغيير كبير، لذا فلنكرّر ذلك: سيقوم المُغلّف الوصفيّ (recipe) الآن بإعادة كائنٍ بدلًا من تابع، وسنستخدم العنصر link في هذا الكائن المُعاد للوصول إلى التّابع.

فيما يلي مثالٌ يستخدم قالبًا لإنشاء عددٍ غير محدّدٍ مسبقًا من عناصر li.

angular.module('app')
  .directive('wordList', function() {
    return {
      link: function(scope, element, attrs) {
        scope.words = attrs.wordList.split(" ");
      },
      template: "<li ng-repeat='word in words'>\
                   {{word}}\
                 </li>"
    };
  });

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

<ul word-list="we love templates"></ul>

الناتج:

. we
. love
. templates

أظنّ بأن المثال بدأ يوضّح لك قوّة وأناقة التّوجيهات المخصّصة.

العنصر templateUrl

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

<li ng-repeat="word in words">
  {{word}}
</li>

لقد قمنا باستخراج شيفرة القالب إلى مورد ويب موجودٍ في الرابط النّسبيّ views/word-list.html، والآن سنقوم بإضافة العنصر templateUrl إلى كائن الإعدادات الذي يعيده التّوجيه الخاص بنا، وسنقوم بإسناد مسار القالب إليه، وذلك بدلًا من استخدام العنصر template وإسناد شيفرة القالب إليه مباشرةً.

angular.module('app')
  .directive('wordList', function() {
    return {
      link: function(scope, element, attrs) {
        scope.words = attrs.wordList.split(" ");
      },
      templateUrl: '/views/word-list.html'
    };
  });

أمّا الاستخدام فيبقى كما هو دون تغيير.

 

<ul word-list="external templates rock"></ul>

الناتج:

. external
. templates
. rock

القوالب الخارجيّة عمومًا أسهلُ بكثيرٍ للقراءة وللتّعديل من القوالب السّطريّة.

العنصر replace

لا بُدّ من استخدام التّوجيه في المثال السابق ضمن قائمة، ولكن ماذا لو لم يقم المستخدم باستخدام التّوجيه بشكلٍ صحيح؟ أحد الحلول هو استبدال العنصر الذي يتضمّن التّوجيه بدلًا من إلحاق العناصر به. إذًا لنقُم بإضافة عنصر ul لتغليف مخرجات قالبنا، سنحتاج أيضًا إلى عنصرٍ جديدٍ في كائن الإعدادات هو العنصر replace، وسنُسند إليه القيمة true. (القيمة الافتراضيّة له هي بالطّبع false.)

لنقم أوّلًا بإضافة العنصر ul إلى قالبنا.

<ul>
  <li ng-bind="word" ng-repeat="word in words"></li>
</ul>

التّغيير الوحيد الهامّ في التّوجيه هو استخدام العنصر الجديد replace.

angular.module('app')
  .directive('wordList', function() {
    return {
      link: function(scope, element, attrs) {
        scope.words = attrs.wordList.split(" ");
      },
      templateUrl: '/views/word-list-ul.html',
      replace: true
    };
  });

بما أنّ العنصر الذي يتضمّن التّوجيه سيتمّ استبداله مهما كان، يُمكننا تضمين التّوجيه في عنصرٍ غريبٍ وغير ملائمٍ مثل h1.

 

<h1 word-list="lists not headlines"></h1>

الناتج:

. lists
. not
. headlines

يبيّن المثال السّابق قوّة تأثير التّوجيهات على المستند، ولكنّني أُفضّل أن يكون التّصميم أكثر وضوحًا، ودلالته صحيحة، ولذلك فبدلًا من استبدال وسوم HTML، أرى أنّ الصّواب هو التّحقّق من صحّة العنصر الذي قام بتضمين التّوجيه، وإن لم يكن صحيحًا يتمّ إلقاء خطأ (throw an error).

العنصر controller

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

angular.module('app')
  .controller('MessageController', function($scope) {
    $scope.message = 'hello, from the external controller';
  })
  .directive('message', function() {
    return {
      template: "<strong>{{message}}</strong>"
    };
  });

بما أنّ المتحكّم والتّوجيه منفصلان، فكلاهما يحتاج إلى التّضمين بشكلٍ منفصلٍ في المثال التالي. يُمكننا أن نضع المتحكّم في نفس العنصر الذي يحوي التّوجيه أو في عنصرٍ مغلّف، فلا فرق.

The message is <span message ng-controller="MessageController"></span>.

الناتج:

The message is hello, from the external controller.

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

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

angular.module('app')
  .directive('message', function() {
    return {
      template: "<strong>{{message}}</strong>",
      controller: function($scope) {
        $scope.message = 'hello, from the internal controller';
      }
    };
  });

أليس هذا رائعًا؟ لم نحصل الآن على مكوِّنٍ مغلّفٍ جيّدًا وحسب، بل تخلّصنا الآن من تضمين المتحكّم واستخدام التّوجيه ng-controller في الوسم.

The message is <span message></span>.

الناتج:

The message is hello, from the internal controller.

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

الخيار restrict

تُوجد أربع طرقٍ لتضمين توجيهٍ ما، إلّا أنّنا لم نرَ حتّى الآن إلّا طريقة تضمين التّوجيهات كخصائص لعناصر HTML، وذلك لأنّه الأسلوب الأكثر شيوعًا وفائدةً للقيام بذلك، وهي أيضًا القيمة الافتراضيّة للخيار restrict الذي يُمكن أن يملك واحدةً أو أكثر من الخيارات التّالية:

  • A‘ يتمّ تضمينه كخاصّيّة (Attribute)
  • C‘ يتمّ تضمينه كفئة (Class)
  • E‘ يتمّ تضمينه كعنصر (Element)
  • M‘ يتمّ تضمينه كتعليق (Comment)

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

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

في المثال التّالي لن يكون لدينا هذا العدد الكبير من الوسوم إلّا أنّنا سنصمّم جدولًا مع كلّ الزخرفات الخاصة به.

<table class="table table-condensed">
  <thead>
    <tr>
      <th ng-bind="column" ng-repeat="column in columns"></th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="model in models">
      <td ng-bind="model[column]" ng-repeat="column in columns"></td>
    </tr>
  </tbody>
</table>

سيستخدم تابع الرّبط الآن وسيطين في تعريف التّوجيه الخاصّ بنا، وسنصل إلى هذا الوسيطين عن طريق attrs.models و attrs.columns.

angular.module('app')
  .directive('modelsTable', function() {
    return {
      restrict: 'E',
      templateUrl: '/views/models-table.html',
      link: function(scope, element, attrs) {
        scope.models = scope.$eval(attrs.models);
        scope.columns = attrs.columns.split(",");
      }
    };
  });

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

سنقوم مرّةً أخرى باستخدام متحكّمٍ خارجيّ في هذا المثال.

angular.module('app')
  .controller('ItemsController', function($scope) {
    $scope.items = [
      {name: 'Item 1', color: 'green', price: 5.0},
      {name: 'Item 2', color: 'blue', price: 4.93}
    ];
  });

وأخيرًا، سنقوم بإضافة Bootstrap إلى صفحتنا لجعل مثالنا الأخير أكثر أناقة.

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>

الخطوة الأخيرة الآن هي تضمين عنصر التّوجيه الخاصّ بنا مع وسيطيه models وcolumns حيث سنضيفهما على شكل خصائص للوسم.

<p ng-controller="ItemsController">
  <models-table models="items" columns="name,color,price"></models-table>
</p>

رائع، لقد قمنا بإخفاء الشيفرات المزعجة الخاصّة بجدول HTML بإحكام.

خاتمة

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

لا يزال علينا فهم أمورٍ أخرى بخصوص التّوجيهات، خصوصًا عندما تدخل مشكلة الحالة المشتركة إلى السّاحة. سيعرض الفصل القادم هذه السيناريوهات المتقدّمة، فإن كنت تشعر بأنك حصلت على المعرفة الكافية لخدمة أهدافك، فيُمكنك القفز مباشرةً إلى فصل HTTP. أما إن كنت ترغب في تعلُّم المزيد، فتابع مباشرةً إلى الجزء الثّاني من فصل التّوجيهات.

30يوليو

( تعلم Angular ) – المرشحات (filters) في AngularJS

تَستخدم المُرشّحات في Angular نمط خطّ الأنابيب (pipeline) بشكلٍ مشابه للـUnix shell، فقد استعارت رمز الخطّ العمودي المشابه لرمز الأنبوب من Unix لتستخدمه لتمرير عبارات التّهيئة أو خواص المجال للمُرشّح وأيضًا لربط المُرشّحات معًا.

سنقوم بتحميل إطار عمل Bootstrap الخاصّ بـCSS لأننا سنقوم بإنشاء بعض الجداول في أمثلة هذا الفصل ممّا سيجعلها أكثر ترتيبًا، وسنجلبه من شبكة توصيل المحتوى (CDN) كما تُبيّن الشّيفرة التّالية.

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>

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

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

سنقوم بإسناد اسم وحدة الجذر لتطبيقنا إلى التّوجيه ng-app.

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

مفهوم المُرشّحات سهلُ التّعلّم، لذا سيكون هذا الفصل مرورًا سريعًا على المُرشّحات المبنيّة داخل Angular، ثُمّ سنستعرض كيف يمكننا إنشاء المُرشّحات الخاصّة بنا.

lowercase و uppercase

لنبدأ جولتنا مع المُرشّحات المبنيّة في Angular ببعض التّحويلات على السّلاسل النّصية، والمُرشّح lowercase بدايةٌ ممتازة. نبدأ بكتابة أيّ عبارة (في مثالنا استخدمنا عبارة “AngularJS”)، ثم نضع رمز الأنبوب (|)، وبعده نكتب اسم المُرشّح (lowercase).

<p>{{"AngularJS" | lowercase}}</p>

الناتج:

angularjs

يُمكننا تطبيق المرشّح uppercase على النّتيجة عن طريق إضافة رمز أنبوبٍ آخر، ثمّ اسم المرشّح، وهذا ما نُسمّيه بسلسلة المُرشّحات.

<p>{{"AngularJS" | lowercase | uppercase }}</p>

الناتج:

ANGULARJS

بالطّبع ليس هناك فائدةٌ من وضع lowercase قبل uppercase، وسنرى في نهاية هذا الفصل مثالًا أفضل على سَلسَلة المُرشّحات، ولكن على الأقل رأينا كيف نقوم بذلك الآن.

number

المُرشّح التّالي سهلٌ أيضًا، المُرشّح number يُقدّم المساعدة الأساسيّة لتنسيق الأعداد وتقريبها.

<p>{{1000.12345 | number}}</p>

الناتج:

1,000.123

بالنّسبة لإعداداتي المحلّيّة، en-us، المُخرجات في المثال هي 1,000.123، يقوم المُرشّح بتقريب العدد المُمرّر إليه إلى 3 خاناتٍ بعد الفاصلة، ومن الواضح أنّ هذا لن يناسب جميع الاستخدامات، ولحسن الحظ، يمكننا تمرير وسطاء إلى المرشّحات التي تقبل القيام بذلك، والمُرشّح number يقبل ذلك. الوسيط الأول الذي نقوم بتمريره هو القيمة التي نريد تطبيق المُرشّح عليها، أما الوسيط الإضافي الثّاني فيجب أن يتبع اسم المُرشّح ويكون مفصولًا عنه بنقطتين (:).

الوسيط الإضافيّ الثّاني للمُرشّح number هو fractionSize، وهو يسمح لنا بالتّحكّم بعدد المنازل التي يتم عرضها بعد الفاصلة، وبهذا يمكننا رؤية القيمة كاملةً.

<p>{{1000.12345 | number:5}}</p>

الناتج:

1,000.12345

ماذا يحدث لو قمنا بتمرير قيمةٍ سالبةٍ إلى الوسيط fractionSize؟ استبدل 5 بـ 3- ثمّ جرّب 4-، هل يُمكنك تخمين السّبب؟ لا مشكلة، حتى أنا لا يمكنني ذلك.

currency

المُرشّح التّالي مفيدٌ بعض الشّيء في التّطبيقات العمليّة: يُقدّم المُرشّح currency الدّعم الأساسيّ لتنسيق العملة وتقريب قيمة العدد.

<p>{{1999.995 | currency}}</p>

الناتج:

$2,000.00

وهو يقبل وسيطين إضافيين، أوّلهما هو رمز العملة، استبدل currency بـ’€’:currency في المثال السّابق لتجريبه.

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

date

سيبيّن المُرشّح date لك إلى أيّ مدىً تذهب بك إعدادات المُرشّحات.

لا تسمح Angular بتهيئة كائنٍ من نوع Date داخل عبارة، لذا سنستخدم متحكّمًا لتحضير النّموذج الخاصّ بالمثال.

angular.module('app')
  .controller('DateController', function($scope) {
    $scope.now = Date.now();
  });

يبدأ المثال التّالي بعنصرٍ في المجال اسمه now، بدلًا من كتابة عبارة.

<p ng-controller="DateController">
{{now | date}}
</p>

الناتج:

Oct 28, 2015

إن قمت بإزالة date | من المثال السّابق سترى القيمة الأصليّة للعنصر now، وهي عدد الميللي ثانية منذ شهر كانون الثّاني عام 1970 حسب التوقيت العالميّ UTC. والآن قم بتجربة بعض التّنسيقات المُعرّفة سابقًا، والتي يدعمها هذا المُرشّح، قم بتجربة ‘date:’medium | ثمّ ‘date:’shortTime | وبعدها’date:’yyyy-MM-dd HH:mm:ss Z |، وجرّب بعض التّعديلات من عندك بعد ذلك. يُسمح بكتابة السّلاسل المحرفية داخل نمط التّنسيق، مثلًا “date:”MMMM ‘of the year’ yyyy |، وبدلًا من استخدام محارف الهروب مثل % لوضع رموز التّنسيق، سيكون عليك وضع السّلاسل المحرفية بين علامتَي تنصّيّصٍ مفردة (‘) وأن تكون عبارة التّنسيق الكاملة بين علامتَي تنصّيّصٍ مزدوجة (“). كما أنّ الفراغات في عبارة التّنسيق تدخل في تنسيق العبارة.

orderBy

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

لنفترض أنّ خدمةً في النهاية الخلفية (backend) أَرجعت سجلًّا مرتًبًا بطريقةٍ غير مرغوبةٍ لأسباب تتعلّق بالعرض. (لتبسيط المثال، ستكون البيانات مكتوبةً يدويًّا داخل الخدمة items التي تراها أدناه، وذلك بدلًا من التّعامل مع نهايةٍ خلفيّة بعيدة حقيقيّة.)

angular.module('app')
  .value('items', [
    {name: 'Item 3', position: 3, color: 'red', price: 2.75},
    {name: 'Item 1', position: 1, color: 'red', price: 0.92},
    {name: 'Item 4', position: 4, color: 'blue', price: 3.09},
    {name: 'Item 2', position: 2, color: 'red', price: 1.95}
  ]);

سنقوم بعد ذلك بحقن المصفوفة items في داخل متحكّم.

angular.module('app')
  .controller('ItemsController', function($scope, items) {
    $scope.items = items;
  });

بناءً على ترتيب العناصر الحالي في المصفوفة، كيف يمكننا ترتيب العناصر حسب قيمة العنصر position؟

إن كنتَ قد قفزتَ من مقعدك الآن وصرخت قائلًا “Underscore” (أو “lodash“)، فتستحقّ منّي وسام الوفاء لـJavaScript، ولكنّها ليست الإجابة التي نبحث عنها، فـAngular تملك مُرشّحًا للقيام بذلك، إنّه المُرشّح orderBy.

<ol ng-controller="ItemsController">
  <li ng-repeat="item in items | orderBy:'position'">
     {{item.name}}
  </li>
</ol>

الناتج:

1. Item 1
2. Item 2
3. Item 3
4. Item 4

هذا رائع، ولكن ماذا لو أردنا أن يكون التّرتيب بالجهة المعاكسة؟ في هذا المثال البسيط يمكننا القيام بذلك بطريقتين، الأولى هي إضافة الرمز – قبل اسم العنصر، أي أن نكتب ‘orderBy:’-position بدلًا من ‘orderBy:’position. (يمكنك أيضًا إضافة الرمز + لاختيار التّرتيب التّصاعدي، إلا أنّه لا يؤثّر على مثالنا السّابق.) أمّا الطّريقة الثّانية فهي إعطاء قيمةٍ للوسيط البولياني reverse بعد اسم العنصر المطلوب التّرتيبُ حسب قيمته. في مثالنا، قم بكتابة orderBy:’position’:true بدلًا من ‘orderBy:’position.

فائدةٌ إضافيّة: عدّل سجلّ البيانات في المتحكّم بحيث تجعل عنصرين في المصفوفة لهما القيمة نفسها بالنّسبة للعنصر position، ألا يزال بإمكانك استخدام المُرشّحorderBy لترتيب العناصر بشكلٍ صحيح؟ ماهي الخاصّيّة التي استخدمتها؟

انتبه للتحديثات

بفضل طريقة Angular في الربط ثنائيّ الاتّجاه، سيكون من السّهل جدًّا أن تجعل النّماذج في قائمة أو جدول العرض قابلةً للتّعديل، كلّ ما تحتاجه هو إضافة خانة إدخالٍ داخل الحلقة مع إسناد اسم عنصر المجموعة إلى التّوجيه ng-model. ولكن انتبه، فبما أنّ Angular جاهزةٌ دومًا وتنتظر إعادة توليد العرض عند أيّ تغيير في الحالة، فقد يتمّ إعادة ترتيب قائمتك قبل أن تنتهي من تعديل العنصر حتّى.

<table class="table table-condensed" ng-controller="ItemsController">
  <tr ng-repeat="item in items | orderBy:'name'">
    <td ng-bind="item.name"></td>
    <td><input type="text" ng-model="item.name"></td>
  </tr>
</table>

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

limitTo

المُرشّح limitTo مفيدٌ عندما ترغب بعرض مجموعةٍ جزئيّةٍ فقط من مجموعةٍ مرتّبة. مثلًا، يمكننا استخدامها بالتّعاون مع المُرشّح orderBy لنعرض فقط أغلى عنصرين في المجموعة.

<ol ng-controller="ItemsController">
  <li ng-repeat="item in items | orderBy:'-price' | limitTo:2">
     {{item.price | currency}}
  </li>
</ol>

الناتج:

1. $3.09
2. $2.75

لا تُقدّم Angular طريقةً لتمرير وسيط offset إلى المُرشّح limitTo حاليًّا. ولتقديم حلٍّ كاملٍ للتصحيف (pagination) سيتضمّن ذلك استخدام التّابع slice داخل المتحكّم.

filter

اسم هذا المُرشّح ظريفٌ نوعًا ما، فهو مرشّحٌ اسمه filter، ربّما لم يكن من المناسب تسمية المُرشّحات بهذا الاسم العام (ربّما يكون الاسم مزخرِفات أو مساعِدات أفضل)، إلّا أنّ هذا المرشّح يقوم فعلًا بالتّرشيح. فهو يقوم بإعادة مصفوفةٍ تحوي فقط على العناصر التي تطابق الشرط المُسند. لنرى ما هي العناصر التي ستطابق وضعنا لشرطٍ يقول بأنّ القيمة العدديّة تساوي 3.

<table class="table table-condensed" ng-controller="ItemsController">
  <thead>
    <tr><th>name</th><th>color</th><th>price</th></tr>
  <thead>
  <tbody>
    <tr ng-repeat="item in items | filter:3">
      <td ng-bind="item.name"></td>
      <td ng-bind="item.color"></td>
      <td ng-bind="item.price | currency"></td>
    </tr>
  <tbody>
</table>

لقد حصلنا على تطابقات في كلا العنصرين name وprice. ماذا سيحدث لو استبدلنا الشرط 3 بالسّلسلة النّصية ‘3’ أو بسلسلة نصّيّة خالية؟ أو لو أضفنا فراغًا في البداية (‘3 ‘)؟

لتجنُّب مطابقة أيّ عنصرٍ، يمكنك تمرير كائنٍ باعتباره شرط التّطابق.

<table class="table table-condensed" ng-controller="ItemsController">
  <thead>
    <tr><th>name</th><th>color</th><th>price</th></tr>
  <thead>
  <tbody>
    <tr ng-repeat="item in items | filter:{price: 2, color: 'red'}">
      <td ng-bind="item.name"></td>
      <td ng-bind="item.color"></td>
      <td ng-bind="item.price | currency"></td>
    </tr>
  <tbody>
</table>

في المثال السّابق، لم يتمّ مطابقة 2 مع item 2 لأننا حدّدنا اسم عنصر التّطابق بأنّه price.

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

Filter: <input type="text" ng-model="q">
<table class="table table-condensed" ng-controller="ItemsController">
  <thead>
    <tr><th>name</th><th>color</th><th>price</th></tr>
  <thead>
  <tbody>
    <tr ng-repeat="item in items | filter:q">
      <td ng-bind="item.name"></td>
      <td ng-bind="item.color"></td>
      <td ng-bind="item.price | currency"></td>
    </tr>
  <tbody>
</table>

إن كان هناك مثالٌ واحدٌ يبيّن بأفضل طريقةٍ مرونة وقوّة امتدادات HTML التي توفّرها Angular، فقد يكون هذا هو.

استخدام المرشحات في JavaScript

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

<p ng-controller="ItemsController">
  <!-- Invalid code! -->
  {{items | filter:{color:'red'}.length}} red items
</p>

الناتج:

[{"name":"Item 3","position":3,"color":"red","price":2.75},{"name":"Item 1","position":1,"color":"red","price":0.92},{"name":"Item 4","position":4,"color":"blue","price":3.09},{"name":"Item 2","position":2,"color":"red","price":1.95}] red items

يُمكنك حقن المُرشّحات أينما أردت، كما أنّه ليس من الغريب استخدام المُرشّح filter للتعامل مع مصفوفةٍ داخل متحكّم.

يكون اسم التّابع الذي يقوم بالترشيح من الشكل filtername>Filter>. مثلًا لاستخدام المُرشّح date نقوم بحقن dateFilter، لاستخدام المُرشّح currency نقوم بحقن currencyFilter، ولاستخدام المُرشّح filter نقوم بحقن filterFilter. (أعرف ذلك، أعرف ذلك)

angular.module('app')
  .controller('FilteredItemsController', function($scope, items, filterFilter) {
    $scope.redItemsCount = filterFilter(items, {color: 'red'}).length;
  });
<p ng-controller="FilteredItemsController">
  {{redItemsCount}} red items
</p>

الناتج:

3 red items

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

مرشح مخصص

المُرشّح المخصّص هو أفضل مكانٍ لتضع فيه الشيفرات التي تقوم بتحويل بيانات النّموذج للعرض، وذلك لأنّ المُرشّح نموذجيًّا مكوِّنٌ ثابت الحالة (stateless) ويُمكن استخدامه في أيّ مكانٍ داخل القوالب دون خوفٍ من أيّ تأثيراتٍ جانبيّة، كما أنّ إنشاء مُرشّح مخصّص ليس صعبًا، فبشكلٍ مشابهٍ لبقيّة المكوّنات في Angular، نقوم بتسجيل وصفةٍ للمُرشّح داخل الوحدة، باستخدام التّابع filter.

angular.module('app')
  .filter('smiley', function() {
    return function(text) {
      return '\u263A ' + text + ' \u263A';
    };
  });

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

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

<strong ng-bind="'hello' | smiley"></strong>

الناتج:

 hello 

إليك هذا التّحدّي: هل يمكنك إعادة تشكيل المُرشّح بحيث يصبح بإمكاننا تمرير رمز الوجه الضّاحك كوسيط؟

حقن التبعية

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

يسمح تابع الوصفة الإنشائيّة الخارجيّ بالتّصريح عن التّبعيّات تمامًا كما نقوم بذلك في الوحدات. فلنلقِ نظرةً على الكيفيّة التي نقوم بها بحقن التّبعيّات في داخل مُرشّح مخصّص للمحلّيّات (localization). سنقوم بتسمية المُرشّح بالاسم localize، ولكن قبل ذلك نحتاج إلى شيءٍ ما لنقوم بالحقن. يمكننا إنشاء جدول لمحتوى المتغيّر locales، حيث يتمّ تعريفها كخدمةٍ قابلةٍ للحقن باستخدام التّابع value.

angular.module('app')
  .value('locales', {
    'de': {greeting: 'guten Tag'},
    'en-us': {greeting: 'howdy'},
    'fr': {greeting: 'bonjour'}
  });

يمكننا حقن هذه البيانات في داخل مُرشّحنا البسيط المخصّص بالتّصريح عن وسيطٍ اسمه locales، ولنتمكّن من الحصول على الموضع الحاليّ للمستخدم، يُمكننا استخدام الخدمة locale$ المبنيّة داخل Angular .

angular.module('app')
  .filter('localize', function(locales, $locale) {
    return function(key) {
      var locale = locales[$locale.id] || locales['en-us'];
      return locale[key];
    };
  });

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

<p>
  The app says <strong ng-bind="'greeting' | localize"></strong>.
</p>

الناتج:

The app says howdy.

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

خاتمة

المُرشّحات المبنيّة في Angular جزءٌ هامٌّ من قدراتها الرائعة، كما أنّ استخدامها سهلٌ وبديهيّ بشكلٍ عامٍّ، كما أنّها مرنة بفضل الوسطاء الإضافيّة وسَلسلة المُرشّحات، وأيضًا فإنّ إنشاء مرشّحٍ مخصّص سيكون سهلًا فورَ إتقانك لنمط الوصفة الإنشائيّة (creation recipe) التي تسمح لك بحقن التّبعيّات. والآن بعد أن اعتدنا استخدام هذه الأنماط، فنحن الآن جاهزون للتّحدّي في الفصل القادم، حيث سنقوم بإعداد التّوجيهات الخاصّة بنا.

30يوليو

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

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

30يوليو

( تعلم Angular ) – حقن التبعية (Dependency-injection) في AngularjS

تدير Angular الموارد باستخدام نظام الوحدات، ولهذا السّبب فهي تحتاج أيضًا إلى تقديم طريقةٍ للوصول إلى هذه الموارد المنظّمة ضمن حاويات (container-managed)، حيث تقوم Angular بذلك باستخدام نمطٍ إنشائيّ يُدعى حقن التبعية.

 

إن لم يكن لديك معرفةٌ مسبقةٌ بهذا المفهوم فسأقوم هنا بأفضل ما لديّ لتلخيصه لك: لنفترض أنّك تحتاج إلى الحصول على مَوردٍ (resource) من Angular، المَورد هو الحاوية. للحصول عليه يجب أن تكتب تابعًا يُعرّف وسيطًا له نفس اسم هذا المَورد بالضّبط. تقوم الحاوية باكتشاف التّابع الذي كتبته، وتتعرّف على توافق الوسيط مع المَورد المطلوب، فتقوم بإنشاء نُسخةٍ (instance) من هذا المَورد (أو تقوم بأخذ النُّسخة المنفردة (singleton) من هذا المَورد، إن كان من النّوع المنفرد) ثُمّ تقوم باستدعاء التّابع الخاص بك وجعل هذه النّسخة هي قيمة الوسيط الخاص بالتابع. بما أنّ الحاوية هنا تلعب الدّور الفعّال (على عكس الطلب الذي تقوم به للحصول على نسخة من المَورد)، لذلك يُعتبر حقن التّابعيّة عكسًا للتّحكّم (inversion of control)، ويُقدّم على أنّه تطبيق لمبدأ Hollywood، “لا تكلّمنا، نحن سوف نكلِّمُك”.

أعتقد بأنّ مخترعي Angular قد اختاروا طريقة حقن التّابعيّة بسبب تاريخ Google الطّويل مع منصّة Java، بيئة برمجة ساكنة يقوم فيها حقنُ التّابعيّة بدورٍ فعّال في عمليّات تغيير الأنواع أثناء وقت التّشغيل. (لقد تخطّت Google الحدود في ذلك إلى درجة أنّها أنشأت إطار عمل Java خاصٍّ بها، Guice، الذي يعتمد على حقن التّابعيّة كُلّيًّا.) أمّا في JavaScript، وهي البيئة المرنة والديناميكيّة، حيث يمكن أن تتغيّر الأنواع أثناء وقت التّشغيل، فقد حظِيَ فيها أسلوب محدّد موضع الخدمة (service locator) البسيط والمباشَر بشعبيّة هائلة عن طريق الواجهة البرمجيّة لـCommonJS، وهي عبارة require المستخدمة في Node.js وbrowserify. والأكثر من ذلك، أنّ الطريقة البدائيّة في القيام بحقن التّابعيّة قد تبيّن أنّ لها نقطة ضعفٍ عند استخدامها في JavaScript من طرف العميل، وذلك بسبب تعديل أسماء الوُسطاء باستخدام المُقصّرات (minifiers). ولنستطيع التّعامل مع هذه المشكلة، من الضّروريّ أن توجد أداة بناءٍ بديلة، أو أن نتعلّم ونستخدم حواشي التّبعيّات، وسنصل إلى ذلك بعد قليل، ولكن لنتعرّف أوّلًا على النّموذج البدائيّ.

التبعيات الضمنية

لن نستخدم خارج هذا الفصل للقيام بحقن التّبعيّة غير طريقة التّبعيّات الضّمنيّة لأنّها الأسهل بين جميع الطُّرق. كما ذكرت سابقًا، يمكننا جلب مرجعٍ لأحد الموارد المتاحة عن طريق وضعه كوسيطٍ في تابعنا الإنشائيّ، ولقد قمنا بذلك للمرّة الأولى عندما أضفنا الوسيط scope$ الذي يعطي تابع التّحكّم مرجعًا إلى كائن المجال المنظّمٍ ضمن حاوية (container-managed).

يقوم نظام حقن التّبعيّة في Angular بالعثور على نسخةٍ للمَورد المطلوب (أو بإنشاء نسخةٍ لهذا المَورد) وتمريره إلى المتحكّم. لنرى مرّةً أخرى كيف نقوم بذلك على المَورد المدمج في Angular، الخدمة locale$.

نحتاج إلى تعريف وحدتنا app لأنّ بعض أمثلة هذا الفصل تستخدم نظام الوحدات.

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

وبعد ذلك سنقوم بتحميل الوحدة عن طريق تمرير اسمها إلى التّوجيه ng-app. (أكرّر التّنبيه إلى أنّ الاسم app ليس إجباريًّا ويمكن استخدام أيّ اسم آخر.)

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

نحن الآن جاهزون للانطلاق مع مثالنا الأوّل، وبطريقةٍ مماثلةٍ لإضافة الوسيط scope$ سنضيف الوسيط locale$، وستلاحظ Angular هذين الوسيطين وتقوم بحقنهما. تقوم Angular بحقن الموارد داخل التّابع لأنّ هذا التّابع يمثّلُ متحكّمًا.

angular.module('app')
  .controller('LocaleController', function($scope, $locale) {
    $scope.locale = $locale.id;
  });

داخل جسم المتحكّم قمنا ببساطةٍ بإسناد المتغيّر locale الذي يمكننا تغيير اسمه إلى أيّ اسمٍ آخر، إلى العنصر id من الوسيط locale$ الذي لا يمكننا استخدام اسمٍ آخر له.

<p ng-controller="LocaleController">
  Your locale is <strong ng-bind="locale"></strong>
</p>

الناتج:

Your locale is en-us

يُفترض أن ترى شيئًا مماثلًا لعبارة “Your locale is en-us” في المخرجات. ما الذي سيحدث لو أنّك أضفت الوسيط myResource إلى تابع التّحكّم؟ هل عطّلت عمل المثال؟ هل يمكنك قراءة الخطأ في الـconsole الخاصة بـJavaScript في متصفّحك؟ تعرض أخطاء Angular غالبًا رابطا مفيدا، يمكنك عن طريق هذا الرابط أن تحصل على درسٍ رائع وقصير عن حلّ مشاكل حقن التّبعيّة، انقر على الرّابط السّابق واقرأ الشرح إن أردت.

الحواشي (Annotations)

تمثّلُ الحاشية في لغة برمجةٍ مثل Java، معلوماتٍ إضافيّة تُضاف إلى الشيفرة المصدريّة ويتجاهلها المترجم. أمّا في سياق الحديث عن حقن التّبعيّة في Angular، فتعني كلمة الحاشية التّصريح عن الموارد التي نريد حقنها بطريقةٍ صريحةٍ تحافظ على أسماء الوسطاء قصيرةً باستخدام أسلوب التّصغير (minification). (استخدام أداة البناء UglifyJS مثالٌ على أسلوب التّصغير.) لنتابع الآن طريقة Angular التي تسمح لنا فيها بإضافة الحواشي إلى التّبعيّات.

ng-annotate

من الطرق الملائمة للتخلص من إزعاج مهمّة بناء ما، استخدام مهمّة بناءٍ أخرى. ستقوم أداة البناء ng-annotate بإضافة الحواشي للتّبعيّات، ومن ثمّ ستسمح لنا بمواصلة استخدام طريقة التّبعيّات الضّمنيّة التي شرحناها في الفقرة السّابقة. ستحتاج فقط إلى تشغيل ng-annotate قبل أن تُشغّل المقصّر (minifier) الخاصّ بك. هذه الأداة المركزيّة صُمّمت لتُستخدم عن طريق سطر الأوامر، إلّا أنّه يوجد غلافٌ لها لتفادي التّعامل مع سطر الأوامر، كما يوجد غلافٌ لمعظم أدوات البناء وحزم الأدوات، من ضمنها grunt-ng-annotate وgulp-ng-annotate وbrowserify-ngannotate وng-annotate-webpack-plugin.

الحاشية السطرية

هناك أسلوبان لكتابة الحواشي، وأكثرهما شُهرةً يُعرف باسم الحاشية السّطريّة (inline annotation)، وهي تتضمّن وضع تابع التّحكّم داخل مصفوفةٍ كما يلي.

angular.module('app')
  .controller('LocaleController', ['$scope', '$locale', function(s, l) {
    s.locale = l.id;
  }]);

الناتج:

Your locale is en-us

في المثال السّابق، قُمت بمحاكاة تأثير التّقصير عن طريق تغيير أسماء الوُسطاء من scope$ و locale$ إلى s وl بالترتيب. بالطّبع لا يمكن لـAngular أن تجد مَوردين لهما الأسماء s وl لو اعتمدنا على أسلوب إضافة الحواشي الضّمنيّ، ولكن هنا، ولأنّ المصفوفة تحوي أسماء الوسطاء الحقيقيّين ستتمكّن من حقنهم بنجاح.

إضافة الحاشية بواسطة inject$

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

var LocaleController = function(s, l) {
  s.locale = l.id;
};
 
LocaleController['$inject'] = ['$scope', '$locale'];
 
angular.module('app')
  .controller('LocaleController', LocaleController);

الناتج:

Your locale is en-us

المهمّ أنّك الآن تعرف الخيارات المتاحة للقيام بإضافة الحواشي.

angular.injector

أرجو أن تكون عند هذه النّقطة قد قمت بإضافة ng-annotate إلى بُنيتك، لتتمكّن من استخدام الأسلوب البدائيّ في التّبعيّات الضّمنيّة التي ستراها في هذه السلسلة دون مشاكل. على أيّ حال، إن لم تكن قد قمت بذلك، لديّ اقتراح لك، إلّا أنّه ليس اقتراحًا جادًّا: قم بكتابة تابع require الخاصّ بك، وأفصح عن التّبعيّات الّتي تريد ضمن عمليّة البحث المباشر عن الخدمات، وذلك بدلًا من استخدام أسلوب حقن التّبعيّة. يمكنك القيام بذلك عن طريق استدعاء تابع angular.injector مباشرةً، وعليك تمرير وسيط وحيدٍ إليه هو مصفوفةٌ فيها أسماء الوحدات التي ترغب بالبحث ضمنها، ويجب عليك تمرير اسم الوحدة ng دومًا وجعلها في بداية القائمة.

var require = function(name) {
  return angular.injector(['ng','app']).get(name);
};

angular.module('app')
  .controller('LocaleController', ['$scope', function($scope) {
    var $locale = require('$locale');
    $scope.locale = $locale.id;
  }]);

الناتج:

Your locale is en-us

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

مخاطر

المثال التّالي سيستبق الأحداث، ويأخذنا إلى الفصل القادم، الخدمات. سنستخدم في هذا المثال التّابع factory، ورغم أنّك لم تألف استخدامه من قبل إلّا أنّه بإمكانك تخمين وظيفته، فهو يقوم بإعداد مصنعٍ للمكوّن باستخدام نظام الوحدات. لاحظ أيضًا أنّ الخدمة http$ (التي سنناقشها في فصلٍ لاحق HTTP) ستكون خدمةً نموذجيّةً ومثالًا من الحياة العمليّة لاستخدام حقن التّبعيّة داخل خدماتٍ كهذه.

angular.module('app')
  .factory('fullPrice', ['$http', function($http) {
    return function() {
      // Use $http to fetch remote data.
      return 100;
    }
  }])
  .factory('discountPrice', ['$http', function($http) {
    return function() {
      // Use $http to fetch remote data.
      return 40;
    }
  }]);

استلهمتُ المثال التّالي من تطبيق Angular حقيقيّ، حيث استُبدلت التّبعيّات بقائمةً طويلةً من الخدمات المدمجة في Angular، ولكنّ طول القائمة هو نفسه تقريبًا.

angular.module('app')
  .controller('PriceController',
    ['$scope',
    '$anchorScroll',
    '$animate',
    '$cacheFactory',
    '$compile',
    '$controller',
    '$document',
    '$exceptionHandler',
    '$filter',
    '$http',
    '$httpBackend',
    '$interpolate',
    '$interval',
    'fullPrice',
    'discountPrice',
    '$locale',
    '$location',
    '$log',
    '$parse',
    '$q',
    '$rootElement',
    '$rootScope',
    '$sce',
    '$sceDelegate',
    '$templateCache',
    '$timeout',
    '$window',
    function(
      $scope,
      $anchorScroll,
      $animate,
      $cacheFactory,
      $compile,
      $controller,
      $document,
      $exceptionHandler,
      $filter,
      $http,
      $httpBackend,
      $interpolate,
      $interval,
      discountPrice,
      fullPrice,
      $locale,
      $location,
      $log,
      $parse,
      $q,
      $rootElement,
      $rootScope,
      $sce,
      $sceDelegate,
      $templateCache,
      $timeout,
      $window) {
        $scope.fullPrice = fullPrice();
        $scope.discountPrice = discountPrice();
  }]);

من الواضح أنني اختصرت جسم المتحكّم PriceController وكذلك اختصرت شيفرة العرض التّالية.

<table ng-controller="PriceController">
  <tr>
    <td>Full price:</td>
    <td>{{fullPrice}}</td>
  </tr>
  <tr>
    <td>Discount price:</td>
    <td>{{discountPrice}}</td>
  </tr>
</table>

الناتج:

Full price:	    40
Discount price:	100

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

30يوليو

( تعلم Angular ) – الوحدات (Modules) في AngularJS

إنّ هدف الوحدات عمومًا هو توزيع المهام عن طريق تعريف توابع الواجهة البرمجية (API) والحدّ من رؤية السلوك (أجسام التوابع) والبيانات (العناصر والمتغيّرات).

معظم المنصّات البرمجيّة تتضمّن دعمًا داخليًّا للوحدات، حتّى أصبح استخدامها أمرًا مسلّمًا به. ولكنّ JavaScript المستخدمة من طرف العميل لا تستخدم الوحدات حاليًّا، وتؤدي إلى احتدام النقاشات المليئة بين مؤيّد ومعارض للحلّ المشهور (الإضافات add-ons) مثل CommonJS و تعريف الوحدة غير المتزامن (AMD).

أبقِ في ذهنك عندما تقوم بمقارنة الحلول المطروحة، أنّ نظام الوحدات في Angular لا يقوم بتحميل الوحدة. فهو لا يقوم بتحميل الشيفرة المصدرية من ملفّاتٍ أو من الإنترنت. فالحلّ الذي تقدّمه Angular هو نظام الوحدات المدمج فيها، والذي يرتبط ارتباطًا وثيقًا بنظام حقن التابعية المدمج أيضًا في Angular. معًا، يكوّن هذان النّظامان جزءًا كبيرًا من البنية التّحتيّة لـAngular. ولكن هل نحن بحاجةٍ إليها حقًّا؟ صحيحٌ أنّ بإمكان أيّ مطوّر تعلّمها، ولكن لمَ نحتاجها؟

تُشكّل الوحدات وحقن التّابعيّة كلّ بنية Angular، ولكن كما رأينا فإنّ Angular تسمح لنا بالبدء دون استخدام نظام الوحدات. حتّى الآن، كان بإمكاننا إنجاز الكثير ببساطة عن طريق تعريف المتحكمات كتوابع معرّفة في المجال العامّ. ولكنّ ذلك لم يكن ليعمل لولا القليل من الإعدادات، وهذه الإعدادات سمحت للتّوجيه ng-controller بالبحث عن توابع التّحكّم في المجال العام وأيضًا في نظام الوحدات. هذه الطريقة السهلة في استخدام المجال العام مسموحةٌ في Angular، ولكن فقط إلى هذه النقطة.

لماذا نستخدم وحدات Angular؟

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

لنبدأ بنظرةٍ عامّة إلى المزايا الهامّة التي تدفعنا لاستخدام نظام الوحدات:

  • مكوّنات مخصّصة – لتعريف المتحكّمات الخاص بك، التّوجيهات، المرشّحات، والمؤثّرات الخاصّة، لا بدّ من استخدام نظام الوحدات.(يمكننا استثناء المتحكّمات كما أشرنا سابقًا.)
  • حقن التّابعيّة – الخدمات ليست إلّا كائنات وتوابع JavaScript عاديّة، إلّا أنّ الخدمات التي يتمّ إنشاؤها في نظام الوحدات يمكن أن تُحقن بسهولة مع التّبعيّات الخاصّة بها، وذلك إن كانت تتيح حقن التّابعيّة فيها.
  • الوحدات الخارجيّة – هناك نظامٌ بيئيّ مثيرٌ للاهتمام، مجّانيٌّ ومفتوح المصدر من الإضافات لـAngular، وقد قام بكتابة بعض هذه الإضافات فريق تطوير قلب Angular وكذلك المطوّرون كطرفٍ ثالث. لاستخدام أيٍّ من هذه المكاتب في تطبيقك لا بُدّ من استخدام نظام الوحدات.
  • إعدادات وقت-التّحميل – يسمح نظام الوحدات في Angular بالوصول إلى تعلّقات دورة الحياة (lifecycle hooks) المتعلّقة بالإعدادات الدّاخليّة لـAngular، وكذلك المتعلّقة بإعدادات الوحدات المضافة، وسنتطرّق لها في نهاية الفصل الأخير، حيث سنقوم بإعداد رأس HTML مخصص.
  • الاختبار – إنّ البنية التّحتيّة الخاصّة بالاختبار في Angular تبيّن فاعليّة نظام حقن التّابعيّة والّذي يعتمد بدوره على نظام الوحدات.

سيبيّن فصلٌ لاحقٌ، فصل الخدمات، كيف نقوم بتعريف مقدم لمكوّنات JavaScript المخصّصة. سنرى في هذا الفصل كيف نعرّف متحكّمًا، وسنقوم أيضًا بتحميل وحدةٍ خارجيّةٍ، وأثناء تنفيذ ذلك سنتعلّم أساسيّات نظام الوحدات في Angular.

إنشاء وحدة التطبيق

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

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

أرجو أن تركّز على الوسيط الثاني للتابع module في مثالنا السابق، ورغم أنّه يبدو فارغًا، وله مظهر مصفوفة بريئة خالية ([]) إلّا أنّ وجود هذا الوسيط الثاني شديد الأهمّيّة، فبدونه سيتغيّر سلوك التّابع angular.module كليا، وسأشرح لك سلوك هذا التابع كما يلي:

عند استدعاء التابع angular.module مع وجود الوسيط الثاني، سيعمل التّابع في نمط الإنشاء (create)، وسيقوم بإنشاء وحدةٍ ويسمّيها app إن لم يكن هناك وحدة بنفس الاسم سابقًا، أمّا عند استدعائه دون الوسيط الثّاني فسيعمل في نمط البحث (lookup)، فإن عثر على وحدةٍ بالاسم المعطى سيقوم بإعادتها، أمّا إن لم يجدها فسيقوم برمي خطأٍ يطلب تمرير الوسيط الثاني. قمنا في المثال السابق بالاستخدام الصحيح للتابع، ولكن كن حذرًا دومًا من الفرق بين الطريقتين في استدعاء هذا التّابع, أمّا بالنّسبة لهذه المصفوفة التي تمرر كوسيط، فهي تُستخدم لجلب الوحدات الخارجية التي سنناقشها في هذا الفصل، باستخدام وحدة animation في أمثلتنا.

تحميل وحدة التطبيق

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

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

ستكون جميع المكوّنات التي نضيفها إلى وحدة التّطبيق متاحةً للاستخدام. لنرى الآن كيف نقوم بتعريف متحكّم داخل وحدةً بدلًا من تعريفه في المجال العام.

تعريف متحكم

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

سنحتاج أوّلًا إلى جلب مرجعٍ لوحدة التّطبيق، وكما ذكرنا سابقًا، يجب أن نقوم باستدعاء التابع angular.module بدون تمرير الوسيط الثاني، وذلك كي يعمل في نمط البحث lookup.

var app = angular.module('app');

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

والآن، في مكان ما ضمن العنصر الذي قمنا فيه باستدعاء الوحدة عن طريق “ng-app=”app، يمكننا استخدام التّوجيه ng-controller لتحميل المتحكّم.

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

الناتج

This is a model.

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

سلسلة التعريفات

لنفترض بأننا نريد تعريف متحكّمين للقالب التالي.

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

إنّ توابع التعريف ضمن العنصر Module قابلةٌ للسَّلسَلة، حيث يمكننا إنشاء متحكّمين باستخدام عبارةٍ واحدة.

angular.module('app')

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

  .controller('AnotherMessageController', function($scope) {
    $scope.message = "This is another model.";
  });

الناتج

This is a model.

This is another model.

لاحظ أن الاستدعاء الأول للتابع controller لم تتبعه فاصلةٌ منقوطة.

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

var app = angular.module('app');

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

app.controller('AnotherMessageController', function($scope) {
  $scope.message = "This is another model.";
});

يمكنك اختيار أيٍّ من الطريقتين، ولكنّ طريقة السَّلسلة هي الأكثر شيوعًا.

تحميل الوحدات

يُعدّ التحريك (animations) ميزةً جديدةً في Angular تمّت إضافتها إلى حزمة منفصلة اسمها ngAnimate. الخطوة الأولى لاستخدام التحريك هي تضمين ملف JavaScript المحتوي على الوحدة. الشيفرة المصدريّة للوحدة جزءٌ من قلب Angular، ولكنّها موجودةٌ في ملفٍّ منفصل، كما نرى أدناه.

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular-animate.js"></script>

حان الوقت لنستخدم الوسيط الثّاني للتابع module كما يجب، فبدلًا من تمرير مصفوفةٍ خاليةٍ فيه سنقوم الآن بالتصريح عن الوحدات المتعلّقة بوحدتنا.

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

لقد قمنا الآن بتحميل ngAnimate إلى داخل وحدة التّطبيق، والآن ستقوم التّوجيهات ng-show و ng-hide و ng-class و العديد من التّوجيهات الأخرى بالتّحقّق من التحريك الخاصّ بـCSS وJavaScript أيضًا الذي يمكن تطبيقه على أحداث المستند مثل add و enter و leave و move و remove، وتطبيق هذا التّحريك شفّاف فهو لا يتطلّب أيّ تغيير على شيفرة القالب المكتوبة، وإن لم نكن قد طبّقنا هذا التّحريك سواء على شكل CSS أو كـJavaScript عن طريق تسجيل الحركة عبر module.animation، فإنّ الشيفرة ستسلك السّلوك الافتراضيّ لها بشكل طبيعيّ.

<input type="checkbox" ng-model="showMessage">
    Check the box to show the message
<h2 ng-show="showMessage">
    The secret message.
</h2>

بالنّقر على الـcheckbox ستقوم ng-show بعملها كالمعتاد، وسيتمّ التّبديل بين إظهار وإخفاء العنصر بشكلٍ فوريّ دون تدرّج أو تأثير اختفاءٍ بسيط، إلى أن نكتب شيفرة التّحريك في CSS أو JavaScript. في المثال التّالي سنستخدم CSS، وهي عمومًا الخيار الأفضل للتحريك بسبب الأسلوب التصريحي في كتابتها. يمكنك على أيّ حالٍ استخدام التابع animate في jQuery ليقوم بالمهمّة إن وجدت ذلك أسهل، أما Angular فتقوم بذلك عن طريق الوحدة module.animation.

يتطلّب التّحكّم بتأثير الاختفاء التّدريجيّ استخدام أربع فئات CSS تقوم بالتّعلّق (hook)، وذلك حسب توثيق الواجهة البرمجية الخاصّ بالتّوجيه ng-show. هذه الفئات الأربعة هي ng-hide-add و ng-hide-add-active و ng-hide-remove و ng-hide-remove-active.

.ng-hide-add,
.ng-hide-remove {
  transition: all linear 1s;
  display: block !important;
}

.ng-hide-add.ng-hide-add-active,
.ng-hide-remove {
  opacity: 0;
}

.ng-hide-add,
.ng-hide-remove.ng-hide-remove-active {
  opacity: 1;
}

قد تحتاج إلى إضافة خصائص محدّدة من قبل المصنّع لبعض المتصفّحات حسب زوّار موقعك، مثل webkit-transition- الخاص بمتصفّح سفاري 6 لأنظمة iOS. (يُعدّ موقع caniuse.com مرجعًا جيّدًا لتوافق المتصفّحات مع HTML5 و CSS3.) أرجو أن تنتبه أيضًا إلى أنّ هذا التّحريك سيتمّ تطبيقه في كلّ الأماكن التي ستستخدم فيها ng-show و ng-hide في تطبيقك. يمكنك إضافة فئة CSS مخصّصة لتزيد قدرتك على التّحكّم، وذلك للعنصر ولمحدّد CSS الذي تتعامل معه، مثلًا يمكنك كتابة my-class.ng-hide-add. و my-class.ng-hide-remove. وهكذا.

خاتمة

أتمنى أن تكون سهولة إضافة ngAnimate قد أقنعتك بأهمّية فهم نظام الوحدات في Angular. ومن المفرح أن تعلم بأنّ ngAnimate ليست إلّا البداية ضمن الكثير من الوحدات المُضافة إلى Angular. يمكنك أن تختار وحداتٍ أخرى متاحةّ بشكلٍ مجّانيّ من هذا النّظام البيئيّ سريع النموّ الذي يعتمد على إبداعات المطوّرين من مواقع مثل GitHub، وذلك إضافةً إلى الوحدات التي تتيحها Angular أصلًا مثل ngResource وngRoute. أحد أكثر المشاريع شُهرةً هو AngularUI، الذي يحوي وحداتٍ عظيمة الأهمّيّة مثل UI Router وUI Bootstrap، ومعظم هذه الوحدات يمكن تحميله بسهولة في مشروعك باستخدام Bower، مدير حزم الويب، ولربّما ستبدأ بنشر وحدات Angular الخاصّة بك مفتوحة المصدر على GitHub وBower عندما تملك زمام Angular، وهذا ما أتمنّاه.

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

© Copyright 2014, All Rights Reserved For Haitham.me