لكل مستند في نموذج "مستندات" نموذج البيانات لدينا. كل عنصر
يحتوي على رمز ملف ورابط لفتحه على الويب وآخر updateDate.
ملاحظة:  لجعل النموذج صالحًا بتنسيق HTML، نستخدم سمات data-* للغة Angular
يمكن تكرار ngRepeat ، ولكن لن نضطر إلى ذلك. يمكنك بسهولة كتابة المكرر
<li ng-repeat="doc in docs">.بعد ذلك، نحتاج إلى تحديد وحدة التحكّم التي ستشرف على عرض هذا النموذج في Angular. لذلك،
استخدام التوجيه ngController  لتوجيه DocsController بالسيطرة على النموذج
:
<body data-ng-controller="DocsController">
<section id="main">
  <ul>
    <li data-ng-repeat="doc in docs">
      <img data-ng-src=""> <a href=""></a> 
      <span class="date"></span>
    </li>
  </ul>
</section>
</body>
ما لا يظهر لك هنا هو ربط أدوات معالجة الأحداث أو المواقع الإلكترونية بالبيانات
الربط. يُجري Angular هذه المهمة الصعبة نيابةً عنا.
الخطوة الأخيرة هي جعل Angular تضيء نماذجنا. الطريقة النموذجية للقيام بذلك هي تضمين
ngApp  بالكامل في :
<html data-ng-app="gDriveApp">
يمكنك أيضًا تحويل التطبيق إلى جزء أصغر من الصفحة إذا أردت ذلك. لدينا فقط
وحدة تحكُّم واحدة في هذا التطبيق، ولكن إذا أضفنا المزيد لاحقًا، سنضع ngApp  في المقدمة
العنصر يجعل الصفحة بأكملها جاهزة لاستخدام Angular.
يبدو المنتج النهائي لـ main.html على النحو التالي:
<html data-ng-app="gDriveApp">
<head>
  …
  <base target="_blank">
</head>
<body data-ng-controller="DocsController">
<section id="main">
  <nav>
    <h2>Google Drive Uploader</h2>
    <button class="btn" data-ng-click="fetchDocs()">Refresh</button>
    <button class="btn" id="close-button" title="Close"></button>
  </nav>
  <ul>
    <li data-ng-repeat="doc in docs">
      <img data-ng-src=""> <a href=""></a>  
      <span class="date"></span>
    </li>
  </ul>
</section>
لمحة عن سياسة أمان المحتوى 
على عكس العديد من أُطر عمل JS MVC الأخرى، لا يتطلب الإصدار 1.1.0 أو الإصدارات الأحدث من Angular أي تعديلات للعمل ضمن نطاق
سياسة CSP : إنها طريقة سهلة الإعداد،
ومع ذلك، إذا كنت تستخدم إصدارًا قديمًا من Angular بين v1.0.1 وv1.1.0، يجب معرفة ذلك.
Angular للتشغيل في "وضع أمان المحتوى". ويتم ذلك من خلال تضمين التوجيه ngCsp .
إلى جانب ngApp :
<html data-ng-app data-ng-csp>
التعامل مع التفويض 
لا يتم إنشاء نموذج البيانات بواسطة التطبيق نفسه. وإنما تتم تعبئته من خلال واجهة برمجة تطبيقات خارجية (
Google Drive API). وبالتالي، هناك بعض العمل اللازم لتعبئة بيانات التطبيق.
قبل أن نتمكن من تقديم طلب بيانات من واجهة برمجة التطبيقات، يجب علينا جلب رمز OAuth المميز لحساب المستخدم على Google.
ولتحقيق ذلك، أنشأنا طريقة لإنهاء المكالمة إلى chrome.identity.getAuthToken() وتخزين
accessToken التي يمكننا إعادة استخدامها في الطلبات المستقبلية لواجهة Drive API.
GDocs . prototype . auth   =   function ( opt_callback )   { 
   try   { 
     chrome . identity . getAuthToken ({ interactive :   false },   function ( token )   { 
       if   ( token )   { 
         this . accessToken   =   token ; 
         opt_callback  &&  opt_callback (); 
       } 
     }. bind ( this )); 
   }   catch ( e )   { 
     console . log ( e ); 
   } 
}; 
ملاحظة:  يتيح لنا تمرير معاودة الاتصال الاختيارية معرفة متى يصبح رمز OAuth المميز مرنًا.
ملاحظة:  لتبسيط الأمور قليلاً، أنشأنا مكتبة gdocs.js  للتعامل مع مهام واجهة برمجة التطبيقات.بعد حصولنا على الرمز المميّز، يحين وقت تقديم طلبات مقابل واجهة برمجة تطبيقات Drive وملء النموذج.
وحدة تحكّم هيكلية 
"النموذج" للقائم بالتحميل، هي مصفوفة بسيطة (تسمى مستندات) من الكائنات التي سيتم عرضها على هيئة
تلك 
 في القالب:
var   gDriveApp   =   angular . module ( 'gDriveApp' ,   []); 
gDriveApp . factory ( 'gdocs' ,   function ()   { 
   var   gdocs   =   new   GDocs (); 
   return   gdocs ; 
}); 
function   DocsController ( $scope ,   $http ,   gdocs )   { 
   $scope . docs   =   []; 
   $scope . fetchDocs   =   function ()   { 
      ... 
   }; 
   // Invoke on ctor call. Fetch docs after we have the oauth token. 
   gdocs . auth ( function ()   { 
     $scope . fetchDocs (); 
   }); 
} 
لاحظ أن gdocs.auth() يتم استدعاءه كجزء من الدالة الإنشائية لـ DocsController. متى يتم تشغيل Angular
وحدة التحكم الداخلية، نحرص على أن يكون لدينا رمز OAuth مميز جديد بانتظار المستخدم.
جارٍ استرجاع البيانات 
تم وضع القالب. تم تثبيت وحدة التحكّم. رمز OAuth المميز متوفر. فماذا أفعل الآن؟
حان الوقت لتحديد طريقة وحدة التحكُّم الرئيسية، fetchDocs(). إنه ركيزة وحدة التحكم،
مسؤولة عن طلب ملفات المستخدم وملء مصفوفة المستندات ببيانات من ردود واجهة برمجة التطبيقات.
$scope . fetchDocs   =   function ()   { 
   $scope . docs   =   [];   // First, clear out any old results 
   // Response handler that doesn't cache file icons. 
   var   successCallback   =   function ( resp ,   status ,   headers ,   config )   { 
     var   docs   =   []; 
     var   totalEntries   =   resp . feed . entry . length ; 
     resp . feed . entry . forEach ( function ( entry ,   i )   { 
       var   doc   =   { 
         title :   entry . title . $t , 
         updatedDate :   Util . formatDate ( entry . updated . $t ), 
         updatedDateFull :   entry . updated . $t , 
         icon :   gdocs . getLink ( entry . link , 
                             'http://schemas.google.com/docs/2007#icon' ). href , 
         alternateLink :   gdocs . getLink ( entry . link ,   'alternate' ). href , 
         size :   entry . docs$size   ?   '( '   +   entry . docs$size . $t   +   ' bytes)'   :   null 
       }; 
       $scope . docs . push ( doc ); 
       // Only sort when last entry is seen. 
       if   ( totalEntries   -   1   ==   i )   { 
         $scope . docs . sort ( Util . sortByDate ); 
       } 
     }); 
   }; 
   var   config   =   { 
     params :   { 'alt' :   'json' }, 
     headers :   { 
       'Authorization' :   'Bearer '   +   gdocs . accessToken , 
       'GData-Version' :   '3.0' 
     } 
   }; 
   $http . get ( gdocs . DOCLIST_FEED ,   config ). success ( successCallback ); 
}; 
يستخدم fetchDocs() خدمة $http في Angular لاسترداد الخلاصة الرئيسية عبر XHR. الوصول إلى OAuth
يتم تضمين رمز مميّز في العنوان Authorization مع رؤوس ومَعلمات مخصّصة أخرى.
تعالج السمة successCallback استجابة واجهة برمجة التطبيقات وتنشئ كائن مستند جديدًا لكل إدخال في
الخلاصة.
إذا شغّلت fetchDocs() الآن، ستسير الأمور على ما يرام وستظهر قائمة الملفات:
رائع.
مهلاً،...تنقصنا رموز الملفات الرائعة هذه. What gives? يُظهر التحقق السريع من وحدة التحكم مجموعة
من الأخطاء المتعلقة بسياسة CSP:
السبب هو أننا نحاول ضبط الرموز img.src على عناوين URL خارجية. يخالف هذا المحتوى سياسة CSP. بالنسبة
مثال: https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png لإصلاح ذلك،
لسحب الأصول البعيدة هذه محليًا إلى التطبيق.
جارٍ استيراد مواد عرض الصور عن بُعد 
لكي يتوقف CSP عن الصراخ علينا، نستخدم XHR2 من أجل "الاستيراد" أيقونات الملف على أنها Blobs، ثم قم بتعيين
img.src إلى "blob: URL" الذي أنشأه التطبيق
في ما يلي successCallback المعدّلة الذي يتضمّن رمز XHR المضاف:
var   successCallback   =   function ( resp ,   status ,   headers ,   config )   { 
   var   docs   =   []; 
   var   totalEntries   =   resp . feed . entry . length ; 
   resp . feed . entry . forEach ( function ( entry ,   i )   { 
     var   doc   =   { 
       ... 
     }; 
     $http . get ( doc . icon ,   { responseType :   'blob' }). success ( function ( blob )   { 
       console . log ( 'Fetched icon via XHR' ); 
       blob . name   =   doc . iconFilename ;   // Add icon filename to blob. 
       writeFile ( blob );   // Write is async, but that's ok. 
       doc . icon   =   window . URL . createObjectURL ( blob ); 
       $scope . docs . push ( doc ); 
       // Only sort when last entry is seen. 
       if   ( totalEntries   -   1   ==   i )   { 
         $scope . docs . sort ( Util . sortByDate ); 
       } 
     }); 
   }); 
}; 
وبعد أن أصبحت CSP سعيدة معنا مرة أخرى، حصلنا على رموز ملفات رائعة:
العمل بلا اتصال بالإنترنت: تخزين الموارد الخارجية مؤقتًا 
التحسين الواضح الذي يجب إجراؤه: عدم إجراء 100 ثانية من طلبات XHR لكل رمز ملف على
كل مكالمة إلى fetchDocs(). تحقق من ذلك في وحدة تحكم أدوات المطور عن طريق الضغط على "تحديث"
الزر عدة مرات. في كل مرة، يتم جلب n من الصور:
يمكننا تعديل successCallback لإضافة طبقة تخزين مؤقت. يتم تمييز الإضافات بالخط العريض:
$scope . fetchDocs   =   function ()   { 
   ... 
   // Response handler that caches file icons in the filesystem API. 
   var   successCallbackWithFsCaching   =   function ( resp ,   status ,   headers ,   config )   { 
     var   docs   =   []; 
     var   totalEntries   =   resp . feed . entry . length ; 
     resp . feed . entry . forEach ( function ( entry ,   i )   { 
       var   doc   =   { 
         ... 
       }; 
       // 'https://ssl.gstatic.com/doc_icon_128.png' -> 'doc_icon_128.png' 
       doc . iconFilename   =   doc . icon . substring ( doc . icon . lastIndexOf ( '/' )   +   1 ); 
       // If file exists, it we'll get back a FileEntry for the filesystem URL. 
       // Otherwise, the error callback will fire and we need to XHR it in and 
       // write it to the FS. 
       var   fsURL   =   fs . root . toURL ()   +   FOLDERNAME   +   '/'   +   doc . iconFilename ; 
       window . webkitResolveLocalFileSystemURL ( fsURL ,   function ( entry )   { 
         doc . icon   =   entry . toURL ();   // should be === to fsURL, but whatevs. 
         $scope . docs . push ( doc );   // add doc to model. 
         // Only want to sort and call $apply() when we have all entries. 
         if   ( totalEntries   -   1   ==   i )   { 
           $scope . docs . sort ( Util . sortByDate ); 
           $scope . $apply ( function ( $scope )   {});   // Inform angular that we made changes. 
         } 
       },   function ( e )   { 
         // Error: file doesn't exist yet. XHR it in and write it to the FS. 
         $http . get ( doc . icon ,   { responseType :   'blob' }). success ( function ( blob )   { 
           console . log ( 'Fetched icon via XHR' ); 
           blob . name   =   doc . iconFilename ;   // Add icon filename to blob. 
           writeFile ( blob );   // Write is async, but that's ok. 
           doc . icon   =   window . URL . createObjectURL ( blob ); 
           $scope . docs . push ( doc ); 
           // Only sort when last entry is seen. 
           if   ( totalEntries   -   1   ==   i )   { 
             $scope . docs . sort ( Util . sortByDate ); 
           } 
         }); 
       }); 
     }); 
   }; 
   var   config   =   { 
     ... 
   }; 
   $http . get ( gdocs . DOCLIST_FEED ,   config ). success ( successCallbackWithFsCaching ); 
}; 
يُرجى ملاحظة أنّه في معاودة الاتصال webkitResolveLocalFileSystemURL() نتّصل بـ $scope.$apply() عندما
مشاهدة آخر إدخال. الاتصال العادي بـ $apply() ليس ضروريًا. يرصد Angular التغييرات في البيانات.
النماذج تلقائيًا. ولكن في حالتنا هذه، لدينا طبقة إضافة من معاودة الاتصال غير المتزامنة
إنّ Angular ليس على علم بالأمر. يجب أن نخبر Angular صراحةً عندما يتم تحديث نموذجنا.
عند التشغيل لأول مرة، لن تكون الرموز في نظام ملفات HTML5 ولن تكون الطلبات
سيؤدي ذلك إلى استدعاء ميزة معاودة الاتصال بالخطأ window.webkitResolveLocalFileSystemURL(). لذلك
في هذه الحالة، يمكننا إعادة استخدام التقنية من قبل وجلب الصور. الفرق الوحيد هذه المرة هو
أنّه تتم كتابة كل كائن ثنائي كبير (blob) إلى نظام الملفات (راجع writeFile() ). تتحقّق وحدة التحكّم من هذا الإجراء.
السلوك:
عند التشغيل التالي (أو الضغط على الزر "تحديث")، تم تمرير عنوان URL إلى
يتوفّر webkitResolveLocalFileSystemURL() لأنه سبق أن تم تخزين الملف مؤقتًا. يحدد التطبيق
doc.icon إلى filesystem: URL في الملف وتجنُّب إجراء XHR المكلف للرمز.
التحميل من خلال سحبه وإفلاته 
يُعد تطبيق القائم بالتحميل إعلانًا خاطئًا إذا لم يتمكن من تحميل ملفات!
تعالج علامة تبويب app.js  هذه الميزة من خلال تنفيذ مكتبة صغيرة حول "السحب والإفلات" في HTML5 والتي تسمى
DnDFileController يتيح إمكانية سحب الملفات من سطح المكتب وتحميلها
إلى Google Drive.
تؤدي إضافة هذا إلى خدمة gdocs هذه المهمة:
gDriveApp . factory ( 'gdocs' ,   function ()   { 
   var   gdocs   =   new   GDocs (); 
   var   dnd   =   new   DnDFileController ( 'body' ,   function ( files )   { 
     var   $scope   =   angular . element ( this ). scope (); 
     Util . toArray ( files ). forEach ( function ( file ,   i )   { 
       gdocs . upload ( file ,   function ()   { 
         $scope . fetchDocs (); 
       }); 
     }); 
   }); 
   return   gdocs ; 
});