برنامج تعليمي خطوة بخطوة لأول تطبيق AngularJS
نشرت: 2022-03-11ما هو AngularJS؟
AngularJS هو إطار عمل JavaScript MVC تم تطويره بواسطة Google ويتيح لك إنشاء تطبيقات أمامية جيدة التنظيم وقابلة للاختبار ويمكن صيانتها.
ولماذا يجب علي استخدامه؟
إذا لم تكن قد جربت AngularJS حتى الآن ، فأنت في عداد المفقودين. يتكون إطار العمل من مجموعة أدوات متكاملة بإحكام ستساعدك على إنشاء تطبيقات غنية التنظيم من جانب العميل بطريقة معيارية - مع تعليمات برمجية أقل ومرونة أكبر.
تقوم AngularJS بتوسيع HTML من خلال توفير التوجيهات التي تضيف وظائف للترميز الخاص بك وتسمح لك بإنشاء قوالب ديناميكية قوية. يمكنك أيضًا إنشاء توجيهاتك الخاصة ، وصياغة مكونات قابلة لإعادة الاستخدام تلبي احتياجاتك وتلغي كل منطق معالجة DOM.
كما يقوم بتنفيذ ربط البيانات ثنائي الاتجاه ، وربط HTML (طرق العرض) بكائنات JavaScript (النماذج) بسلاسة. بعبارات بسيطة ، هذا يعني أن أي تحديث يتم إجراؤه على نموذجك سينعكس على الفور في طريقة العرض الخاصة بك دون الحاجة إلى أي تلاعب في DOM أو معالجة الحدث (على سبيل المثال ، مع jQuery).
أخيرًا ، أحب Angular نظرًا لمرونته فيما يتعلق بالاتصال بالخادم. مثل معظم أطر عمل JavaScript MVC ، فإنه يتيح لك العمل مع أي تقنية من جانب الخادم طالما يمكنها خدمة تطبيقك من خلال واجهة برمجة تطبيقات الويب RESTful. لكن Angular توفر أيضًا خدمات أعلى XHR تعمل على تبسيط التعليمات البرمجية بشكل كبير وتسمح لك بتجريد استدعاءات واجهة برمجة التطبيقات في خدمات قابلة لإعادة الاستخدام. نتيجةً لذلك ، يمكنك نقل نموذجك ومنطق عملك إلى الواجهة الأمامية وإنشاء تطبيقات ويب محايدة للجهة الخلفية. في هذا المنشور ، سنفعل ذلك ، خطوة بخطوة.
إذن ، من أين أبدأ؟
أولاً ، دعنا نقرر طبيعة التطبيق الذي نريد بناءه. في هذا الدليل ، نفضل عدم قضاء الكثير من الوقت في الخلفية ، لذلك سنكتب شيئًا بناءً على البيانات التي يمكن الوصول إليها بسهولة على الإنترنت - مثل تطبيق موجز رياضي!
نظرًا لأنني من أشد المعجبين بسباق السيارات والفورمولا 1 ، فسوف أستخدم خدمة واجهة برمجة التطبيقات لرياضة السيارات لتكون بمثابة النهاية الخلفية لدينا. لحسن الحظ ، فإن الرجال في Ergast طيبون بما يكفي لتوفير واجهة برمجة تطبيقات مجانية لرياضة السيارات ستكون مثالية بالنسبة لنا.
للحصول على لمحة سريعة عما سنبنيه ، ألق نظرة على العرض التوضيحي المباشر. لتجميل العرض التوضيحي وإظهار بعض القوالب الزاويّة ، قمت بتطبيق سمة Bootstrap من WrapBootstrap ، لكن نظرًا لأن هذه المقالة لا تتعلق بـ CSS ، سأقوم فقط بتجريدها بعيدًا عن الأمثلة وتركها.
برنامج تعليمي للشروع في العمل
لنبدأ تطبيق المثال الخاص بنا ببعض النماذج المعيارية. أوصي بمشروع البذرة الزاوية لأنه لا يوفر لك هيكلًا رائعًا للتمهيد فحسب ، بل يهيئ أيضًا الأرضية لاختبار الوحدة مع Karma و Jasmine (لن نقوم بأي اختبار في هذا العرض التوضيحي ، لذلك سنقوم فقط اترك هذه الأشياء جانباً في الوقت الحالي ؛ راجع الجزء 2 من هذا البرنامج التعليمي لمزيد من المعلومات حول إعداد مشروعك للوحدة والاختبار الشامل).
تحرير (مايو 2014): منذ أن كتبت هذا البرنامج التعليمي ، مر مشروع البذرة الزاوية ببعض التغييرات الثقيلة (بما في ذلك إضافة Bower كمدير للحزم). إذا كانت لديك أي شكوك حول كيفية نشر المشروع ، فقم بإلقاء نظرة سريعة على القسم الأول من دليلهم المرجعي. في الجزء الثاني من البرنامج التعليمي ، تمت تغطية Bower بمزيد من التفصيل من بين أدوات أخرى.
حسنًا ، الآن بعد أن استنسخنا المستودع وقمنا بتثبيت التبعيات ، سيبدو الهيكل العظمي لتطبيقنا كما يلي:
الآن يمكننا البدء في البرمجة. نظرًا لأننا نحاول إنشاء موجز رياضي لبطولة السباق ، فلنبدأ بالرأي الأكثر صلة: طاولة البطولة .
نظرًا لأن لدينا بالفعل قائمة برامج تشغيل محددة في نطاقنا (توقف معي - سنصل إلى هناك) ، وتجاهل أي CSS (لسهولة القراءة) ، قد يبدو HTML الخاص بنا كما يلي:
<body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>
أول شيء ستلاحظه في هذا النموذج هو استخدام التعبيرات ("{{" و "}}") لإرجاع قيم المتغيرات. في تطوير AngularJS ، تسمح لك التعبيرات بتنفيذ بعض العمليات الحسابية لإرجاع القيمة المطلوبة. بعض التعبيرات الصحيحة هي:
-
{{ 1 + 1 }}
-
{{ 946757880 | date }}
-
{{ user.name }}
بشكل فعال ، التعبيرات هي مقتطفات تشبه جافا سكريبت. لكن على الرغم من كونك قويًا جدًا ، لا يجب عليك استخدام التعبيرات لتنفيذ أي منطق ذي مستوى أعلى. لذلك ، نستخدم التوجيهات.
فهم التوجيهات الأساسية
الشيء الثاني الذي ستلاحظه هو وجود ng-attributes
، والتي لن تراها في الترميز المعتاد. هذه توجيهات.
على مستوى عالٍ ، فإن التوجيهات عبارة عن علامات (مثل السمات والعلامات وأسماء الفئات) تخبر AngularJS بإرفاق سلوك معين بعنصر DOM (أو تحويله ، واستبداله ، وما إلى ذلك). دعونا نلقي نظرة على تلك التي رأيناها بالفعل:
توجيه
ng-app
مسؤول عن تمهيد تطبيقك وتحديد نطاقه. في AngularJS ، يمكن أن يكون لديك العديد من التطبيقات داخل نفس الصفحة ، لذا فإن هذا التوجيه يحدد مكان بدء كل تطبيق مميز ونهايته.يحدد التوجيه
ng-controller
أي وحدة تحكم ستكون مسؤولة عن العرض الخاص بك. في هذه الحالة ، نشير إلىdriversController
، والذي سيوفر قائمة السائقين الخاصةdriversList
(قائمة السائقين).يعد التوجيه
ng-repeat
replic أحد أكثر التوجيهات استخدامًا ويعمل على تحديد نطاق القالب الخاص بك عند التكرار عبر المجموعات. في المثال أعلاه ، يكرر سطرًا في الجدول لكل محرك فيdriversList
.
مضيفا وحدات تحكم
بالطبع ، لا فائدة من وجهة نظرنا بدون وحدة تحكم. دعنا نضيف driversController
إلى وحدات التحكم الخاصة بنا:
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });
ربما لاحظت متغير $scope
الذي نمرره كمعامل إلى وحدة التحكم. من المفترض أن يقوم متغير $scope
بربط وحدة التحكم الخاصة بك وطرق العرض. على وجه الخصوص ، يحتوي على جميع البيانات التي سيتم استخدامها داخل القالب الخاص بك. أي شيء تضيفه إليه (مثل قائمة driversList
في المثال أعلاه) سيكون متاحًا بشكل مباشر في طرق العرض الخاصة بك. في الوقت الحالي ، دعنا نعمل فقط مع مصفوفة بيانات وهمية (ثابتة) ، والتي سنستبدلها لاحقًا بخدمة API الخاصة بنا.
الآن ، أضف هذا إلى app.js:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);
باستخدام هذا السطر من التعليمات البرمجية ، نقوم بالفعل بتهيئة تطبيقنا وتسجيل الوحدات التي يعتمد عليها. سنعود إلى هذا الملف ( app.js
) لاحقًا.
الآن ، دعنا نجمع كل شيء معًا في index.html
:
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
أخطاء Modulo البسيطة ، يمكنك الآن تشغيل التطبيق الخاص بك والتحقق من قائمة برامج التشغيل (الثابتة).
ملاحظة: إذا كنت بحاجة إلى مساعدة في تصحيح أخطاء تطبيقك وتصور النماذج والنطاق داخل المتصفح ، فإنني أوصي بإلقاء نظرة على مكون Batarang الإضافي الرائع لمتصفح Chrome.
تحميل البيانات من الخادم
نظرًا لأننا نعرف بالفعل كيفية عرض بيانات وحدة التحكم الخاصة بنا في وجهة نظرنا ، فقد حان الوقت لجلب البيانات الحية فعليًا من خادم RESTful.
لتسهيل الاتصال بخوادم HTTP ، توفر AngularJS خدمات $resource
$http
و $. الأول ليس سوى طبقة أعلى XMLHttpRequest أو JSONP ، بينما يوفر الأخير مستوى أعلى من التجريد. سنستخدم $http
.
لاستخراج استدعاءات واجهة برمجة تطبيقات الخادم الخاصة بنا من وحدة التحكم ، دعنا ننشئ خدمتنا المخصصة التي ستجلب بياناتنا وتعمل كغلاف حول $http
عن طريق إضافة هذا إلى services.js
لدينا:
angular.module('F1FeederApp.services', []). factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
باستخدام أول سطرين ، نقوم بإنشاء وحدة نمطية جديدة ( F1FeederApp.services
) ونسجل خدمة داخل تلك الوحدة ( ergastAPIservice
). لاحظ أننا نقوم بتمرير $http
كمعامل لهذه الخدمة. هذا يخبر محرك حقن التبعية في Angular أن خدمتنا الجديدة تتطلب (أو تعتمد على ) خدمة $http
.
بطريقة مماثلة ، نحتاج إلى إخبار Angular بتضمين وحدتنا الجديدة في تطبيقنا. فلنقم بتسجيله في app.js
، مع استبدال الكود الحالي بـ:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);
الآن ، كل ما نحتاج إلى القيام به هو تعديل controller.js
الخاصة بنا قليلاً ، بما في ذلك ergastAPIservice
كتبعية ، وسنكون على ما يرام:
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; ergastAPIservice.getDrivers().success(function (response) { //Dig into the responde to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); });
الآن أعد تحميل التطبيق وتحقق من النتيجة. لاحظ أننا لم نجري أي تغييرات على نموذجنا ، لكننا أضفنا متغير nameFilter
إلى نطاقنا. دعونا نستخدم هذا المتغير.

المرشحات
رائعة! لدينا وحدة تحكم وظيفية. لكنه يظهر فقط قائمة السائقين. دعنا نضيف بعض الوظائف من خلال تنفيذ إدخال بحث نصي بسيط يقوم بتصفية قائمتنا. دعنا نضيف السطر التالي إلى index.html
، أسفل علامة <body>
مباشرة:
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
نحن الآن نستخدم التوجيه ng-model
. يربط هذا التوجيه حقل النص الخاص بنا بالمتغير $scope.nameFilter
من أن قيمته محدثة دائمًا مع قيمة الإدخال. الآن ، دعنا نزور index.html مرة أخرى ونجري تعديلًا بسيطًا على السطر الذي يحتوي على الأمر ng-repeat
:
<tr ng-repeat="driver in driversList | filter: nameFilter">
يخبر هذا السطر ng- ng-repeat
أنه قبل إخراج البيانات ، يجب تصفية مصفوفة driversList
حسب القيمة المخزنة في nameFilter
.
في هذه المرحلة ، يبدأ ربط البيانات ثنائي الاتجاه: في كل مرة يتم فيها إدخال قيمة في حقل البحث ، يضمن Angular على الفور $scope.nameFilter
الذي ربطناه به بالقيمة الجديدة. نظرًا لأن الربط يعمل في كلا الاتجاهين ، في اللحظة التي يتم فيها تحديث قيمة nameFilter
، يحصل التوجيه الثاني المرتبط به (أي ng- ng-repeat
) أيضًا على القيمة الجديدة ويتم تحديث العرض على الفور.
أعد تحميل التطبيق وتحقق من شريط البحث.
لاحظ أن هذا المرشح سيبحث عن الكلمة الأساسية في جميع سمات النموذج ، بما في ذلك السمات التي لم تكن تستخدم. لنفترض أننا نريد فقط التصفية حسب Driver.givenName
و Driver.familyName
: أولاً ، نضيف إلى driversController
، أسفل $scope.driversList = [];
خط:
$scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };
الآن ، بالعودة إلى index.html
، نقوم بتحديث السطر الذي يحتوي على الأمر ng-repeat
:
<tr ng-repeat="driver in driversList | filter: searchFilter">
قم بإعادة تحميل التطبيق مرة أخرى والآن لدينا بحث بالاسم.
طرق
هدفنا التالي هو إنشاء صفحة تفاصيل السائق والتي ستتيح لنا النقر فوق كل سائق والاطلاع على تفاصيل حياته المهنية.
أولاً ، لنقم بتضمين خدمة $routeProvider
(في app.js
) التي ستساعدنا في التعامل مع مسارات التطبيق المتنوعة هذه. بعد ذلك ، سنضيف مسارين من هذا القبيل: أحدهما لجدول البطولة والآخر لتفاصيل السائق. هنا app.js
الجديد:
angular.module('F1FeederApp', [ 'F1FeederApp.services', 'F1FeederApp.controllers', 'ngRoute' ]). config(['$routeProvider', function($routeProvider) { $routeProvider. when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}). when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}). otherwise({redirectTo: '/drivers'}); }]);
مع هذا التغيير ، سيؤدي الانتقال إلى http://domain/#/drivers
إلى تحميل driversController
والبحث عن العرض الجزئي لعرضه في الجزئية partials/drivers.html
. لكن انتظر! ليس لدينا أي آراء جزئية حتى الآن ، أليس كذلك؟ سنحتاج إلى إنشاء هؤلاء أيضًا.
طرق عرض جزئية
سيسمح لك AngularJS بربط مساراتك بوحدات تحكم وطرق عرض محددة.
لكن أولاً ، علينا إخبار Angular بمكان تقديم هذه العروض الجزئية. لذلك ، سنستخدم التوجيه ng-view
، مع تعديل index.html
ما يلي:
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
الآن ، عندما نتنقل عبر مسارات التطبيق الخاصة بنا ، سيقوم Angular بتحميل العرض المرتبط وعرضه بدلاً من علامة <ng-view>
. كل ما نحتاج إلى القيام به هو إنشاء ملف باسم partials/drivers.html
ووضع HTML لجدول البطولة الخاص بنا هناك. سنستخدم هذه الفرصة أيضًا لربط اسم السائق بمسار تفاصيل السائق:
<input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>
أخيرًا ، دعنا نقرر ما نريد إظهاره في صفحة التفاصيل. ماذا عن ملخص لجميع الحقائق ذات الصلة بالسائق (على سبيل المثال ، الميلاد والجنسية) مع جدول يحتوي على نتائجه الأخيرة؟ للقيام بذلك ، نضيف إلى services.js
:
angular.module('F1FeederApp.services', []) .factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
هذه المرة ، نقدم معرف السائق للخدمة حتى نتمكن من استرداد المعلومات ذات الصلة فقط بسائق معين. الآن ، نقوم بتعديل controllers.js
:
angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });
الشيء المهم الذي يجب ملاحظته هنا هو أننا أدخلنا خدمة $routeParams
في وحدة تحكم السائق. ستسمح لنا هذه الخدمة بالوصول إلى معلمات URL الخاصة بنا (بالنسبة إلى :id
، في هذه الحالة) باستخدام $routeParams.id
.
الآن بعد أن أصبح لدينا بياناتنا في النطاق ، نحتاج فقط إلى العرض الجزئي المتبقي. لنقم بإنشاء ملف يسمى partials/driver.html
ونضيف:
<section> <a href="./#/drivers"><- Back to drivers list</a> <nav class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>
لاحظ أننا نضع الآن التوجيه ng-show
للاستخدام الجيد. سيعرض هذا التوجيه عنصر HTML فقط إذا كان التعبير المقدم true
(على سبيل المثال ، ليس false
ولا null
). في هذه الحالة ، لن تظهر الصورة الرمزية إلا بمجرد تحميل كائن برنامج التشغيل في النطاق بواسطة وحدة التحكم.
اللمسات الأخيرة
أضف مجموعة من CSS واعرض صفحتك. يجب أن تنتهى مع بعض الأشياء التى تحبها:
أنت الآن جاهز لتشغيل تطبيقك والتأكد من أن كلا المسارين يعملان بالشكل المطلوب. يمكنك أيضًا إضافة قائمة ثابتة إلى index.html
لتحسين إمكانيات التنقل للمستخدم. الاحتمالات لا حصر لها.
تحرير (مايو 2014): لقد تلقيت العديد من الطلبات للحصول على نسخة قابلة للتنزيل من التعليمات البرمجية التي أنشأناها في هذا البرنامج التعليمي. لذلك قررت إصداره هنا (تم تجريده من أي CSS). ومع ذلك ، لا أوصي حقًا بتنزيله ، نظرًا لأن هذا الدليل يحتوي على كل خطوة تحتاجها لإنشاء نفس التطبيق بيديك ، والذي سيكون تدريبًا تعليميًا أكثر فائدة وفعالية.
خاتمة
في هذه المرحلة من البرنامج التعليمي ، قمنا بتغطية كل ما تحتاجه لكتابة تطبيق بسيط (مثل وحدة تغذية Formula 1). تشترك كل صفحة من الصفحات المتبقية في العرض التوضيحي المباشر (على سبيل المثال ، جدول بطولة المُنشئ ، وتفاصيل الفريق ، والتقويم) في نفس البنية الأساسية والمفاهيم التي قمنا بمراجعتها هنا.
أخيرًا ، ضع في اعتبارك أن Angular إطار عمل قوي جدًا ، وبالكاد قمنا بخدش السطح من حيث كل ما تقدمه. في الجزء 2 من هذا البرنامج التعليمي ، سنقدم أمثلة على سبب تميز Angular بين أطر MVC الأمامية النظيرة: قابلية الاختبار. سنراجع عملية كتابة وتشغيل اختبارات الوحدة مع Karma ، وتحقيق التكامل المستمر مع Yeomen و Grunt و Bower ونقاط القوة الأخرى في إطار العمل الرائع هذا.