لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 12/24/15 in مقالات البرمجة
-
SSE اختصار لـ Server Sent Events وتسمى أيضًا EventSource تُستخدم لِبنّاء تطبيقات ويب تفاعلية. حيث يُرسل الخادوم أحداثا في الوقت الحقيقي إلى المستخدم، قد يتضمن الحدث تنبيهات للمستخدم أو بيانات يمكن استغلالها في عمل شيء ما وتحديث صفحة الويب. في السّابق، لِعمل تحديث تلقائي للبيانات كل ثانية كانت تُستَخدم هذه الطريقة: setInterval(function(){ // إستدعاء الدالة بشكل تكراري xhr = new XMLHttpRequest(); // إستخدام طلب ajax xhr.onreadystatechange = function(){// عند أي تغير في حالة الطلب if(this.readyState == 4){// تم إستقبال النص بنجاح // إفعل بعض الاوامر } } xhr.open("get","http://localhost"); // تحديد نوع الطلب ورابط الموقع xhr.send("mydata"); // إرسال البيانات إلى الخادم }, 1000); // تحديد مدّة التكرار إلى ثانية أو هذه الطريقة الأكثر تطورًا: function getAjax(){ // تعريف الدالة التي ستدعى بشكل تكراري xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(this.readyState == 4) setTimeout(function(){ // استدعاء ألدالة مجددًا بعد ثانية من الإنتهاء لِتكرار طلب الajax getAjax(); },1000); } xhr.open("get","http://localhost"); xhr.send("mydata"); } getAjax();// إستدعاء الدالة للمرة الأولىهذه الطرق صحيحة لكنها ليست الأفضل، حيث تقوم بإرسال طلب كل ثانية للخادم مما يُسبٍّب ضغط عليه وغالبًا لا يكون التحديث في الوقت الحقيقي لظهوره. فكرة عمل SSE أن الطلب يظل مَفتوحا في إنتظار محتوى جديد والخادم هو من يُرسل المحتوى الجديد وليس المتصفح من يطلبه. SSE ليست الطريقة الوحيدة لِبناء تطبيقات ويب تفاعلية، يوجد العديد من الطرق الأخرى من أشهرها WebSocket. مُقارنة بين SSE و WebSocketالسهولة والتوافقيةمعظم الإستضافات المشتركة لا تَدعم WebSocket وهو يعمل على بروتوكل ws أو wss (في حالة إستخدام شهادة أمان ssl) وهما مختلفان عن الذي عليه واجهة المستخدم مما يُسبِّب مشاكل في مصادفة الاثنين معًا (يوجد مكتبات لفعل ذلك) على خلاف SSE التي تُعتبر طلب عادي على نفس بروتوكول واجهة المستخدم النهائية. تزامن المتسخدمينWebSocket يُمكنها عمل تزامن بين المستخدمين بطريقة مباشرة حيث أن الإتصلات تُعالج كلها معًا. في SSE لايوجد طريقة مباشرة لِتزامن المسخدمين معًا لأن كل طلب مُنفصل عن الآخر لذا تُستخدم قاعدة بيانات وتحفظ أخر التغيرات للمستخدم الأول وعند المستخدم الآخر يَتم البحث بشكل دوري عن التغيرات في قاعدة البيانات ثم إرسالها إلى المستخدم الآخر. إرسال وإستقبال البياناتWebSocket و SSE يدعمان إستقبال البيانات لكن لا يُمكن إرسال البيانات إلى الخادم بإستخدام SSE، أي عند بناء تطبيق يعتمد على إرسال وإستقبال البيانات من الخادوم ستضطر إلى بناء صفحة منفصلة تقوم بإستقبال الطلبات من المستخدم بواسطة ajax مثلًا. تطبيقات قد يكون إستخدام WebSocket فيها أفضلفي تطبيق سيحتاج إلى تفاعل المستخدمين معًا (تطبيق تواصل مثلًا) سيكون إستخدام WebSocket أفضل من SSE لأن الأخيرة لا يُمكنها إستقبال الطلبات من الخادوم.إرسال إشعارات أو رسائل للمستخدم أو أي تطبيق لن يَحتاج المستخدم فيه إلى إرسال رسائل إلى الخادوم. هنا ممكن أن يكون إستخدام SSE أفضل وأسهل.اهلًا بالعالمقبل البدء يجب أن تَعرف المتصفحات الداعمة لـِ SSE وأن متصفح أنترنت أكسبلور لايَدعمه لكن يُمكنك إستخدام مكتبة خارجية لدعمه مثل EventSource.js (يتم تضمين ملف javascript في الصفحة) هذا مثال لنص مصدري لإستجابة طلب SSE: http/1.1 200 OK ## الصفحة موجودة ! Content-Type: text/event-stream ## تعريف المتصفح أن الصفحة هي محتوى لطلب SSE event: delete## تعريف الحدث الذي سيُستقبل من javascript data: "some data" ## البيانات التي سترسل إلى الحدث event: add## تعريف حدث آخر data: "some data" data: "some data"## إرسال بيانات بدون حدث مُعرف id: 3 ## غير مهم وغير مطلوب بشكل ضروري يُستخدم لتحديد مُعرف lastEventId للرسائلالأمثلة مكتوبة بلغة php ويُمكنك فعلها في أي لغة ويب أخرى هذا تطبيق بسيط لجلب الساعة من الخادم لحظة بلحظة. ملف server.php ( الذي سيعالج طلبات SSE احفظه بجوار واجهة المستخدم أو أي مكان آخر مع تغير المسار في شفرة javascript ): <?php set_time_limit(0);// تحديد أقصى مدة للطلب غير نهائية لان هناك خوادم تقوم بتحديدها $time = 1;// متغير بقيمة الوقت الذي سيرسل فيه تحديث جديد header("Content-Type: text/event-stream");// إرسال ترويسة أن الصفحة هي محتوى sse header('Cache-Control: no-cache'); // لمنع حفظ الكاش للصفحة while(true){// تكرار /* date("s-i-h") تُخرج الوقت على التَنسيق ساعة-دقيقة-ثانية PHP_EOL تعني سطر جديد يُمكن إستخدام \n لكن سَتختلف من نظام تَشغيل الى آخر */ echo "data: ".date("s-i-h").PHP_EOL; echo PHP_EOL; // كتابة نص جديد آخر للتفريق بين التنبيهات // إرسال المحتوى الذي كتب دون الإنتظار إلى انتهاء الصفحة ob_flush(); flush(); // إيقاف الاسكربت إلى المدة التي تم تعريفها في المتغير time لمدة ثانية أن لم تَقف الاسكربت سَيعمل بدون توقف sleep($time); } ?>إن فتحت الصفحة ستجد أنها تقوم بإرسال الساعة الحالية كل ثانية. الآن الواجهة التي ستتعامل معها: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> </head> <body style="direction: rtl"> <label>الساعة الآن : <span>.......</span></label> <script type="text/javascript"> if(!EventSource) alert("مٌتصفحك لايَدعم SSE :("); else{ SSE = new EventSource("server.php"); // انشاء الطلب SSE.onmessage = function(ms){ // عند وجود رسالة جديدة document.querySelector("span").innerHTML = ms.data;// استبدال النص بالجديد } SSE.onerror = function(){//عند وجود خطأ alert("يوجد خطأ في الاتصال ..."); mkcon(); // إعادة الطلب } } </script> </body> </html> الأحداث وطرق انشاءهايُمكن في SSE إنشاء أحداث مخصصة لكي تَكون الشفرة أكثر ذكاءا، مثلًا إرسال حدث لحذف عنصر وحدث لإنشاء عنصر آخر. مثال: تعريف مصفوفة بإسم الألوان أحمر أخضر أزرق. $colors = array("red", "green", "blue");ثم تعريف اسم الحدث بإسم عشوائي من هذه الألوان: $colors[rand(0,2)];ليكون النص الكامل لملف server.php: <?php set_time_limit(0);// تحديد أقصى مدة للطلب غير نهائية لان هناك خوادم تقوم بتحديدها $time = 1;// متغير بقيمة الوقت الذي سيرسل فيه تحديث جديد header("Content-Type: text/event-stream");// إرسال ترويسة أن الصفحة هي محتوى sse $colors = array( // مصفوفة الألوان من النوع الرقمي "red", "green", "blue" ); while(true){// تكرار /* date("s-i-h") تُخرج الوقت على التَنسيق ساعة-دقيقة-ثانية PHP_EOL تعني سطر جديد يُمكن إستخدام \n لكن سَتختلف من نظام تَشغيل إلى آخر */ echo "data: ".date("s-i-h").PHP_EOL; echo "event: ". $colors[rand(0,2)] // توليد لون عشوائي بإسم الحدث .PHP_EOL; echo PHP_EOL; // كتابة نص جديد آخر للتفريق بين التنبيهات // إرسال المحتوى وعدم الإنتظار إلى إكتمال الطلب إلى النهاية ob_flush(); flush(); // إيقاف الاسكربت إلى المدة التي تم تعريفها في المتغير time لمدة ثانية أن لم تَقف الاسكربت سَيعمل بدون توقف sleep($time); } ?>وفي واجهة المستخدم إنشاء بعض العناوين: <style> .red{ color: red } .blue{ color: blue } .green{ color: green } </style> <label class="red"> ....... </label> <label class="green"> ....... </label> <label class="blue"> ....... </label>الآن إستقبال الأحداث: SSE.addEventListener("red",function(ms){ //اللّون الأحمر red اسم الحدث الذي عرف من php document.querySelector(".red").innerHTML = ms.data; });لتصبح شفرة الواجهة الكاملة: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <style> .red{ color: red } .blue{ color: blue } .green{ color: green } </style> </head> <body> <label class="red"> ....... </label> <label class="green"> ....... </label> <label class="blue"> ....... </label> <script type="text/javascript"> if(!EventSource) alert("مٌتصفحك لايَدعم SSE :("); else{ function mkcon(){ SSE = new EventSource("server.php"); // إنشاء الطلب SSE.addEventListener("red",function(ms){ //اللون الأحمر document.querySelector(".red").innerHTML = ms.data; }); SSE.addEventListener("green",function(ms){ //اللون الأخضر document.querySelector(".green").innerHTML = ms.data; }); SSE.addEventListener("blue",function(ms){ //اللون الأزرق document.querySelector(".blue").innerHTML = ms.data; }); SSE.onerror = function(){//عند وجود خطأ alert("يوجد خطأ في الاتصال ..."); mkcon(); // اعادة الطلب } } mkcon(); } </script> </body> </html>ستجد أن أحدى الساعات من الثلاثة تزيد بشكل عشوائي كل ثانية لكن بالطبع هذا مثال، لن تقوم بإستخدام هذه التقنية في هذا. إستخدام بيانات jsonإن أردت إرسال مقال بإستخدام SSE فستحتاج إلى إرسال العنوان والمحتوى ولن تُرسل حدثين لذلك يُمكنك إستخدام json كما يلي: { "title": "العنوان", "content": "المحتوى" }اذًا سَترسل هذه البيانات: data: {"title": "ألعنوان","content":"ألمحتوى"}ثم سيكون إستقبال البيانات من خلال javascript بهذا الشكل: SSE.onmessage = function(ms){ var JsonData = JSON.parse(ms.data); // قراءة نص json alert(JsonData.title + "\n" + JsonData.content); }مثال عملي عن إرسال بيانات json بتوقيت السيرفر المحلي: ملف server.php <?php set_time_limit(0);//تُحدد أقصى مدة للطلب غير نهائية لان هناك خوادم تقوم بتحديدها $time = 1;// متغير بقيمة الوقت الذي سيرسل فيه تحديث جديد header("Content-Type: text/event-stream");// إرسال ترويسة أن الصفحة هي محتوى sse while(true){// تكرار /* date("s-i-h") تُخرج الوقت على التَنسيق ساعة-دقيقة-ثانية PHP_EOL تعني سطر جديد يُمكن إستخدام \n لكن سَتختلف من نظام تَشغيل الى آخر */ echo 'data: '. /** مصفوفة بالوقت. دالة json_encode تقوم بتحويل البيانات الى ترميز json */ json_encode(array( "hour" => date("h"), // الساعة "minute" => date("i"), // الدقيقة "second" => date("s") // الثانية )) .PHP_EOL; echo PHP_EOL; // كتابة نص جديد آخر للتفريق بين التنبيهات // إرسال المحتوى وعدم الإنتظار إلى إكتمال الطلب إلى النهاية ob_flush(); flush(); // إيقاف الاسكربت إلى المدة التي تم تعريفها في ألمتغير time لمدة ثانية ان لم تَقف الاسكربت سَيعمل بدون توقف sleep($time); } ?>وملف الواجهة: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> </head> <body style="direction: rtl"> <p>الساعة الآن <b id="hour">0</b> و <b id="minute">0</b> دقيقة و <b id="second">0</b> ثانية.</p> <script type="text/javascript"> if(!EventSource) alert("مٌتصفحك لايَدعم SSE :("); else{ function mkcon(){ SSE = new EventSource("server.php"); // إنشاء الطلب SSE.onmessage = function(ms){ var JsonData = JSON.parse(ms.data); // قراءة نص json document.querySelector("#hour").innerHTML = JsonData.hour; // إستبدال الساعة document.querySelector("#minute").innerHTML = JsonData.minute;// إستبدال الدقائق document.querySelector("#second").innerHTML = JsonData.second;// إستبدال الثواني } SSE.onerror = function(){//عند وجود خطأ alert("يوجد خطأ في الاتصال ..."); mkcon(); // إعادة الطلب } } mkcon(); } </script> </body> </html>في هذا المثال والذي سبقه لن تَحتاج إلى إستحدام SSE ويمكن عَمل ذلك بإستخدام javascript بسهولة. تطبيق على ما شُرح: بناء تطبيق تواصللم تُستخدم قاعدة بيانات لتسهيل تشغيل السكربت (لكن بالطبع إستخدام قاعدة بيانات أفضل) سيولد لون لكل مستخدم وسيحفظ في cookies للتعرف عليه في الرسائل، ستحفظ البيانات في ملف ChatData.json على هذا الشكل (كان يُمكن إستخدام قاعدة بيانات sqlite): [// مصفوفة الرسائل { "author": "rgb(54,48,98)", // لون السمتخدم "message": "رسالة", }, { "author": "rgb(54,48,98)", // لون السمتخدم "message": "رسالة 2", }// ....,...,.. ]تصميم الواجهةهذه هيكلة الصفحة: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>تجربة</title> <meta name="viewport" content="width=device-width"> </head> <body> <div class="messages"> </div> <div class="form"> <div class="author me"></div> <textarea placeholder="كتابة رسالة"></textarea> <button id="send">أرسل</button> </div> </body> </html>إضافة الحيوية للصفحة: body,html{ width: 100%;/** جعل عرض الصفحة بكامل الشاشة **/ direction: rtl; height: 100%; background: #f0f0f0;/** لون الخلفية **/ padding: 0; margin: 0 } /** جعل حجم بعض العناصر يحسب بالحواشي الخارجة **/ .messages, .messages > .message, .message > .content, .form, .form > textarea{ box-sizing: border-box } /** الرسائل **/ .messages{ padding: 40px; /** عمل فراغ بقيمة 40px من كل جهة **/ height: 80%; overflow-y: auto/** جعل الرسائل الزائدة تتحول الى اسكرول **/ } /** عناصر لن تكون بكامل عرض الحافظة **/ .message > .author, .message > .content{ display: inline-block;/** لن يأخذ العنصر كامل العرض **/ vertical-align: top /** العنصر سيكون في محاذة الأعلى **/ } /** هوية المستخدم **/ .message > .author{ width: 64px; height: 64px; background: #ddd; /** اللون الافتراضي سيغير بعد ذلك **/ border-radius: 100% /** تدوير العنصر **/ } /** جسم الرسالة **/ .message > .content{ background: white; box-shadow: 0 0 1px #ccc; /** تأثير الظل **/ padding: 20px; margin-right: 30px; /** لجعل الصفحة أكثر إستجابة على الشاشات المختلفة **/ width: 100%; max-width: 550px } /** عناصرالتحكم لإرسال رسالة **/ .form{ padding: 10px; height: 20% } /** صندوق النصوص **/ .form > textarea{ width: 100%; max-width: 678px; height: 100% } /** زر الإرسال **/ .form > button{ background: rgb(31, 158, 238); padding: 4px 20px; border: 0; cursor: pointer; /** مؤشر الفارة **/ color: white; border-radius: 3px /** عمل بعض الحواف **/ } .form > button:active{ background: rgb(31, 158, 200) } /** تصميم الجوال إصلاح بعض العلل **/ @media screen and (max-width:640px){ .messages{ padding: 0 } .message > .content{ margin: 0; margin-bottom: 20px } }إستقبال الرسائلSEE لاتدعم إرسال البيانات إلى الخادوم لذا سَتستخدم ajax لذلك. هذه شفرة بسيطة لإرسال الرسائل إلى الخادوم (يُضاف بين الوسم script في أخر الصفحة قبل </body> أو إن كنت ستضيفه الى head، يجب أن يكون في دالة: window.onload = function(){ //هنا }; document.querySelector("#send").addEventListener("click",function(){// عند الضغط على زر إرسال var mss = document.querySelector(".form > textarea").value; // محتوى الرسالة if(mss && mss.length){// التأكد من أن الرسالة مكتوبة var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function(){ if(this.readyState == 4) alert("تم إرسال الرسالة"); } ajax.open("get","server.php"); // نوع الطلب ومسار الإرسال ajax.send("?ms="+mss);// إرسال الرسالة } });ستحدث مشكلة إن أرسل المستخدم أكثر من رسالة في وقت واحد، من الممكن أن تصل رسالة قبل الأخرى. لذا سنستعمل قائمة بالرسائل المرسلة ونقوم بمعالجتها واحدة ثم الأخرى مع تحسين تجربة المستخدم قليلًا: messagesList = []; // مصفوفة بالرسائل InProgress = false;// يوجد طلب يُعالج أم لا function sendFirstMessage(){ if(!messagesList.length || InProgress) // لايوجد رسائل أو هناك رسالة تُعالج return false; var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function(){ // الرسالة ارسلت if(this.readyState == 4){ messagesList.splice(0, 1);// حذف الرسالة المرسلة اول رسالة InProgress = false; // تم الإنتهاء من العمل sendFirstMessage(); // إرسال الرسالة الأخرى } } ajax.onerror = function(){ alert("خطأ في الإتصال جاري المحاولة مجددًا"); sendFirstMessage(); } InProgress = true; ajax.open("get","send.php?message="+messagesList[0]); // نوع الطلب ومسار الإرسال ajax.send();// إرسال الرسالة } document.querySelector("#send").addEventListener("click",function(){// عند الضغط على زر إرسال var mss = document.querySelector(".form > textarea").value; // محتوى الرسالة if(mss && mss.length){// التأكد من أن هناك رسالة مكتوبة messagesList.push(mss); // إضافة رسالة إلى المصفوفة sendFirstMessage(); // إرسال الرسالة الأولى document.querySelector(".form > textarea").value = "";// افراغ حقل إرسال الرسائل } });ملف الخادوم الذي سيستقبل الرسائل، لن تكتب [] في الملف لتسهيل الكتابة في نهايته دون قِرائته: ملف send.php (الذي سيستقبل الرسائل) <?php if(empty($_GET["message"])) die(); /* التأكد من المستخدم */ if(!isset($_COOKIE["author"]) || !preg_match("/^rgb\([0-9]{0,3}\,[0-9]{0,3}\,[0-9]{0,3}\)$/i",$_COOKIE["author"])){ // توليد لون عشوائي أن لم يُعرف من قبل $R = rand(0,250); // اللّون الأحر $G = rand(0,250); // اللّون الأخضر $B = rand(0,250); // اللّون الأزرق setcookie("author","rgb($R,$G,$B)"); // نمط الألوان rgb } $message = str_replace(array("<",">"),array("<",">"),$_GET["message"]); // الوقاية من ثغرة xss $data = array( // البيانات التي ستكتب "content" => $message, "author" => $_COOKIE["author"] ); $FILE = fopen("./ChatData.json","a"); //فتح الملف للكتابة فيه $star = (!filesize("./ChatData.json")) ? "" :",";// إضافة فاصلة أن تم الكتابة بالملف من قبل fwrite($FILE,$star.json_encode($data)); // كتابة بيانات json في الملف fclose($FILE); ?>ملاحظة: سبب إستخدام الألوان هو لكي تتمكن من التعامل مع المستخدمين وحفظ البيانات في قاعدة البيانات للمستخدم. ملف server.php (الذي سيراقب تغير الملف ويرسل التحديث) <?php set_time_limit(0); // لايوجد اقصى مدة للطلب header("Content-Type: text/event-stream"); $viewd = 0; // عدد الرسائل التي عرضت لمنع إرسال الرسالة اكثر من مرة $lastFileSize = 0; // حجم الملف عندما يتغير فان هناك رسالة ارسلت while(true){ if(filesize("./ChatData.json") >= $lastFileSize){// التحديث فقط عند تغير حجم الملف $messages = json_decode("[".file_get_contents("./ChatData.json")."]"); // قراءة الرسائل for($i=$viewd;$i<=sizeof($messages)-1;$i++){ // حلقة تكرار من أخر رسالة مُرسلة echo "data: ".json_encode($messages[$i]).PHP_EOL; echo PHP_EOL; } $viewd = sizeof($messages); // تحديث عدد الرسائل المرسلة $lastFileSize = filesize("./ChatData.json"); // تحديث مُتغير أخر حجم للملف } ob_flush(); flush(); sleep(1); } ?>ثم في واجهة المستخدم شفرة javascript لجلب التحديثات: sse = new EventSource("server.php"); // انشاء الاتصال sse.onmessage = function(ms){ var data = JSON.parse(ms.data); // قراءة بيانات json var message = document.createElement("div"); // عنصر الرسائل message.setAttribute("class","message"); // اضافة كلاسس message var author = document.createElement("div"); // عنصر مُرسل الرسائل author.setAttribute("class","author"); // اضافة كلاسس author author.style.backgroundColor = data.author; // تغير لون الخلفية لمعرف المستخدم message.appendChild(author); // اضافة عنصر مُعرف مُعرف المستخدم الى عنصر الرسالة var content = document.createElement("p"); // محتوى الرسالة content.setAttribute("class","content"); content.innerHTML = data.content;// محتوى الرسالة message.appendChild(content); messages = document.querySelector(".messages"); messages.appendChild(message);// اضافة عنصر الرسالة الى حافظة الرسائل messages.scrollTop = messages.scrollHeight; // انزال الاسكرول الى الاعلى } ليعمل على انترنت اكسبلور يجب تضمين ملف javscript هذا في الـ header: <script src="https://rawgithub.com/remy/polyfills/master/EventSource.js"></script> يجب ان يكون العمل مُطابق لهذا: إن لم يعمل معك بشكل صحيح يُمكنك تحميل ملفات الأمثلة وقراءتها حتى تتمكن من معرفة كيف يعمل هذا المثال . الخاتمةيُمكنك رؤية المزيد من المصادر لمعرفة المزيد حول SSE: eventsource من طرف W3C.Server Sent Events من طرف مدونة موزيلا للمبرمجين.1 نقطة
-
AngularJS هي إطار عمل لتطبيقات الويب من طرف المستخدم يقوم بتفسير نصوص HTML مرة ثانية، ولو كنت خبيرا في تطوير الويب فمن الطبيعي أن تحاول مقارنة AngularJS مع منصات ومكتبات JavaScript التي تألفها مثل jQuery ،Knockout ،Backbone ،Ember وربّما مع React أيضا، وربما حاولت بناءً على معارفك السابقة ببرمجة واجهة المستخدم الرسومية GUI أن تربط AngularJS بـMVC أو MVVM، إلّا أنّ هذه المقارنات ستجعل معرفتك بـAngularJS أكثر ضبابية، لذا سأطلب منك في هذا الفصل فقط أن تتوقف عن النظر إلى AngularJS على أنها إطار عمل للغة JavaScript، كما أرجو منك أن تتخلّى في البداية عن رغبتك في فهم آلية عمل Angular وأن تحاول أخذها كما هي دون تفاصيل، فلتعتبرها مجموعة قوية من التطويرات لـHTML. سنبدأ دروسنا مع البنى الثّلاث الأساسيّة في AngularJS : العبارات expressions، التّوجيهات directives والمجالات scopes ولكننا سنبدأ قبل ذلك بالتعرف على كيفية تشغيل Angular في صفحة ويب. التثبيتيمكنك جلب Angular من الموقع الرسمي ثم تحميلها إلى صفحتك عن طريق المكاتب المستضافة عند Google (كـCDN) وهي طريقة ملائمة وتعمل جيدا، وقد قمنا بتشغيل شيفرات Angular عن طريق إضافة الاستدعاء التّالي في رأس ملف HTML: index.html <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>والآن بإمكاننا البدء معًا. بعد تضمينك لملف المكتبة في رأس الصفحة ستكون الخطوة الأولى تحديد الجزء الذي ستعالجه Angular داخل مستند HTML، تذكر دومًا أن Angular موجّهة نحو HTML أكثر من توجهها نحو JavaScript، فمبدؤها العام هو: بدلا من كتابة شيفرة JavaScript ثم تنفيذها، نقوم بكتابة خصائص HTML غير معياريّة تفهمها Angular وتقوم بمعالجتها. وهنا سنتعرّف على الخاصّية الأولى ng-app التي يمكن إضافتها إلى أي عنصر من عناصر مستند HTML، وقد اخترنا وضعها كخاصية للعنصر body في مثالنا هذا، فعند وضعها في body أو في html ستقوم Angular بمعالجة المستند كاملًا بحثًا عن تعليماتها لتقوم بتنفيذها، وبالطّبع يمكنك تحديد مجال أضيق كفقرة أو عنوان فقط، إن أردت أن تستعمل بيئة عمل أخرى إلى جانب Angular أو تحميل عدّة تطبيقات لـAngular في نفس المستند. إذًا لنكتب في المستند الذي نعمل عليه الشيفرة التالية: index.html <html> <head> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script> </head> <body ng-app> <!-- كل الأمثلة توضع هنا --> </body> </html> بهذه الخاصية السحرية سنتمكن من جعل Angular تعالج تعليماتها الموجودة داخل الصفحة، والآن لنتعرف على إمكانيات هذه المكتبة وما الذي يمكنها فعله. العبارات Expressionsتحذير: إذا كنت تتبنّى فكرة إخفاء شفرات جافا سكريبت فالمثال التالي سيثير حفيظتك حول Angular فهي تعتمد على كتابة عبارات شبيهة بعبارات JavaScript داخل نصوص HTML. تشمل عبارات Angular أي عبارة بسيطة وصحيحة في JavaScript إلّا أنّها لا تسمح بتعليمات التّحكم بسير البرنامج مثل الحلقات وغيرها، وأرجو منك تأجيل أحكامك المسبقة بينما نستكشف الأمور المسموحة (والتي يمكن ألا تكون مستحسنة) في Angular بطرق التّجريب التّقليدية. لنبدأ بجمع عددين إلى بعضهما: <p>The number {{11 + 12}}.</p>الناتج The number 23.هيا انطلق، قم بتغيير 12+11 في المثال السابق إلى أي عبارة رياضيّة أخرى، جرب إن كان بإمكانك إيجاد شيء لا يمكن لـAngular معالجته (عندما تفشل Angular في معالجة عبارة ما، فإنّها تعرض السلسلة الأصلية دون تغيير، أو لا تعرض شيئا في بعض الحالات الأخرى). الأقواس المتعرجة المزدوجة هي التي تحدد قالب Angular، وقد تكون قد شاهدتها من قبل لو تعاملت مع Mustache أو مع Handlebars، أحيانًا يمكنك التّفكير في Angular بانها مكتبة قوالب شديدة التعقيد، فهي تتعامل مع كل شيءٍ داخل العنصر الذي قمت بإضافة الخاصية ng-app إليه على أنه قالب وتقوم بترجمته عند تحميل الصّفحة، ثم تقوم بإعادة عملية الإخراج كلما حدث تغيير على البيانات (لا تقلق إن لم يكن مفهوم القوالب مألوفًا لك فسوف نتطرّق إلى مبادئه لاحقًا). ماذا عن اختبار المساواة لقيمتين؟ بالطّبع يمكننا القيام بهذا أيضًا. <p>Are strings and numbers equal? {{'2' == 2}}</p>الناتج Are strings and numbers equal? trueالنّتيجة موافقةٌ لما هي عليه في JavaScript، إذا كنت متفاجئًا بناتج عمليّة المقارنة السّابقة فقم بمراجعةٍ سريعة لعملية المقارنة ثم قم بتغيير == إلى === في المثال السّابق، لترى اختلاف النّتيجة. فيما يلي مثال لدمج سلسلتين نصيتين، ويوضح قدرتنا على الوصول إلى توابع مكتبة JavaScript المعيارية مثل توابع السلاسل النصية كالتابع toUpperCase. <code><p>{{"Angular" + "js".toUpperCase()}}</p></code>الناتج AngularJSهل تشعر الآن بأنّه بإمكانك القيام بأي شيء داخل عبارات Angular؟ لا تكن متسرّعًا يا شريك، وانظر إلى المثال التّالي: <!-- كود خاطئ! هذه الدالة ليست مسموح بها داخل عبارة --> <p>{{alert("Hello world")}}</p>لا يمكنك استخدام التّابع alert كما لا يمكنك الوصول إلى معظم الكائنات العامة مثل Math، Number و Date وهلم جرا. حاول استبدال ("alert("Hello world في المثال السّابق بـ ("parseInt("1 أو ()Date.now أو Number.Nan أو ()Math.random، إن لم تنتج أي مخرجات فهذا يعني أن Angular قد رفضت معالجة العبارة. لا بد من أن التساهل في مسألة دمج الشيفرة التنفيذية مع نصوص HTML قد أتعبك، وزاد عليه محدوديّة ما يمكنك القيام به داخل عبارات Angular، ولكن لا تتوقّف هنا فهناك الكثير بانتظارك، لنحاول الوصول إلى حدود إمكانيات العبارات في Angular، هل تظن بأنها تسمج بإسناد المتغيرات؟ <p>{{a = 1; a + a}}</p>الناتج 2إنها تعمل، ولكن حاول إضافة الكلمة المفتاحية var إلى اسم المتغير، فلن تقوم Angular بمعالجة العبارة وستصدر خطأ، فعندما ترى العبارة كما هي مع الأقواس المتعرجة فهذا يعني حدوث خطأ في الترجمة، لا تشعر بالاستياء، فتخريب الأشياء يكون أحيانًا وسيلة ممتازة للتعلم. هكذا تكون التجربة، لا أدري إن كان بالإمكان تعريف متغيّر في مجموعة من الأقواس المتعرّجة ثمّ استخدامه داخل مجموعة أخرى، لم لا نجرّب ذلك؟ <p>{{a = 1}} remains {{a}}</p>الناتج 1 remains 1إنّها تعمل أيضًا، ولكن ماذا لو نقلنا تهيئة المتغيّر إلى العبارة الثّانية؟ هيّا جرّبها، هل عملت؟ هل يمكنك معرفة السبب المحتمل وراء ذلك؟ حسنًا، العبارات تدعم عمليّة إسناد المتغيّرات، ما رأيك بتجربة معامل الشّرط الثلاثي؟ <p>There {{count = 1; (count == 1 ? "is " : "are ") + count}} of them.</p>الناتج There is 1 of them.من حسن حظّنا أنها تعمل أيضًا، فهذه العمليّة الثلاثية توفّر لنا تركيبًا مختصرا عمليًا داخل القوالب، والآن ماذا عن عملية الزّيادة "++"، هل يمكننا استخدامها؟ <p>{{a = 1; ++a}}</p>الناتج 1من الواضح أنّها لم تعمل، فهل هذا يعني بأنّ حلقة for غير موجودة في Angular، لم لا نستغني عن الكلمة var ونستبدل عملية الزّيادة "++" بجمع عادي. <p>{{for (i = 0; i < 10; i = i + 1) {i}}}</p>الناتج {{for (i = 0; i < 10; i = i + 1) {i}}}لم يتمّ تشغيل شيفرة الحلقة، كما أنّها سبّبت إصدار خطأ نحوي يمكن رؤيته من نافذة المتصفح console، رغم أنّ الحلقة ليس فيها أي خطأ ويمكن نسخها ولصقها في النافذة ليتم تشغيلها وستصل إلى العدد 9، جرّبها لتتأكّد بنفسك. إذًا لقد وصلنا إلى بعض الحدود، عبارات Angular ليست JavaScript، فـلغة العبارات لا تدعم الشروط، الحلقات ولا رمي الأخطاء واستلامها. يبدو أنّنا وصلنا إلى حدود العبارات، بالنّسبة لي فأنا أتقبّل وجود بعض شيفرات JavaScript داخل نص HTML إن كانت شيفرات مختصرة ومتعلقة بالعرض، لذا فأنا أقدّر الطبيعة المتساهلة لـAngular مع هذا الأمر، ولكن هذا لا يعني أنّني أشجع على كتابة شيفرات JavaScript أكبر من ذلك، فذلك سيتحوّل سريعًا إلى خربشات مربكة وغير مقروءة. من الجيّد أنّ العبارات لا تلعب إلّا دورًا بسيطًا في Angular، ويجب ألّا يكون هناك داع لكتابة الكثير من الشيفرات داخل العبارة الواحدة، فالمفتاح الحقيقي للإنتاجيّة المذهلة لـAngular هو التوجيه directive. التوجيهات Directivesالتّوجيهات هي روح Angular وقلبها النّابض، وأنصحك بأن تنظر إليها على أنها HTML معدلة، ففي كثير من الأحيان ترى أنها تشابه إلى حد كبير خصائص HTML التي يمكن استخدامها مع عناصر عاديّة ومألوفة. تبدأ التوجيهات المدمجة مع Angular عادة بالسابقة ng- التي تدل على الحرفين الثاني والثالث من كلمة A**ng**ular، كما أنّك ستجد عددًا هائلًا من التّوجيهات التي برمجها طرف ثالث متاحة للاستخدام. أول ما عليك التفكير به عندما تواجه شيفرة JavaScript مطولة داخل أحد التعبيرات في Angular هو أنه لا بد من وجود توجيه يقوم بذلك. لقد قمنا باستخدام أحد التّوجيهات مسبقًا بالفعل، هل تذكر التّوجيه الذي يجعل Angular تعالج محتويات العنصر body، إنه التوجيه ng-app، ففي هذه الحالة استخدمنا التوجيه دون تمرير أي وسطاء إليه، ولتمرير الوسطاء إلى التوجيهات ما عليك سوى استخدام "=" كما تقوم بإسناد القيم إلى خصائص HTML تمامًا. يمكنك على سبيل المثال تمرير اسم التّطبيق كوسيط "ng-app="myApp. (اسم التطبيق يشكل فعليا اسم وحدة وهي جزء هام من بنية Angular، وسنقوم بتغطيته في فصل الوحدات) ng-bindتقبل بعض التوجيهات تمرير سلاسل نصية تحوي عبارات لتقوم بتفيذها، (يمكنك التعرف على محددات كل توجيه من التوجيهات بزيارة توثيق API الخاصّبه)، فمثلًا يقوم التوجيه ng-bind بمعالجة العبارات وإخراجها، تمامًا كالأقواس المتعرّجة المزدوجة التي رأيناها في الفقرة السابقة، والمثال التالي يوضح استخدامه: <p>The number <span ng-bind="11 + 12"></span>.</p>الناتج The number 23.قد تجد أنّ المثال السابق مطابق لأول مثال تطرّقنا إليه، إلا أنّ هناك فرقًا هاما هو أن ng-bind تسبب إخفاء العبارة ريثما تقوم Angular بالترجمة وعملية الإخراج، ولذلك فهي الطريقة الأكثر تفضيلًا لاستخدام العبارات في Angular، فأثناء فتح الصّفحة وترجمتها ثمّ إخراجها لن تظهر العبارات والأقواس المتعرّجة، ولك أن تتخيّل شكل صفحة مليئةٍ بالعبارات قبل أن تنتهي عمليّة الإخراج خصوصًا إن كان هناك تأخير كبير بسبب كِبَرِ حجم الصّفحة وكثرة عباراتها. ربما ترغب في إخفاء كامل المحتوى أو جزءًا كبيرًا منه ريثما تنتهي عمليّة الإخراج بشكل كامل، وهذا ما تتيحه لك ng-cloak، لا بد من وجود توجيه يقوم بذلك. ng-initهل تذكر قيامنا بتهيئة المتغيّر في العبارة لنرى هل يعمل أم لا، حسنًا لا بدّ أن تحزر الآن، هناك توجيهٌ للقيام بهذا أيضًا، فالتّوجيه ng-init يسمح لك بتهيئة المتغيّرات لاستخدامها في أي مكان داخل العنصر الذي طبقت هذا التوجيه عليه. <div ng-init="sum = 3 + 2"> <p> {{sum + 1}} is more than <span ng-bind='sum - 1'></span> </p> </div>الناتج 6 is more than 4كما يمكنك استخدام الفاصلة المنقوطة لتعريف عدّة متغيّرات داخل توجيه ng-init كما يوضّح المثال التّالي: <div ng-init="count = 7; units = 'days'; collection = 'week'"> <p> There are {{count}} {{units}} in a {{collection}}. </p> </div>الناتج There are 7 days in a week.وقد تجد عند مرحلة ما أنه من الضروري تنظيم متغيّراتك داخل كائنات. <div ng-init="time = {count: 12, units: 'months', collection: 'year'}"> <p> There are {{time.count}} {{time.units}} in a {{time.collection}}. </p> </div>الناتج There are 12 months in a year.والمصفوفات مفيدة أيضًا. <div ng-init="months = ['January','February','March','April']"> <p> {{months[3]}} follows {{months[2]}} in {{months}}. </p> </div>الناتج April follows March in ["January","February","March","April"].انتبه، فلا مجال للخطأ في أسماء العناصر داخل الكائن، فـAngular لا تساعدك في تحديد هذا الخطأ وكلّ ما تقوم به هو عدم ترجمة العبارة وتجاهلها، وكذلك الحال في أخطاءِ الوصول إلى عناصر كائنٍ غير موجود أصلًا والوصول إلى عناصر خارج حدود المصفوفة. <div ng-init="paragraph = {sentence: {words: ['first','second','third']}}"> <p> "{{typo.sentence.words[0]}}", "{{paragraph.typo.words[1]}}", "{{paragraph.sentence.typo[2]}}", "{{paragraph.sentence.words[3]}}". </p> </div>الناتج "", "", "", "".كنت أتمنّى لو أن Angular تحتوي على عملية اختبار الوجود كالموجودة في CoffeeScript لجعل التّساهل مع هذه الأخطاء خيارًا وليس القاعدة. والآن بعد أن رأينا سماحيّات Angular هل تتوقع بأنها تسمح بتعريف التوابع داخل ng-init؟ ما الذي تتوقThere are months in a year.عه؟ <div ng-init="count = function() { return 12; }"> <p> There are {{count()}} months in a year. </p> </div>الناتج There are months in a year.لا، لا تسمح Angular بعبارات تعريف التوابع، كما أنها تقوم برمي خطأ parse:syntax$ أثناء معالجة الوسيط الممرّر للتوجيه ng-init، ويمكنك رؤيته في نافذة المتصفح console، فالمتحكم هو المكان الصحيح لتعريف التوابع لاستخدامها في عبارات Angular، وسنتعرف عليها في فصل المتحكمات وهي في الحقيقة المكان المناسب لتحضير جميع بيانات التطبيق للعرض، أمّا التّوجيه ng-init فوظيفته الأساسية هي تغيير أسماء المتغيرات لسبب سنراه لاحقًا في فصل المجموعات. ng-non-bindableيمكن استخدام التوجيه ng-non-bindable لمنع Angular من معالجة جزء من المستند، كما يوضح المثال التالي: <p ng-non-bindable> {{2 * 2}} is the same as <span ng-bind='2 + 2'>?</span> </p>الناتج {{2 * 2}} is the same as ?جرب إزالة التوجيه السابق لترى ما سيحدث. ng-showحان الوقت للتّعرف على أمور أكبر من مجرّد معالجة وإخراج ناتج عبارة، سؤالٌ للمبتدئين: كيف يمكننا إظهار وإخفاء عنصر HTML حسب شرطٍ ما؟ احتجت مرة لإخفاء نموذج استمارةٍ بعد أن يقوم المستخدم بملئها بنجاح، فقمت بإضافة استدعاء للتابع jQuery.hide داخل متحكّم قمت بإنشائه في Angular، لقد عمل بشكل صحيح رغم أن تلك الطريقة ليست هي الأفضل للقيام بذلك، ولكن الرغبة في إنهاء المشروع سريعًا دفعتني لتجنب البحث عن التّوجيه الّذي يقوم بذلك في Angular، وبالفعل وجدت توجيهين للقيام بذلك لا واحدًا فقط، ng-hide وng-show وسيبيّن المثال التالي استخدامهما معًا: <p ng-init="authorized = true"> The secret code is <span ng-show="authorized">0123</span> <span ng-hide="authorized">not for you to see</span> </p>الناتج The secret code is 0123جرّب تغيير true إلى false في المثال السابق لترى ما سيحدث، إنّ Angular تراقب التحديثات الّتي تطرأ على المتغيرات طوال الوقت، ولذلك فسيؤدّي تغيير المتغير authorized في أي مكان إلى تغيير نتيجة المثال السابق في العرض. أرى أنه يمكننا التعمق الآن في الحديث عن المتغيرات لنعرف ماهيّتها الحقيقيّة. المجالات Scopesتُعدّ المجالات مصدر البيانات الوحيد داخل تطبيقك، والفكرة من ورائها هي أنّه مهما كان عدد الأماكن الّتي تسخدم فيها المتغيّر في طبقة العرض، يجب عليك الالتزام بموضع واحدٍ لتغيير قيمته ويجب أن ينتشر هذا التغيير تلقائيًا إلى أماكن استخدام هذا المتغيّر في العرض. بما أنّ عملية الإخراج وإعادة الإخراج تحتاج بنيةً تحتيّة، فأكثر ما ستلاحظه في كائنات JavaScript الّتي تضعها في مجالات Angular هو كم هي مألوفة واعتياديّة بالنسبة لك. قد تكون ملمًّا بمفهوم كائنات نموذج المجال القديمة البسيطة، والذي أصله كائنات Java القديمة البسيطة أو ما يُعرف اختصارًا لـPOJO، فعندما قام Martin Fowler بشرح مفهوم POJO للمرّة الأولى، كان يعني الكائنات الأساسيّة الاعتياديّة التي يقدّمها قلب اللغة في وقت التشغيل runtime، على عكس كائنات نموذج المجال domain model المعقّدة الّتي ترث قدرات خاصّة من فئة متفوّقة superclass في إطار العمل. لقد تمّ توسيع هذا المصطلح ليشمل الكائنات التي تبدو بأنّها بسيطة إلّا أنّ إطار العمل يحسّنها بإضافة قدرات خاصّة بشكل خفي في وقت التشغيل بشكل مستمر عادة. الكائنات المرتبطة بمجالات Angular هي POJOs خاصة بـJavaScript، ويبدو أن Angular ستصبح حقا إطار عمل جافا سكربت البسيطة القديمة عندما سينتشر استخدام الميزة الجديدة Object.observe بين أوساط المبرمجين، وذلك لأنّها تقوم بعمليات معقدة للتّعرف على التّغييرات أثناء وقت التشغيل لتسمح بانتشار تأثير هذه التّغييرات. لقد مررنا مسبقاً على العديد من خصائص المجالات scope properties في هذا الفصل، هل تذكر المتغيّر الّذي قمنا بتهيئته داخل العبارات واستخدمنا ng-init في المثال أعلاه، كلّ هذه كانت من خصائص المجالات، هل تذكر محاولة إضافة الكلمة المفتاحية var إلى إحدى العبارات؟ إنّ استخدامها ممنوعٌ لأنّ ما كنت أقول بأنه متغيّرٌ لم يكن كذلك في الحقيقة(وأعتذر لأنّني كذبت بشأنه) فهو في الحقيقة أحد الخصائص في كائن المجال، يتمّ إنشاؤه تلقائياً خلف الكواليس وسنغطّي في الدّرس القادم المجالات بتفاصيل أعمق، وسنكتفي هنا ببعض الأمثلة الّتي تبين كيفيّة استخدام خصائص المجالات. الربط ثنائي الاتجاهكل الأمثلة السّابقة كانت تستخدم ربطًا أحادي الاتجاه، حيث يتم تحديث بيانات المجال باستمرار في العرض view، وسيزداد الأمر إثارةً عندما نستخدم HTML للتّحكم بهذه البيانات وتغييرها، ومعًا ستشكّل هاتان الطّريقتان في تغيير البيانات الربط ثنائيّ الاتّجاه كمثال مبدئي أوّل، سنستخدم التّوجيه ng-click للتعديل على خاصّية بوليانية boolean. <p ng-init="authorized = true"> The secret code is <span ng-show="authorized">0123</span> <span ng-hide="authorized">not for you to see</span> <br> <input type="button" value="toggle" ng-click="authorized = !authorized"> </p> يمكن أن يكون الوسيط الممرّر للتوجيه ng-click أي عبارة، ورغم أنّ هذا التّوجيه غير محدود بالعمل ضمن خصائص المجالات، إلا أن الشّيفرة الّتي كتبناها لقلب القيمة البوليانية تعمل بشكل صحيح لأنها بسيطةٌ جدًّا، ولكن كيف يمكننا ربط أدوات الدّخل الأخرى والتّعامل مع أنواع متغيّرات أكثر تعقيدًا؟ ng-modelيبدو أنّ جميع شروحات Angular لربط البيانات تسخدم مثالًا لربط دخلٍ نصّيّ بخاصّيةٍ من نوع سلسلةٍ نصّيّة، وها قد حان دورنا لكتابة هذا المثال. <input type="text" ng-model="title"> is bound to "<strong ng-bind="title"></strong>". Type something in the box!هذا المثال هو الطّريقة الأمثل لتوضيح مسألة الربط ثنائيّ الاتجاه، فعندما نضيف التّوجيه ng-model إلى عنصر input الخاصّ بـHTML، فإنّ Angular تقوم بتغليف تحكّم HTML به عن طريق توجيه input الخاصّ بها. ما الذي يمكننا القيام به أكثر من هذا؟ بإمكاننا دومًا استخدام ng-init لتهيئة قيمة ابتدائية للخاصية. <input type="text" ng-init="title = 'Angular'" ng-model="title"> is bound to "<strong ng-bind="title"></strong>". Change it!لنجرّب الآن بعض أدوات الإدخال الأخرى، لنبدأ بـcheckbox. <input type='checkbox' ng-init='optIn = true' ng-model='optIn'> is bound to <strong ng-bind="optIn"></strong>. Change it!القيمة المرتبطة بصندوق checkbox يمكن أن تكون سلسلةً نصّيّةً بدلًا من كونها قيمة بوليانيّة، وذلك بتأهيل قيمة كلٍ من التّوجيهين ng-true-value وng-false-value. <input type='checkbox' ng-init='feeling = "love it"' ng-model='feeling' ng-true-value='"love it"' ng-false-value='"hate it"'> is bound to "<strong ng-bind="feeling"></strong>". Change it!هل يمكنك توقّع وظيفة التّوجيه select في Angular؟ لاحظ أنّ Angular تتجاهل تمامًا وجود الخاصّية selected في الخيارات المقدّمة، وحتّى لو قمت بإزالة التّوجيه ng-init فستبقى بلا تأثير. <select ng-init='answer = "maybe"' ng-model='answer'> <option value='yes' selected>Yes</option> <option value='no'>No</option> <option value='maybe'>Maybe</option> </select> is bound to "<strong ng-bind="answer"></strong>". Change it!يبدو أنّ المثال السّابق يناسبه وجود radio buttons أكثر من قائمة الخيارات، هيّا نغيّره إذًا. <div ng-init='answer = "maybe"'> <input type='radio' ng-model='answer' value='yes'> Yes <input type='radio' ng-model='answer' value='no'> No <input type='radio' ng-model='answer' value='maybe'> Maybe is bound to "<strong ng-bind="answer"></strong>". Change it! </div>لديك الآن بعض خبرات الواقع العمليّ في التّعامل مع بيانات المجالات باستخدام توجيهات Angular، ولا يزال هناك الكثير من التّوجيهات المدمجة في Angular، وسنتعرف على العديد منها في الفصول القادمة. خلاصةلقد طلبت منك في مطلع هذا الفصل ألا تنظر إلى Angular على أنها إطار عمل لـJavaScript، بل كامتداد لـHTML، فكما أخبَرَنا Miško Hevery مخترع Angular في هذه المقابلة الرائعة فإن الهدف الأساسي من Angular هو أن تكون طريقة لزيادة إنتاجية مطوري الـ front-end لتحسين صفحات الويب، وقد صارت لاحقًا إطار عمل لـJavaScript. لقد تم الانطلاق من فكرة كون نمط البرمجة التصريحية هو أفضل الخيارات لتسريع عملية تطوير واجهات المستخدم الرسومية GUI، وقد اختار مطوروها ألا تكون لغة محصورة في مجال محدد مثل ما كانت MXML وXUL بل أرادوا جعلها تعتمد على تشابهها الكبير مع نصوص HTML. لقد رأينا بما يكفي أن Angular لا تستخدم دون JavaScript معدلة، كما أن التطبيقات في الواقع العملي في Angular تستخدم دومًا المتحكمات والخدمات والمسرات كما تدعم الوحدات وحقن التابعية إضافة إلى اعتمادها على البنية التحتية لـAjax، وسنغطي في هذه السلسلة جميع هذه الأمور، وعلى أي حال فإن وعد الإنتاجية الذي تقدمه Angular يمكن تحقيقه في فضاء تصريحي، مع درجة من الاعتماد على لا بد من وجود توجيه يقوم بذلك، كما أنّ كتابة توجيهات مخصصة لملء الفجوات هو الأمر الأكثر تحدّيًا في Angular، وهذا ما سيحاول الفصل الأخير إيصاله ليستقرّ في ذهن القارئ. والآن بعد أن استوعبت أن الهدف الأسمى لـAngular هو إيجاد امتدادات لـHTML لزيادة الإنتاجيّة وليس كتابة شيفرات JavaScript داخل إطار عمل، فأنت الآن جاهز لتبحر في تفاصيلها مع الفصول القادمة. ترجمة وبتصرّف للفصل الثاني من كتاب: Angular Basics لصاحبه: Chris Smith.1 نقطة