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


الدرس: استمع إلى المستخدم باستخدام Forms- الطريقة التفاعلية


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

جهز الأرضية


على عكس طريقة القالب حيث تقوم Angular بإنشاء كائن النموذج ، بالنسبة للطريقة التفاعلية ، يجب عليك إنشائه بنفسك وربطه بالنموذج الخاص بك. حتى إذا كان يبدو أكثر تعقيدًا ، فإنه يسمح لك بإدارة النموذج الخاص بك بالتفصيل ، لا سيما مع إنشاء عناصر التحكم آليًا (مما يسمح ، على سبيل المثال ، للمستخدم بإضافة الحقول).
لتوضيح الطريقة التفاعلية ، ستقوم بإنشاء قسم جديد في تطبيق الأجهزة الكهربائية: ستسمح للمستخدمين بإنشاء ملف تعريف مستخدم بسيط. سيستخدم هذا العرض التوضيحي المهارات التي تعلمتها خلال هذه الدورة ، وستقوم أيضًا بإنشاء نموذج البيانات الأول الخاص بك كفئة TypeScript .
ابدأ بالنموذج   User  ؛ إنشاء مجلد جديد   models وداخل ملف   User.model.ts  :

export class User {
  constructor(
    public firstName: string,
    public lastName: string,
    public email: string,
    public drinkPreference: string,
    public hobbies?: string[]
  ) {}
}
وبالتالي يمكن استخدام هذا النموذج في بقية التطبيق عن طريق استيراده إلى المكونات التي تحتاج إليها. يسمح بناء الجملة هذه باستخدام الكلمة الأساسية   new ، وسيتم تعيين الوسيطات التي تم تمريرها إلى المتغيرات التي تم اختيار الأسماء هنا ، على سبيل المثال ،   const user = new User('James', 'Smith', 'james@james.com', 'jus d\'orange', ['football', 'lecture'])  سيتم إنشاء كائن جديد   User  مع تعيين هذه القيم إلى المتغيرات   user.firstName ،   user.lastName   وما إلى ذلك.
ثم قم بإنشاء   UserService  واحدة بسيطة ستقوم بتخزين قائمة الكائنات   User  والتي ستتضمن طريقة لإضافة مستخدم إلى القائمة:

import { User } from '../models/User.model';
import { Subject } from 'rxjs/Subject';

export class UserService {
  private users: User[];
  userSubject = new Subject<User[]>();

  emitUsers() {
    this.userSubject.next(this.users.slice());
  }

  addUser(user: User) {
    this.users.push(user);
    this.emitUsers();
  }
}
تحتوي هذه الخدمة على صفيف خاص من الكائنات من النوع المخصص   User  وموضوع لإرسال هذا الصفيف. emitUsers()  تطلق الطريقة   هذا الموضوع addUser()   وتضيف الطريقة   كائنًا   User  إلى الصفيف ، ثم تطلق الموضوع.
لا تنس أن تضيف هذه الخدمة الجديدة إلى الصفيف   providers  في   AppModule  !
الخطوة التالية هي إنشاء   UserListComponent  - لتبسيط هذا المثال ، لن تقوم بإنشاء مكون للمستخدمين الفرديين:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { User } from '../models/User.model';
import { Subscription } from 'rxjs/Subscription';
import { UserService } from '../services/user.service';

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

  users: User[];
  userSubscription: Subscription;

  constructor(private userService: UserService) { }

  ngOnInit() {
    this.userSubscription = this.userService.userSubject.subscribe(
      (users: User[]) => {
        this.users = users;
      }
    );
    this.userService.emitUsers();
  }



  ngOnDestroy() {
    this.userSubscription.unsubscribe();
  }

}
يشترك هذا المكون البسيط جدًا في الموضوع UserService ويحثه على استرداد المعلومات وإتاحتها للقالب (الذي ستنشئه الآن):

<ul class="list-group">
  <li class="list-group-item" *ngFor="let user of users">
    <h3>{ { user.firstName }} { { user.lastName }}</h3>
    <p>{ { user.email }}</p>
    <p>Cette persone préfère le { { user.drinkPreference }}</p>
    <p *ngIf="user.hobbies && user.hobbies.length > 0">
      Cette personne a des hobbies !
      <span *ngFor="let hobby of user.hobbies">{ { hobby }} - </span>
    </p>
  </li>
</ul>
هنا تطبيق المبادئ التوجيهية   *ngFor  و   *ngIf  لعرض قائمة من المستخدمين وهواياتهم، إذا كان لديهم.
من أجل أن نرى هذا العنصر الجديد، إضافة الطريق   users  في   AppModule ، وخلق   routerLink  . أضف أيضًا كائنًا   User  مشفرًا إلى الخدمة للاطلاع على النتائج:

const appRoutes: Routes = [
  { path: 'appareils', canActivate: [AuthGuard], component: AppareilViewComponent },
  { path: 'appareils/:id', canActivate: [AuthGuard], component: SingleAppareilComponent },
  { path: 'edit', canActivate: [AuthGuard], component: EditAppareilComponent },
  { path: 'auth', component: AuthComponent },
  { path: 'users', component: UserListComponent },
  { path: '', component: AppareilViewComponent },
  { path: 'not-found', component: FourOhFourComponent },
  { path: '**', redirectTo: 'not-found' }
];
<ul class="nav navbar-nav">
    <li routerLinkActive="active"><a routerLink="auth">Authentification</a></li>
    <li routerLinkActive="active"><a routerLink="appareils">Appareils</a></li>
    <li routerLinkActive="active"><a routerLink="edit">Nouvel appareil</a></li>
    <li routerLinkActive="active"><a routerLink="users">Utilisateurs</a></li>
</ul>
private users: User[] = [
    new User('Will', 'Alexander', 'will@will.com', 'jus d\'orange', ['coder', 'boire du café'])
];
لاحظ أنني اخترت عدم حماية الطريق معها AuthGuard  من   أجل تسريع العملية.
الخطوة الأخيرة: إضافة   ReactiveFormsModule ، المستوردة من   @angular/forms ، مجموعة   imports  الخاص بك   AppModule  :

imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    RouterModule.forRoot(appRoutes)
],
الآن بعد أن أصبح كل شيء جاهزًا ، ستنشئ   NewUserComponent  حاوية لنموذجك المتجاوب.

قم بإنشاء نموذج باستخدام FormBuilder


في طريقة القالب ، كان كائن النموذج المقدم من Angular من النوع   NgForm ، ولكن هذا ليس هو الحال بالنسبة للنماذج التفاعلية. النموذج التفاعلي من النوع   FormGroup ، ويتضمن العديد   FormControl  (كلاهما مستورد من   @angular/forms  ) . لذلك ، تبدأ أولاً بإنشاء الكائن في المكون الجديد الخاص بك   NewUserComponent  :

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

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

  userForm: FormGroup;

  constructor() { }

  ngOnInit() {
  }

}
بعد ذلك ، ستقوم بإنشاء طريقة سيتم استدعاؤها في المُنشئ لمحتوى هذا الكائن ، وستقوم أيضًا بحقن   FormBuilder ، مستورد من   @angular/forms  : 

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

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

  userForm: FormGroup;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit() {
    this.initForm();
  }

  initForm() {
    
  }

}
FormBuilder   هي فئة توفر لك أساليب لتسهيل إنشاء الكائنات   FormGroup  . ستستخدم الآن الطريقة   group  داخل أسلوبك   initForm()  لبدء إنشاء النموذج:

initForm() {
    this.userForm = this.formBuilder.group({
      firstName: '',
      lastName: '',
      email: '',
      drinkPreference: ''
    });
}
Group   تأخذ الطريقة   كوسيطة كائنًا حيث تتوافق المفاتيح مع أسماء عناصر التحكم المطلوبة وتتوافق القيم مع القيم الافتراضية لهذه الحقول. نظرًا لأن الهدف هو الحصول على حقول فارغة في البداية ، فإن كل قيمة هنا تتوافق مع السلسلة الفارغة.
ستتم إضافة عناصر التحكم المقابلة hobbies لاحقًا بطريقة أخرى.
يجب عليك الآن إنشاء قالب النموذج وربط هذا القالب بالكائن   userForm  الذي أنشأته للتو:

<div class="col-sm-8 col-sm-offset-2">
  <form [formGroup]="userForm" (ngSubmit)="onSubmitForm()">
    <div class="form-group">
      <label for="firstName">Prénom</label>
      <input type="text" id="firstName" class="form-control" formControlName="firstName">
    </div>
    <div class="form-group">
      <label for="lastName">Nom</label>
      <input type="text" id="lastName" class="form-control" formControlName="lastName">
    </div>
    <div class="form-group">
      <label for="email">Adresse e-mail</label>
      <input type="text" id="email" class="form-control" formControlName="email">
    </div>
    <div class="form-group">
      <label for="drinkPreference">Quelle boisson préférez-vous ?</label>
      <select id="drinkPreference" class="form-control" formControlName="drinkPreference">
        <option value="jus d\'orange">Jus d'orange</option>
        <option value="jus de mangue">Jus de mangue</option>
      </select>
    </div>
    <button type="submit" class="btn btn-primary">Soumettre</button>
  </form>
</div>
تحليل القالب:
  • على العلامة   <form> ، يمكنك استخدام خاصية ملزمة لربط الكائن   userForm  إلى السمة   formGroup  من النموذج، وخلق صلة بين قالب Angular و TypeScript .
  • أيضًا في العلامة   <form> ، لا يزال لديك طريقة   onSubmitForm()  مرتبطة بـ ngSubmit ، ولكنك لم تعد بحاجة إلى تمرير النموذج كوسيطة نظرًا لأنه لديك حق الوصول إليه من خلال الكائن   userForm  الذي أنشأته.
  • على كل   <input>  ما يتوافق مع   control  النماذج ، أضف السمة   formControlName  حيث تمرر سلسلة مناظرة لاسم   control  كائن TypeScript .
  • Submit   يؤدي زر الكتابة إلى   تشغيل الحدث   ngSubmit ، وبالتالي تشغيل الطريقة   onSubmitForm() التي ستنشئها في TypeScript .
لتجميع كل شيء معًا ، قم بحقنه UserService و Router (بدون نسيان استيراده) في مُنشئ المكون ، وقم بإنشاء الطريقة onSubmitForm() :

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { UserService } from '../services/user.service';
import { Router } from '@angular/router';
import { User } from '../models/User.model';

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

  userForm: FormGroup;

  constructor(private formBuilder: FormBuilder,
              private userService: UserService,
              private router: Router) { }

  ngOnInit() {
    this.initForm();
  }

  initForm() {
    this.userForm = this.formBuilder.group({
      firstName: '',
      lastName: '',
      email: '',
      drinkPreference: ''
    });
  }

  onSubmitForm() {
    const formValue = this.userForm.value;
    const newUser = new User(
      formValue['firstName'],
      formValue['lastName'],
      formValue['email'],
      formValue['drinkPreference']
    );
    this.userService.addUser(newUser);
    this.router.navigate(['/users']);
  }

}
  تسترد   الطريقة   onSubmitForm()  القيمة value للنموذج ، وتنشئ كائنًا جديدًا   User  (ليتم استيراده في الأعلى) من قيمة   controls  النموذج. ثم يضيف المستخدم الجديد إلى الخدمة وينتقل   /users  لإظهار النتيجة.
يبقى فقط لإضافة ارتباط   UserListComponent  يسمح بالوصول إلى   NewUserComponent  وإنشاء المسار المقابل   new-user  في   AppModule  :

<ul class="list-group">
  <li class="list-group-item" *ngFor="let user of users">
    <h3>{ { user.firstName }} { { user.lastName }}</h3>
    <p>{ { user.email }}</p>
    <p>Cette persone préfère le { { user.drinkPreference }}</p>
    <p *ngIf="user.hobbies && user.hobbies.length > 0">
      Cette personne a des hobbies !
      <span *ngFor="let hobby of user.hobbies">{ { hobby }} - </span>
    </p>
  </li>
  <a routerLink="/new-user">Nouvel utilisateur</a>
</ul>
نظرًا لأن هذا   routerLink  داخل   router-outlet ، يجب عليك إضافة /  في بداية عنوان URL للانتقال إليه   localhost:4200/new-user  . إذا لم تضعه   / ، فسوف ينتقل هذا الرابط إلى  localhost:4200/users/new-user

{ path: 'users', component: UserListComponent },
{ path: 'new-user', component: NewUserComponent },

المصادقون Validators


كما أن طريقة القالب، هناك أداة التحقق من صحة البيانات في طريقة رد الفعل:   Validators  . لإضافة التحقق ، ستقوم بتعديل تنفيذ   FormBuilder.group  :

initForm() {
    this.userForm = this.formBuilder.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      drinkPreference: ['', Validators.required]
    });
}
بدلاً من سلسلة بسيطة ، تقوم بتمرير مصفوفة إلى كل منها   control ، مع الإعداد الافتراضي المطلوب كعنصر أول ، وكعنصر ثانٍ هو العنصر المطلوب أو أكثر   Validators  (في الصفيف إذا كان هناك عدة) . يجب عليك أيضًا الاستيراد   Validators  من   @angular/forms  . في هذه الحالة ، جميع الحقول مطلوبة email   ويجب أن تكون قيمة الحقل   بتنسيق عنوان بريد إلكتروني صالح (لا يتم بالضرورة تقييم صحة العنوان نفسه).
حتى لو كانت   Validators  الدالات ، لا تضيف الأقواس من   ()  خلال الإعلان عنها هنا. التصريحات من   Validators  في   FormBuilder  إبلاغ Angular بالتحقق المطلوب: تتولى Angular بعد ذلك تنفيذ هذه الوظائف في الوقت المناسب.
من خلال ربط صلاحية   userForm  بخاصية   disabled  للزر   submit ، يمكنك دمج التحقق من صحة البيانات:

<button type="submit" class="btn btn-primary" [disabled]="userForm.invalid">Soumettre</button>
في هذا الفصل ، شاهدت أدوات التحقق من الصحة   required  و   email  : يوجد آخرون ، ولديك أيضًا خيار إنشاء أدوات التحقق المخصصة. لمزيد من المعلومات ، ارجع إلى القسم المقابل من  التوثيق Angular .

إضافة FormControl بشكل ديناميكي


في الوقت الحالي ، لم تترك بعد إمكانية للمستخدم لإضافة هواياته. سيكون من المثير للاهتمام منحه إمكانية إضافة العدد الذي يريده ، ولهذا ستستخدم   FormArray .     FormArray  هو مصفوفة من عدة   FormControl ، ويسمح ، على سبيل المثال ، بإضافة أخرى جديدة   controls  إلى نموذج. ستستخدم هذه الطريقة للسماح للمستخدم بإضافة هواياته.
قم أولاً بتعديل   initForm()  إضافة   FormArray  فارغ والذي سيسمى hobbies بالطريقة   array  :

initForm() {
    this.userForm = this.formBuilder.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
      drinkPreference: ['', Validators.required],
      hobbies: this.formBuilder.array([])
    });
}
ثم قم بتعديل   onSubmitForm()  لاسترداد القيم ، إذا كانت موجودة (وإلا ، قم بإرجاع صفيف فارغ):

onSubmitForm() {
    const formValue = this.userForm.value;
    const newUser = new User(
      formValue['firstName'],
      formValue['lastName'],
      formValue['email'],
      formValue['drinkPreference'],
      formValue['hobbies'] ? formValue['hobbies'] : []
    );
    this.userService.addUser(newUser);
    this.router.navigate(['/users']);
}
من أجل الوصول   controls  إلى داخل المصفوفة ، ولأسباب الكتابة الصارمة المتعلقة بـ TypeScript ، يجب عليك إنشاء طريقة تعود من   hobbies  خلال الطريقة   get()  في شكل   FormArray  (عمليات  FormArray  الاستيراد من   @angular/forms  ) :

getHobbies(): FormArray {
    return this.userForm.get('hobbies') as FormArray;
}
بعد ذلك ، ستنشئ طريقة لإضافة   FormControl  إلى   hobbies  ، مما يسمح للمستخدم بإضافة العدد الذي يريده. ستجعل أيضًا الحقل الجديد مطلوبًا ، حتى لا يكون لديك صفيف   hobbies  بسلاسل فارغة:

onAddHobby() {
    const newHobbyControl = this.formBuilder.control(null, Validators.required);
    this.getHobbies().push(newHobbyControl);
}
تقوم هذه الطريقة بإنشاء   control  باستخدام الطريقة   FormBuilder.control() ، وإضافتها إلى   FormArray  العرض المتاح بواسطة الطريقة   getHobbies()  .
أخيرًا ، أضف جزءا إلى النموذج يسمح لك بإضافة hobbies بإضافة   <input>  :

<div formArrayName="hobbies">
      <h3>Vos hobbies</h3>
      <div class="form-group" *ngFor="let hobbyControl of getHobbies().controls; let i = index">
        <input type="text" class="form-control" [formControlName]="i">
      </div>
      <button type="button" class="btn btn-success" (click)="onAddHobby()">Ajouter un hobby</button>
</div>
<button type="submit" class="btn btn-primary" [disabled]="userForm.invalid">Soumettre</button>
حلل هذا <div> :
  • إلى    السمة <div>  التي تغطي الجزء بأكمله    hobbies ، تقوم بإضافة السمة   formArrayName التي تتوافق مع الاسم الذي تم اختياره في TypeScript ؛
  • و   <div>  للفئة   form-group  تتكرر لكل   FormControl  في   FormArray  (يقع ارجاعها من   getHobbies() ,في البداية فارغة مشيرا الى ان المؤشر لإنشاء اسم فريد لكل   FormControl  .
  • في هذه   <div> ، أنت مع   <input>  سيأخذ   formControlName  كمؤشر لـ   FormControl ؛
  • أخيرًا ، لديك الزر (من النوع الذي   button  ليمنعه من محاولة إرسال النموذج) و الذي يشغل    الطريقة onAddHobby() ، التي ، كتذكير ، تنشئ واحدة جديدة   FormControl  (عرض نسخة جديدة من   <div>  الفئة   form-group ، وبالتالي إنشاء نسخة جديدة   <input>  )
مبروك! الآن أنت تعرف كيفية إنشاء النماذج بطريقتين مختلفتين ، وكيفية استرداد البيانات التي أدخلها المستخدم. في الوقت الحالي ، اقتصرت القدرة على تسجيل وإدارة هذه البيانات على الخدمة ، وبالتالي يتم إعادة تعيين كل شيء بشكل منهجي إلى الصفر في كل مرة يتم فيها إعادة تحميل التطبيق. في الفصول التالية ، ستتعلم كيفية التفاعل مع الخادم (ثم ، بشكل أكثر دقة ، مع الواجهة الخلفية Firebase ) من أجل جعل البيانات دائمة وتطبيقك ديناميكيًا بالكامل.