5 أشياء أود أن أعرفها عندما بدأت في استخدام الزاوي

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


واجهت نفس المشكلة أيضًا عندما جئت إلى Tinkoff إلى منصب Junior Frontend Developer منذ حوالي عامين وتوغلت في عالم Angular. لذلك ، أقدم لكم قصة قصيرة عن خمسة أشياء ، من شأن فهمها أن يسهل عملي إلى حد كبير في البداية.



حقن التبعية (DI)


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


أقترح على الفور فهم مثال ، لكننا نحتاج إلى فصل. إذا كان JavaScript OOP "عاديًا" موجودًا مع بعض "الاختراقات" ، فهناك مع ES6 بناء جملة "حقيقي". يستخدم الزاوي TypeScript مباشرة خارج المربع ، حيث يكون بناء الجملة نفسه تقريبا. لذلك ، أقترح استخدامه كذلك.


تخيل أن هناك فئة JokerService في تطبيقنا الذي يدير النكات. إرجاع الأسلوب getJokes() قائمة النكات. لنفترض أننا نستخدمها في ثلاثة أماكن. كيفية الحصول على النكات في ثلاثة أماكن مختلفة في الرمز؟ هناك عدة طرق:


  1. إنشاء مثيل للفئة في كل مكان. ولكن لماذا نحتاج إلى إعاقة الذاكرة وإنشاء العديد من الخدمات المتطابقة؟ وإذا كان هناك 100 مقعد؟
  2. اجعل الطريقة ثابتة واسترجع البيانات باستخدام JokerService.getJokes ().
  3. قم بتطبيق أحد أنماط التصميم. إذا احتجنا إلى أن تكون الخدمة واحدة للتطبيق بالكامل ، فسيكون ذلك Singleton. ولكن لهذا تحتاج إلى كتابة منطق جديد في الفصل.

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


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


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


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


إذا كنت تعتقد أنه يمكنك ببساطة إنشاء كائن واستخدام المفتاح لأخذ الخدمة المطلوبة ، يمكنني أن أهنئك: لقد أدركت للتو كيف تعمل Dependency Injection بشكل عام. ولكن دعنا نذهب أعمق قليلا.


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


حقن التبعية في الزاوي


كما تقول الوثائق ، DI هو نمط تصميم مهم للتطبيق. Angular لديها إطار التبعية الخاص بها ، والذي يستخدم في Angular نفسها لزيادة الكفاءة والانتظامية.


بشكل عام ، Dependency Injection هي آلية قوية يحصل فيها الفصل على التبعيات اللازمة من مكان ما في الخارج ، بدلاً من إنشاء حالات بمفرده.


دع بناء الجملة والملفات ذات ملحق html لا تربك. كل مكون في Angular هو كائن JavaScript عادي ، وهو مثيل لفئة. بعبارات عامة: عند إدراج مكون في قالب ، يتم إنشاء مثيل لفئة المكون. وفقًا لذلك ، في هذه اللحظة ، يمكنك نقل التبعيات اللازمة إلى المُنشئ. الآن النظر في مثال:


 @Component({ selector: 'jokes', template: './jokes.template.html', }) export class JokesComponent { private jokes: Observable<IJoke[]>; constructor(private jokerService: JokerService) { this.jokes = this.jokerService.getJokes(); } } 

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


مقدمي


والآن أقترح التعامل مع القضية عندما تحتاج إلى الحصول على مثيلات مختلفة من الخدمة. أولاً ، ألقِ نظرة على الخدمة نفسها:


 @Injectable({ providedIn: 'root', //   ,   «»  }) export class JokerService { getJokes(): Observable<IJoke[]> { //     } } 

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


للراحة ، سأتصل provider الخدمة ، وسيتم التحقق من عملية استبدال القيمة في الفصل. لذلك ، يمكننا تقديم الخدمة بطرق مختلفة وفي أماكن مختلفة. لنبدأ مع آخر واحد. هناك ثلاثة خيارات متوفرة:


  • في التطبيق بالكامل - حدد provideIn: 'root' في مصمم الخدمة نفسه.
  • في الوحدة النمطية - حدد الموفر في مصمم الخدمة على النحو provideIn: JokesModule أو في مصمم وحدة @NgModule providers: [JokerService] .
  • في المكون - حدد الموفر في ديكور المكون ، كما في الوحدة.

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


 @NgModule({ // ...     providers: [{provide: JokerService, useClass: JokerService}], }) //   

يمكن قراءة هذا على النحو التالي: "في حالة طلب خدمة JokerService ، JokerService إعطاء مثيل لفئة JokerService» من هنا يمكنك الحصول على مثيل معين بطرق مختلفة:


  • حسب الرمز المميز - تحتاج إلى تحديد InjectionToken والحصول على خدمة عليه. لاحظ أنه في الأمثلة الموضحة أدناه ، يمكنك تمرير الرمز المميز نفسه:


     const JOKER_SERVICE_TOKEN = new InjectionToken<string>('JokerService'); // ...     [{provide: JOKER_SERVICE_TOKEN, useClass: JokerService}]; 

  • حسب الفصل - يمكنك استبدال الفصل. على سبيل المثال ، سوف نطلب خدمة JokerService ، JokerHappyService - JokerHappyService :


     [{provide: JokerService, useClass: JokerHappyService}]; 

  • حسب القيمة - يمكنك إرجاع المثيل المطلوب فورًا:


     [{provide: JokerService, useValue: jokerService}]; 

  • حسب المصنع - يمكنك استبدال الفصل بمصنع يقوم بإنشاء المثيل المطلوب عند الوصول إليه:


     [{provide: JokerService, useFactory: jokerServiceFactory}]; 


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


بالمناسبة ، لا تعمل DI فقط للخدمات ، ولكن بشكل عام لأي كيان تحصل عليه في مُنشئ المكون. هذه هي آلية قوية للغاية ينبغي استخدامها لتحقيق أقصى إمكاناتها.


ملخص صغير


لفهم تام ، أقترح النظر في آلية حقن التبعية المبسطة في الزاوي في خطوات باستخدام مثال الخدمة:


  1. عند تهيئة التطبيق ، يكون للخدمة رمز مميز. إذا لم نحددها على وجه التحديد في الموفر ، فهذا هو JokerService.
  2. عند طلب خدمة في أحد المكونات ، تتحقق آلية DI من وجود الرمز المميز المنقول.
  3. إذا كان الرمز المميز غير موجود ، فسوف يرتكب DI خطأً. في حالتنا ، يوجد الرمز المميز ويوجد JokerService عليه.
  4. عند إنشاء المكون ، يتم تمرير مثيل JokerService إلى المُنشئ كوسيطة.

تغيير الاكتشاف


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


في الزاوي ، " تغيير الكشف" هو المسؤول عن التحقق من التغييرات. نتيجة لعمليات متعددة - تغيير قيمة خاصية فئة ، واستكمال عملية غير متزامنة ، والاستجابة لطلب HTTP ، وهكذا - تبدأ عملية التحقق عبر شجرة المكون.


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


Zone.js


إن فهم كيفية تتبع Angular لخصائص الفصل والعمليات المتزامنة بسيط للغاية. ولكن كيف تتبع غير متزامن؟ مكتبة Zone.js ، التي تم إنشاؤها من قبل أحد المطورين Angular ، هي المسؤولة عن هذا.


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


يستبدل Zone.js مع تطبيقاته تقريبًا كافة الوظائف والأساليب غير المتزامنة الأصلية. لذلك ، يمكن أن يتعقب متى سيتم callback لوظيفة غير متزامنة. وهذا يعني أن Zone تخبر Angular بموعد ومكان بدء عملية التحقق من التغيير.


تغيير استراتيجيات الكشف


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


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


هناك خياران للاختيار من بينها:


  • الافتراضي - كما يوحي الاسم ، هذه هي الاستراتيجية الافتراضية عند تشغيل قرص مضغوط لكل إجراء.
  • OnPush هي استراتيجية يتم فيها إطلاق قرص مضغوط في حالات قليلة فقط:
    • إذا تغيرت قيمة @Input() ؛
    • إذا وقع حدث داخل المكون أو أحفاده ؛
    • إذا كان الشيك قد بدأ يدويًا ؛
    • إذا وصل حدث جديد إلى Async Pipe.

استنادًا إلى تجربتي التطويرية الخاصة بـ Angular ، بالإضافة إلى تجربة زملائي ، يمكنني القول بالتأكيد أنه من الأفضل دائمًا تحديد استراتيجية OnPush ، ما لم يكن OnPush default ضروريًا حقًا. سيعطيك هذا العديد من المزايا:


  • فهم واضح لكيفية عمل عملية مؤتمر نزع السلاح.
  • عمل أنيق مع خصائص @Input() .
  • كسب الأداء.

العمل مع @Input()


مثل الأطر الشائعة الأخرى ، يستخدم Angular دفق البيانات المتلقين للمعلومات. يقبل المكون معلمات الإدخال التي تم تمييزها باستخدام @Input() decorator. النظر في مثال:


 interface IJoke { author: string; text: string; } @Component({ selector: 'joke', template: './joke.template.html', }) export class JokeComponent { @Input() joke: IJoke; } 

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


 setAuthorNameOnly() { const name = this.joke.author.split(' ')[0]; this.joke.author = name; } 

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


 @Component({ selector: 'joke', template: './joke.template.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class JokeComponent { @Input() readonly joke: IJoke; @Output() updateName = new EventEmitter<string>(); setAuthorNameOnly() { const name = this.joke.author.split(' ')[0]; this.updateName.emit(name); } } 

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


RxJS


بالطبع ، قد أكون مخطئًا ، لكن يبدو أن ReactiveX والبرمجة التفاعلية بشكل عام هي اتجاه جديد. الزاوي استسلم لهذا الاتجاه (أو ربما خلقه) ويستخدم RxJS بشكل افتراضي. يعمل المنطق الأساسي للإطار بأكمله على هذه المكتبة ، لذلك من المهم للغاية فهم مبادئ البرمجة التفاعلية.


ولكن ما هو RxJS؟ إنه يجمع بين ثلاث أفكار سأكشفها بلغة بسيطة إلى حد ما مع بعض الإغفالات:


  • نمط "المراقب" هو كيان ينتج الأحداث ، وهناك مستمع يتلقى معلومات حول هذه الأحداث.
  • يتيح لك Iterator Pattern - الحصول على وصول متسلسل إلى عناصر الكائن دون الكشف عن بنيته الداخلية.
  • البرمجة الوظيفية مع المجموعات هي نمط يتداخل فيه المنطق مع مكونات صغيرة وبسيطة للغاية ، كل منها يحل مشكلة واحدة فقط.

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


 private loadUnreadJokes() { this.showLoader(); //   fromEvent(document, 'load') .pipe( switchMap( () => this.http .get('/api/v1/jokes') //   .pipe(map((jokes: any[]) => jokes.filter(joke => joke.unread))), //   ), ) .subscribe( (jokes: any[]) => (this.jokes = jokes), //   error => { /*   */ }, () => this.hideLoader(), //       ); } 

فقط 18 خطوط مع كل المسافة البادئة الجميلة. الآن حاول إعادة كتابة هذا المثال على Vanilla أو على الأقل jQuery. سيأخذك 100٪ تقريبًا من هذا المساحة على الأقل ضعف المساحة ولن تكون معبرة جدًا. هنا يمكنك فقط اتباع السطر مع عينيك وقراءة الرمز مثل كتاب.


يمكن إدراكه


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


 const observable = []; let counter = 0; const intervalId = setInterval(() => { observable.push(counter++); }, 1000); setTimeout(() => { clearInterval(intervalId); }, 6000); 

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


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


 document.addEventListener('click', event => {}); 

يمكنك وضع الكثير من EventListener في التطبيق بالكامل ، وسوف تعمل ، ما لم تكن ، بطبيعة الحال ، تعتني بالعكس عن قصد.


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


الآن دعونا نلقي نظرة على مثال حقيقي:


 export class JokesListComponent implements OnInit { jokes$: Observable<IJoke>; authors$ = new Subject<string[]>(); unread$ = new Subject<number>(); constructor(private jokerService: JokerService) {} ngOnInit() { //  ,    subscribe()    this.jokes$ = this.jokerService.getJokes(); this.jokes$.subscribe(jokes => { this.authors$.next(jokes.map(joke => joke.author)); this.unread$.next(jokes.filter(joke => joke.unread).length); }); } } 

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


اختبارات


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


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


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


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


 @Component({ selector: 'app-joker', template: '<some-dependency></some-dependency>', styleUrls: ['./joker.component.less'], }) export class JokerComponent { constructor( private jokesService: JokesService, @Inject(PARTY_TOKEN) private partyService: PartyService, @Optional() private sleepService: SleepService, ) {} makeNewFriend(): IFriend { if (this.sleepService && this.sleepService.isSleeping) { this.sleepService.wakeUp(); } const joke = this.jokesService.generateNewJoke(); this.partyService.goToParty('Pacha'); this.partyService.toSay(joke.text); const laughingPeople = this.partyService.getPeopleByReaction('laughing'); const girl = laughingPeople.find(human => human.sex === 'female'); const friend = this.partyService.makeFriend(girl); return friend; } } 

لذلك ، في المثال الحالي هناك ثلاث خدمات. يتم استيراد واحد بالطريقة المعتادة ، واحدة تلو الأخرى ، وخدمة أخرى اختيارية. كيف يمكننا تكوين وحدة الاختبار؟ سأعرض على الفور العرض النهائي:


 beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SomeDependencyModule], declarations: [JokerComponent], //  ,    providers: [{provide: PARTY_TOKEN, useClass: PartyService}], }).compileComponents(); fixture = TestBed.createComponent(JokerComponent); component = fixture.componentInstance; fixture.detectChanges(); //    ,     })); 

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


التبعيات غير الضرورية يمكن تجنبها


يتكون التطبيق الزاوي من وحدات ، والتي يمكن أن تشمل وحدات أخرى ، والخدمات ، والتوجيهات ، وأكثر من ذلك. في الاختبار ، نحتاج ، في الواقع ، إلى إعادة تشغيل تشغيل الوحدة. إذا استخدمنا في مثالنا <some-dependency></some-dependency> في القالب ، فهذا يعني أنه يجب علينا استيراد SomeDependencyModule في الاختبار أيضًا. وإذا كان هناك إدمان هناك؟ لذلك ، يحتاجون أيضًا إلى الاستيراد.
إذا كان التطبيق معقدًا ، فسيكون هناك الكثير من هذه التبعيات. سيؤدي استيراد جميع التبعيات إلى حقيقة أنه في كل اختبار سيتم تحديد موقع التطبيق بالكامل وسيتم استدعاء جميع الأساليب. ربما هذا لا يناسبنا.


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


 TestBed.configureTestingModule({ declarations: [JokerComponent], providers: [{provide: PARTY_TOKEN, useClass: PartyService}], }) .overrideTemplate(JokerComponent, '') //   ,   .compileComponents(); 

, . , . , . , , , , . — .



Injection Token , . . , , .


ts-mockito , , . Angular « ».


 //    export class MockPartyService extends PartyService { meetFriend(): IFriend { return {} as IFriend; } goToParty() {} toSay(some: string) { console.log(some); } } // ... TestBed.configureTestingModule({ declarations: [JokerComponent, MockComponent], providers: [{provide: PARTY_TOKEN, useClass: MockPartyService}], //    }).compileComponents(); 

هذا كل شيء. .



. , — , — . , :


  • .
  • — , . — .

— . , . — .


يؤدي


Angular, . , , «».


, Angular - . HTTP-, , lazy-loading . Angular .

Source: https://habr.com/ru/post/ar480782/


All Articles