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


الدرس: إدارة البيانات الديناميكية


الصفحة السابقة
ميزة استخدام Angular هي أن تكون قادرًا على إدارة DOM )نموذج كائن المستند: عناصر HTML التي يعرضها المستعرض ) ديناميكيًا ، ولهذا السبب ، يجب عليك استخدام ربط البيانات أو "databinding" .
Databinding هو الاتصال بين كود TypeScript الخاص بك وقالب HTML الذي يظهر للمستخدم. ينقسم هذا الاتصال إلى اتجاهين:
  • المعلومات من التعليمات البرمجية الخاصة بك التي يجب عرضها في المستعرض ، مثل المعلومات التي قام الكود بحسابها أو استردادها من خادم. الطريقتان الرئيسيتان لهذا هما "استيفاء السلسلة" و "ربط الخاصية" ؛
  • المعلومات الواردة من النموذج والتي يجب أن تتم إدارتها بواسطة الكود: قام المستخدم بملء نموذج أو النقر فوق زر ، ويجب أن نتفاعل وندير هذه الأحداث. سنتحدث عن "ملزمة الحدث" لذلك.
هناك أيضًا حالات مثل الأشكال ، على سبيل المثال ، حيث نريد التواصل ثنائي الاتجاه: لذلك نتحدث عن "ربط ثنائي الاتجاه" .

سلسلة الاستيفاء interpolation


يعد الاستيفاء الطريقة الأساسية لإخراج البيانات من كود TypeScript الخاص بك.
تخيل تطبيقًا يتحقق من حالة أجهزتك الكهربائية في المنزل لمعرفة ما إذا كانت تعمل أم لا. الآن قم بإنشاء مكون جديد   AppareilComponent  باستخدام الأمر التالي:

ng generate component appareil
ثم افتح appareil.component.html )في المجلد الجديد appareil الذي تم إنشاؤه بواسطة CLI ) ، واحذف المحتوى ، وأدخل الكود أدناه:

<li class="list-group-item">
  <h4>Ceci est dans AppareilComponent</h4>
</li>
ثم افتح app.component.html واستبدل كل المحتوى كما يلي:

<div class="container">
  <div class="row">
    <div class="col-xs-12">
      <h2>Mes appareils</h2>
      <ul class="list-group">
        <app-appareil></app-appareil>
        <app-appareil></app-appareil>
        <app-appareil></app-appareil>
      </ul>
    </div>
  </div>
</div>
فئات CSS المستخدمة هنا هي فئات من Bootstrap لتبسيط التخطيط النظيف. يمكن العثور على مزيد من المعلومات في bootstrapdocs.com  .
الآن يجب أن يعرض لك متصفحك شيئًا مثل هذا:
Angular web site
في الوقت الحالي ، لا يوجد شيء مذهل. ستستخدم الآن الاستيفاء لبدء تعزيز بياناتك. يعدل على appareil.component.html النحو التالي:

<li class="list-group-item">
  <h4>Appareil : { { appareilName }}</h4>
</li>
هنا تجد صيغة الاستيفاء: الأقواس المزدوجة   { { }} . ما بين الأقواس المزدوجة هو تعبير TypeScript الذي نريد عرضه ، أبسط تعبير هو متغير. بالإضافة إلى ذلك ، نظرًا لأن المتغير   appareilName  غير موجود حتى الآن ، فإن متصفحك لا يعرض أي شيء في هذا المكان في الوقت الحالي. افتح الآن   appareil.component.ts  : 

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-appareil',
  templateUrl: './appareil.component.html',
  styleUrls: ['./appareil.component.scss']
})
export class AppareilComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}
أضف الآن سطر التعليمات البرمجية التالي في أعلى تعريف الفئة:

export class AppareilComponent implements OnInit {
  
  appareilName: string = 'Machine à laver';

  constructor() { }
تعريف النوع هنا   النقطتان متبوعتان بالنوع string  ليس إلزاميًا ، لأن TypeScript يستنتج تلقائيًا نوع المتغير عند إنشاء مثيل له بقيمة. لقد قمت ببساطة بتضمين تعريف النوع لإظهار بناء جملة TypeScript )ستحتاج إليها في الفصول اللاحقة) .
بعد حفظ الملف ، يعرض متصفحك الآن:
Angular web site
هنا! لديك الآن اتصال بين كود TypeScript الخاص بك ونموذج HTML الخاص بك. في الوقت الحالي ، يتم ترميز القيم على أنها "صلبة" ، ولكن في النهاية ، يمكن حساب هذه القيم من خلال التعليمات البرمجية الخاصة بك أو استردادها من خادم ، على سبيل المثال. أضف الآن متغير جديد في AppareilComponent :

appareilName: string = 'Machine à laver';
appareilStatus: string = 'éteint';
ثم قم بدمج هذا المتغير في القالب:

<li class="list-group-item">
  <h4>Appareil : { { appareilName }} -- Statut : { { appareilStatus }}</h4>
</li>
يعرض متصفحك هذا:
Angular web site
يمكن استخدام أي تعبير TypeScript صالح للاستيفاء. لتوضيح ذلك ، أضف طريقة للملف   AppareilComponent  :

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-appareil',
  templateUrl: './appareil.component.html',
  styleUrls: ['./appareil.component.scss']
})
export class AppareilComponent implements OnInit {

  appareilName: string = 'Machine à laver';
  appareilStatus: string = 'éteint';

  constructor() { }

  ngOnInit() {
  }

  getStatus() {
    return this.appareilStatus;
  }

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

<li class="list-group-item">
  <h4>Appareil : { { appareilName }}} -- Statut : { { getStatus() }}</h4>
</li>
يجب أن يكون لديك نفس النتيجة المرئية في المتصفح.

ربط الخاصية Property binding


يعد الارتباط حسب الخاصية أو "ربط الخاصية" طريقة أخرى لإنشاء اتصال ديناميكي بين TypeScript والقالب الخاص بك: بدلاً من مجرد عرض محتوى متغير ، يمكنك تعديل خصائص عنصر DOM بشكل ديناميكي بناءً على البيانات الموجودة في TypeScript .
لتطبيقك للأجهزة الكهربائية ، تخيل أنه إذا تمت مصادقة المستخدم ، فسيتم تركه مع إمكانية تشغيل جميع الأجهزة في المنزل. بما أن المصادقة هي قيمة عامة ، أضف متغيرًا منطقيًا في   AppComponent المكون الأساسي الخاص بك (يمكنك حذف المتغير   title  لأنك لم تعد تستخدمه ) :

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  isAuth = false;
}
الآن قم بإضافة زر إلى القالب app.component.html ، أسفل قائمة الأجهزة:

<div class="container">
  <div class="row">
    <div class="col-xs-12">
      <h2>Mes appareils</h2>
      <ul class="list-group">
        <app-appareil></app-appareil>
        <app-appareil></app-appareil>
        <app-appareil></app-appareil>
      </ul>
      <button class="btn btn-success" disabled>Tout allumer</button>
    </div>
  </div>
</div>
تقوم الخاصية disabled بإلغاء تنشيط الزر. لربط هذه الخاصية بـ TypeScript ، يجب عليك وضعها بين قوسين معقوفين [] وربطها مع المتغير كما يلي:

<button class="btn btn-success" [disabled]="!isAuth">Tout allume</button>
علامة التعجب تعني أن الزر معطل عندما   isAuth === false . لإظهار أن هذا الارتباط هو دينامية، وخلق طريقة   constructor  في   AppComponent ، والتي يمكنك إنشاء المهلة التي تجمع قيمة   true  في   isAuth  بعد 4 ثوان ( لمحاكاة، على سبيل المثال، وقت مكالمة API ) :

export class AppComponent {
  isAuth = false;

  constructor() {
    setTimeout(
      () => {
        this.isAuth = true;
      }, 4000
    );
  }
}
لرؤية التأثير ، أعد تحميل الصفحة في متصفحك ولاحظ كيفية تنشيط الزر بعد أربع ثوانٍ. في الوقت الحالي لا يفعل الزر شيئًا: ستكتشف كيفية تنفيذ التعليمات البرمجية عندما ينقر عليها المستخدم باستخدام رابط الحدث ، أو "ربط الحدث".

ربط الحدث Event binding


باستخدام سلسلة الاستيفاء وربط الخاصية ، يمكنك التواصل من قالب TypeScript إلى قالب HTML . الآن ، سأوضح لك كيفية التفاعل في كود TypeScript الخاص بك مع الأحداث القادمة من قالب HTML .
لديك حاليًا زر في نموذجك ينشط بعد 4 ثوانٍ. ستضيف الآن وظيفة ذات صلة بحدث "النقر" (يتم تشغيله عندما ينقر عليه المستخدم).
أضف الرابط التالي إلى الزر الخاص بك في قالب HTML :

<div class="container">
  <div class="row">
    <div class="col-xs-12">
      <h2>Mes appareils</h2>
      <ul class="list-group">
        <app-appareil></app-appareil>
        <app-appareil></app-appareil>
        <app-appareil></app-appareil>
      </ul>
      <button class="btn btn-success" 
              [disabled]="!isAuth" 
              (click)="onAllumer()">Tout allumer</button>
    </div>
  </div>
</div>
هنا ، اخترت نشر الكود على عدة أسطر لجعلها أكثر قابلية للقراءة. هذا بالطبع ليس إلزاميًا ، ولكن أنصحك بالتفكير في الأمر عندما يكون للكائن ثلاث خصائص أو أكثر.
كما ترى ، نستخدم الأقواس   ()  لإنشاء رابط لحدث. في الوقت الحالي ، الطريقة   onAllumer()  غير موجودة ، لذا أقترح عليك إنشائها الآن   app.component.ts ، أسفل المنشئ.
هناك اصطلاح تسمية للطرق المتعلقة بالحدث التي استخدمتها هنا: "على" + اسم الحدث. من بين أمور أخرى ، هذا يجعل من السهل متابعة تنفيذ الأساليب عندما يصبح التطبيق أكثر تعقيدًا.
ستعرض الطريقة ببساطة رسالة في وحدة التحكم في البداية:

onAllumer() {
    console.log('On allume tout !');
}
احفظ الملف وافتح وحدة التحكم في متصفحك. عند تنشيط الزر ، انقر فوقه ، وسترى رسالتك تظهر في وحدة التحكم.
حتى إذا ظلت وظيفة بسيطة للغاية في الوقت الحالي ، فإنها توضح لك كيفية ربط دالة TypeScript بحدث قادم من القالب. بشكل عام ، يمكنك ربط التعليمات البرمجية بأي خاصية أو حدث لعناصر DOM . لمزيد من المعلومات ، يمكنك استشارة Mozilla Developer Network أو W3Schools ، على سبيل المثال.

ربط ذو اتجاهين Two-way binding


يستخدم الربط ذو الاتجاهين ربط الخاصية وربط الحدث في نفس الوقت ؛ يتم استخدامه ، على سبيل المثال ، للنماذج ، من أجل أن تكون قادرة على إعلان واسترجاع محتوى الحقول ، من بين أمور أخرى.
لتتمكن من استخدام الربط ثنائي الاتجاه ، تحتاج إلى الاستيراد   FormsModule  من @angular/forms إلى التطبيق الخاص بك. يمكنك إنجاز هذا عن طريق إضافته إلى مجموعة   imports  من AppModule الخاص بك     (لا ننسى أن إضافة العبارة   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';


@NgModule({
  declarations: [
    AppComponent,
    MonPremierComponent,
    AppareilComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
يستخدم الربط ثنائي الاتجاه مزيجًا من قواعد تركيب الملكية وربط الأحداث: الأقواس والأقواس   [()]  . للحصول على عرض توضيحي أول ، أضف   <input>  في نموذجك   appareil.component.html  واربطه بالمتغير   appareilName  باستخدام التوجيه   ngModel  :

<li class="list-group-item">
  <h4>Appareil : { { appareilName }} -- Statut : { { getStatus() }}</h4>
  <input type="text" class="form-control" [(ngModel)]="appareilName">
</li>
في القالب الخاص بك ، سترى   <input>  لكل جهاز. يشار إلى اسم الجهاز بالفعل فيه ، وإذا قمت بتعديله ، فسيتم تعديل محتوى الجهاز     باستخدام. <h4>لذلك ترى أيضًا أن كل مثيل للمكون   AppareilComponent  مستقل تمامًا بمجرد إنشائه: تعديل أحد التغييرات لا يغير أي شيء إلى الآخرين. هذا المفهوم مهم للغاية ، وهو واحد من أعظم استخدامات Angular .

خصائص مخصصة


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

import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-appareil',
  templateUrl: './appareil.component.html',
  styleUrls: ['./appareil.component.scss']
})
export class AppareilComponent implements OnInit {

    @@Input() appareilName: string;
  
  appareilStatus: string = 'éteint';

  constructor() { }

  ngOnInit() {
  }

  getStatus() {
    return this.appareilStatus;
  }

}
لا تنسى الاستيراد   Input  من   @angular/core  أعلى الملف!
في الواقع ، يخلق هذا الديكور خاصية   appareilName  يمكن تعيينها من العلامة   <app-appareil>  :

<div class="container">
  <div class="row">
    <div class="col-xs-12">
      <h2>Mes appareils</h2>
      <ul class="list-group">
        <app-appareil appareilName="Machine à laver"></app-appareil>
        <app-appareil appareilName="Frigo"></app-appareil>
        <app-appareil appareilName="Ordinateur"></app-appareil>
      </ul>
      <button class="btn btn-success"
              [disabled]="!isAuth"
              (click)="onAllumer()">Tout allumer</button>
    </div>
  </div>
</div>
هذه خطوة أولى مثيرة للاهتمام ، ولكنها ستكون أكثر ديناميكية حتى تتمكن من تمرير المتغيرات من   AppComponent  تسمية الأجهزة (يمكننا أن نتخيل جزءًا آخر من التطبيق يمكنه استرداد هذه الأسماء من خادم ، على سبيل المثال). لحسن الحظ ، أنت تعرف بالفعل كيفية استخدام ربط الملكية!
قم أولاً بإنشاء المتغيرات الثلاثة في   AppComponent  :  

export class AppComponent {
  isAuth = false;
  
  appareilOne = 'Machine à laver';
  appareilTwo = 'Frigo';
  appareilThree = 'Ordinateur';

  constructor() {
استخدم الآن الأقواس المربعة [] لربط محتوى هذه المتغيرات بخاصية المكون:

<ul class="list-group">
    <app-appareil [appareilName]="appareilOne"></app-appareil>
    <app-appareil [appareilName]="appareilTwo"></app-appareil>
    <app-appareil [appareilName]="appareilThree"></app-appareil>
</ul>
يمكنك أيضًا إنشاء خاصية لتعيين حالة الجهاز:

export class AppareilComponent implements OnInit {

    @Input() appareilName: string;
    @Input() appareilStatus: string;

  constructor() {
    <ul class="list-group">
    <app-appareil [appareilName]="appareilOne" [appareilStatus]="'éteint'"></app-appareil>
    <app-appareil [appareilName]="appareilTwo" [appareilStatus]="'allumé'"></app-appareil>
    <app-appareil [appareilName]="appareilThree" [appareilStatus]="'éteint'"></app-appareil>
</ul>
لاحظ أنه إذا كنت تستخدم الأقواس لربط الخاصية وترغب في تمرير سلسلة مباشرة إليها ، يجب عليك وضعها في الفواصل العليا ، لأنه بين علامتي الاقتباس ، يجب أن يكون هناك مجلد TypeScript صالح. إذا حذفت الفواصل العليا ، فستحاول تمرير متغير باسم allumé أو éteint لن يتم ترجمة التطبيق.