تطوير تطبيقات الويب باستخدام Angular


الدرس: تحسين هيكل الكود مع الخدمات


الصفحة السابقة

ما هي الخدمة؟


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

الحقن والحالات


لاستخدامها في التطبيق ، يجب حقن الخدمة ، والمستوى المختار للحقن مهم للغاية. هناك ثلاثة مستويات ممكنة لهذا الحقن:
  • في   AppModule  : لذلك سيتم استخدام نفس مثيل من الخدمة من قبل جميع مكونات التطبيق و غيرها من الخدمات.
  • في   AppComponent  : كما هو موضح أعلاه ، سيتمكن جميع المكونات من الوصول إلى نفس مثيل الخدمة ولكن ليس الخدمات الأخرى ؛
  • في مكون آخر: سيتمكن المكون نفسه وجميع أطفاله (أي جميع المكونات التي يشملها) من الوصول إلى نفس مثيل الخدمة ، ولكن لن يكون لبقية التطبيق أي لا يوجد وصول.
للحصول على الأمثلة في هذه الدورة التدريبية ، ستقوم بإدخال الخدمات بشكل منهجي   AppModule  لإتاحة مثيل واحد فقط لكل خدمة لجميع الأجزاء الأخرى من التطبيق الخاص بك.
الآن قم بإنشاء مجلد فرعي   services  في   app ، وقم بإنشاء ملف جديد يسمى   appareil.service.ts  :

export class AppareilService {
  
}
ستقوم قريبًا بدمج البيانات والوظائف ، ولكن في الوقت الحالي ، ستقوم بحقن هذه الخدمة AppModule بإضافتها إلى الصفيف providers (لا تنس إضافة الاستيراد import المقابل في أعلى الملف) :

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { MonPremierComponent } from './mon-premier/mon-premier.component';
import { AppareilComponent } from './appareil/appareil.component';
import { FormsModule } from '@angular/forms';
import { AppareilService } from './services/appareil.service';


@NgModule({
  declarations: [
    AppComponent,
    MonPremierComponent,
    AppareilComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [
    AppareilService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
تقوم Angular الآن بإنشاء مثيل من الخدمة AppareilService للتطبيق بأكمله. لدمجها في مكون ، نعلن أنها حجة في منشئها. قم بدمجها في AppComponent (بدون نسيان إضافة الاستيراد في الأعلى) :

constructor(private appareilService: AppareilService) {
    setTimeout(
      () => {
        this.isAuth = true;
      }, 4000
    );
  }
الآن في   AppComponent ، لديك عضو يسمى   appareilService  يطابق مثيل هذه الخدمة التي قمت بإنشائها    AppModule  . ستضيف وظائف في الدرس التالي.

استخدم الخدمات


سيكون العنصر الأول الذي سيكون منطقيًا للترحيل في الخدمة هو صفيف appareils . انسخها من    AppComponent  ثم الصقها في   AppareilService  ثم مرة أخرى في   AppComponent أعلن   appareils  ببساطة كمصفوفة من النوع   any  :

export class AppareilService {
  appareils = [
    {
      name: 'Machine à laver',
      status: 'éteint'
    },
    {
      name: 'Frigo',
      status: 'allumé'
    },
    {
      name: 'Ordinateur',
      status: 'éteint'
    }
  ];
}


export class AppComponent {

  isAuth = false;

  appareils: any[];
من الضروري الآن ان AppComponent    يمكنه  استعادة المعلومات المخزنة في   AppareilService  . للقيام بذلك ، ستقوم بتطبيق الطريقة   ngOnInit()  .
ngOnInit()   يتوافق مع " lifecycle hook" . تتجاوز تفاصيل هذه hooks نطاق هذه الدورة التدريبية ، ولكن في الوقت الحالي ، كل ما تحتاج إلى معرفته هو أن طريقة   ngOnInit() يتم تنفيذها مرة واحدة لكل مثيل في وقت إنشاء المكون بواسطة Angular ، وبعد منشئه. غالبًا ما يتم استخدامه لتهيئة البيانات بمجرد إنشاء المكون. لاحقًا في هذا الجزء من الدورة ، ستكتشف أيضًا   ngOnDestroy()  . لمزيد من المعلومات ، راجع الوثائق الزاويّة .
للقيام بذلك ، ستقوم أولاً بإنشاء الوظيفة   ngOnInit()  - عادةً ما يتم وضعها بعد المنشئ (constructor)وقبل طرق المكون الأخرى:

constructor(private appareilService: AppareilService) {
    setTimeout(
      () => {
        this.isAuth = true;
      }, 4000
    );
  }

ngOnInit() {

}
بعد ذلك ، في إعلان الفئة AppComponent ، ستقوم بتطبيق الواجهة OnInit ( من خلال استيرادها من @angular/core أعلاه )

import { Component, OnInit } from '@angular/core';
import { AppareilService } from './services/appareil.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
يمكنك الآن استرداد المعلومات من AppareilService في الطريقة ngOnInit() :

ngOnInit() {
    this.appareils = this.appareilService.appareils;
}
بشكل عام ، لا يعد الاتصال المباشر بمصفوفة كأفضل ممارسة. لقد اخترت استخدام هذه الطريقة هنا لإظهار تكامل الخدمات ببساطة أكبر ، ولكن لا تقلق: سنرى أفضل الطرق لاحقًا في هذه الدورة!
يجب أن يعمل التطبيق الخاص بك مرة أخرى ، مع عرض قائمة الأجهزة الكهربائية كما كان من قبل. لا يوجد فرق مرئي ، لكن كودك أصبح الآن أكثر نمطيًا ، وسيكون من الأسهل إضافة الوظائف. على سبيل المثال، يمكنك إنشاء طريقتين جديدتين:   switchOnAll()  و   switchOffAll()  لتشغيل أو إيقاف تشغيل جميع الأجهزة في وقت واحد.
ابدأ بإعداد هذه الطرق في   AppareilService  :

switchOnAll() {
    for(let appareil of this.appareils) {
      appareil.status = 'allumé';
    }
}

switchOffAll() {
    for(let appareil of this.appareils) {
      appareil.status = 'éteint';
    }
}
ثم أضف زرًا ثانيًا في نموذج AppComponent :

<button class="btn btn-success"
              [disabled]="!isAuth"
              (click)="onAllumer()">Tout allumer</button>
<button class="btn btn-danger"
              [disabled]="!isAuth"
              (click)="onEteindre()">Tout éteindre</button>
وأخيرا، فإنه لا يزال الا ان تتمكن من التقاط الأحداث click في AppComponent ثم تشغيل AppareilService . ابدأ بالفعل بـ onAllumer() :

onAllumer() {
    this.appareilService.switchOnAll();
}
بعد ذلك ، onEteindre() ستعرض أولاً رسالة تأكيد للتأكد من أن المستخدم متأكد من أنه يريد إيقاف تشغيل كل شيء:

onEteindre() {
    if(confirm('Etes-vous sûr de vouloir éteindre tous vos appareils ?')) {
      this.appareilService.switchOffAll();
    } else {
      return null;
    }
}
الأزرار الخاصة بك تشغّل وتطفئ جميع الأجهزة بفضل التواصل بين   AppComponent  و   AppareilService  .
ولكن كان بإمكاني فعل كل هذا داخل المكون - ما الفائدة من وجود كل شيء في الخدمة؟
في الواقع ، يمكن أن تبقى الوظائف التي أضفتها في الوقت الحالي   AppComponent ، ولكن في الفصل التالي ، ستستفيد من الخدمة لإنشاء اتصال بين مكوناتك ، وخاصة المكونات الفرعية تجاه المكون الام.

قم بتواصل المكونات


في الوقت الحالي ، يمكن للمستخدم تشغيل أو إيقاف تشغيل جميع الأجهزة مرة واحدة فقط. ما يمكن أن يكون مثيرا للاهتمام هو أنه يمكنه تشغيله أو إيقافه في وقت واحد. حاليًا ، تبدو خريطة التطبيق كما يلي:
Angular web site
AppareilService   يوفر بيانات الجهاز ل   AppComponent  . ثم يقوم   AppComponent  بإنشاء ثلاث حالات منه   AppareilComponent  بناءً على تلك البيانات. لا يوجد حاليا أي اتصال بين المكونات الفرعية والام. يمكنك تغيير هذا عن طريق دمج   AppareilService  في   AppareilComponent  وإيجاد طرق لتعديل جهاز في وقت واحد. المضي قدما خطوة بخطوة.
أولاً ، يجب أن يكون كل مثيل   AppareilComponent  قادرًا على معرفة   AppareilService  عضو الصفيف   appareils  الذي يتوافق معه. لحسن الحظ ، يتيح لنا Angular القيام بذلك بسهولة. في التوجيه   *ngFor ، أضف:

<ul class="list-group">
    <app-appareil  *ngFor="let appareil of appareils; let i = index"
                   [appareilName]="appareil.name"
                   [appareilStatus]="appareil.status"></app-appareil>
</ul>
هذا الأمر يجعل فهرس الكائن متاحًا   appareil  في الصفيف   appareils  . بعد ذلك ، يجب أن تكون قادرًا على التقاط هذا المتغير والعمل عليه: يمكنك استخدام ربط الخاصية. للقيام بذلك ، قم بإضافة عضو   index  إلى المكون على النحو التالي   @Input()  :

@Input() appareilName: string;
@Input() appareilStatus: string;
@Input() index: number;
ثم اربط الفهرس i به من القالب:

<ul class="list-group">
    <app-appareil  *ngFor="let appareil of appareils; let i = index"
                   [appareilName]="appareil.name"
                   [appareilStatus]="appareil.status" 
                   [index]="i"></app-appareil>
</ul>
من هناك ، لديك متغير   index  متاح داخل المكون الذي يتوافق مع فهرس الجهاز في صفيف   AppareilService  . سترى في لحظات قليلة لماذا تحتاج إليه. 
في   AppareilService ، ستقوم الآن بإنشاء الطرق التي تسمح بتشغيل أو إيقاف تشغيل جهاز واحد وفقًا لفهرسه في الصفيف   appareils  :

switchOnOne(i: number) {
    this.appareils[i].status = 'allumé';
}

switchOffOne(i: number) {
    this.appareils[i].status = 'éteint';
}
بعد ذلك AppareilComponent ، ستقوم أولاً بدمج الخدمة AppareilService، واستيرادها في أعلى الملف كما هو الحال دائمًا:

constructor(private appareilService: AppareilService) { }
ثم ستقوم بإعداد الطريقة التي تقوم بتشغيلها أو إيقاف تشغيلها وفقًا للحالة الحالية للجهاز:

onSwitch() {
    if(this.appareilStatus === 'allumé') {
      this.appareilService.switchOffOne(this.index);
    } else if(this.appareilStatus === 'éteint') {
      this.appareilService.switchOnOne(this.index);
    }
}
  يتم اختيار الاسم  onSwitch() هنا لاستيفاء معيار استخدام "on" لالتقاط حدث ، وليس القول "switch on" كـ "allumer" .
أخيرًا ، ستنشئ الزر في النموذج الذي سيشغل هذه الطريقة. سيكون من المثير للاهتمام إذا كان هذا الزر سياقيًا: إذا كان الجهاز قيد التشغيل ، فسيعرض "إيقاف التشغيل(Éteindre)" والعكس صحيح. أسهل طريقة للقيام بذلك هي إنشاء زرين مع التوجيه   *ngIf  : 

<li [ngClass]="{'list-group-item': true,
                'list-group-item-success': appareilStatus === 'allumé',
                'list-group-item-danger': appareilStatus === 'éteint'}">
  
  <h4 [ngStyle]="{color: getColor()}">Appareil : { { appareilName }} -- Statut : { { getStatus() }}</h4>
  <input type="text" class="form-control" [(ngModel)]="appareilName">

  <button class="btn btn-sm btn-success"
          *ngIf="appareilStatus === 'éteint'"
          (click)="onSwitch()">Allumer</button>
  <button class="btn btn-sm btn-danger"
          *ngIf="appareilStatus === 'allumé'"
          (click)="onSwitch()">Eteindre</button>

</li>
يمكنك حذف   <div>  الشرطي الأحمر ، لأنه لم يعد مستخدمًا حقًا ، مع جميع الأنماط التي أضفتها للإبلاغ عن حالة الجهاز.
 ها أنت ذا! تتواصل مكوناتك مع بعضها البعض باستخدام الخدمة ، التي تعمل على مركزة البيانات ووظائف معينة. حتى إذا كانت التأثيرات مرئية فقط في الوقت الحالي ، يمكنك أن تتخيل أنه ضمن أساليب الخدمة   AppareilService ، هناك استدعاءات API تسمح بتشغيل الأجهزة أو إيقاف تشغيلها والتحقق من تشغيلها .