الضوابط المخصصة في الزاوي

فاتحة


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

رد الفعل وأشكال قوالب يحركها


بضع كلمات للتأكد من أننا على نفس الصفحة. تحتوي الزاويّة على نوعين من الأشكال: النماذج المدفوعة بالقالب والنماذج التفاعلية .

النماذج التي تحركها القوالب هي نماذج تستند إلى الربط ثنائي الاتجاه (مرحبًا ، angularjs). نحدد الحقل في الفصل (على سبيل المثال ، اسم المستخدم) ، في html في علامة الإدخال التي نربطها [(value)] = "اسم المستخدم" ، وعندما تتغير قيمة الإدخال ، تتغير قيمة اسم المستخدم. في عام 2011 ، كان هذا السحر لعنة! حسنا ، ولكن هناك فارق بسيط. بهذه الطريقة ، سيكون بناء الأشكال المعقدة أمرًا صعبًا.

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

class UsefullComponent { public control = new FormControl(''); public formG = new FormGroup({username: control}); } 

 <form [formGroup]="formG"> <input type="text" formControlName="username"> </form> 

نتيجة لذلك ، حصلنا على شكل رد فعل ، مع لعبة ورق و ... حسنا ، أنت تفهم ، مع كل وسائل الراحة. على سبيل المثال ، سيمنح formG.valueChanges لنا ملاحظة (دفق) من تغييرات النموذج. يمكنك أيضًا إضافة عناصر تحكم جديدة وحذف عناصر التحكم الموجودة وتغيير قواعد التحقق من الصحة والحصول على قيمة النموذج ( formG.value ) وغير ذلك الكثير. وكل هذا يعمل مع مثيل واحد من formG .

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

 class UsefullComponent { public formG: FormGroup; constructor(private fb: FormBuilder) { this.formG = fb.group({ name: '' //  new FormControl() !! }) } } 

ضوابط مخصصة


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

 import { Component, Input } from '@angular/core'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent { @Input() value = 0; up() { this.value++; } down() { this.value - ; } } 

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

الآن يمكننا استخدام المكون الذي تم إنشاؤه:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control></counter-control> ` }) class ParentComponent {} 

كل شيء يعمل بالطبع ، ولكن أين هي الأشكال؟! على الأقل يحركها القالب ....
لا داعي للذعر ، كل شيء سيكون بعد الاجتماع ControlValueAccessor .

التحكم في قيمة الموصل


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

بفضل ControlValueAccessor ، لدينا طريقة واحدة للعمل مع عناصر التحكم ، والتي ، بالمناسبة ، لا تستخدم فقط لإنشاء عناصر تحكم مخصصة. الزاوي تحت غطاء محرك السيارة يستخدم أيضا هذه الواجهة. من اجل ماذا؟ وفقط لإلقاء نظرة موحدة سلوك الضوابط الأصلية. على سبيل المثال ، في الإدخال ، يتم احتواء القيمة في سمة القيمة ، في خانة الاختيار ، يتم تحديد القيمة من خلال السمة المحددة. وبالتالي ، يكون لكل نوع من عناصر التحكم ControlValueAccessor الخاص به: DefaultValueAccessor للمدخلات و textarea و CheckboxControlValueAccessor لخانات الاختيار و RadioControlValueAccessor لأزرار الراديو ، وهكذا.

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

الآن وقد علمنا حول ControlValueAccessor ، يمكننا تطبيقه على سيطرتنا.

لنلقِ نظرة على واجهته:

 interface ControlValueAccessor { writeValue(value: any): void registerOnChange(fn: any): void registerOnTouched(fn: any): void } 

WriteValue (القيمة: أي) - يتم استدعاؤها عند تعيين المصدر ( FormControl الجديد ('I am default value') ) أو قيمة جديدة أعلى control.setValue ('' I am setted value ') .

registerOnChange (fn: any) - هي الطريقة التي تحدد المعالج الذي يجب استدعاؤه عندما تتغير القيمة (fn عبارة عن رد اتصال يخطر النموذج الذي تغيرت القيمة في عنصر التحكم هذا).

registerOnTouched (fn: any) - يحدد رد الاتصال الذي يتم استدعاؤه في حدث الضبابية (يتم وضع علامة على التحكم باللمس )

 import { Component, Input } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent implements ControlValueAccessor { @Input() value = 0; onChange(_: any) {} up() { this.value++; } down() { this.value - ; } writeValue(value: any) { this.value = value; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched() {} } 


لكي يكون النموذج الأصلي على دراية بالتغييرات في عنصر التحكم ، نحتاج إلى استدعاء أسلوب onChange لكل تغيير في قيمة القيمة. من أجل عدم كتابة مكالمة onChange في كل طريقة (صعودا وهبوطا) ، فإننا ننفذ حقل القيمة من خلال getters و setters:

 // … class CounterControlComponent implements ControlValueAccessor { private _value; get value() { return this._value; } @Input() set value(val) { this._value = val; this.onChange(this._value); } onChange(_: any) {} up() { this.value++; } down() { this.value - ; } // … } 

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

 import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ … providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterControlComponent), multi: true }] }) class CounterControlComponent implements ControlValueAccessor { … } 

في هذا القسم من التعليمة البرمجية ، نقول للزاوية "أرغب في تسجيل عنصر تحكم مخصص جديد ( توفر: NG_VALUE_ACCESSOR ) ، استخدم مثيل مثيل المكون (في هذه اللحظة الذي سيتم فيه تهيئة مكوننا ) للمكون ( useExisting: forwardRef (() => CounterControlComponent )" )) .

multi: true يشير إلى أنه يمكن أن يكون هناك العديد من التبعيات بمثل هذا الرمز المميز ( NG_VALUE_ACCESSOR ).

ليس فقط خلق


حان الوقت ل zayuzat سيطرتنا المخصصة. لا تنس أن تضيف FormsModule / ReactiveFormsModule إلى واردات الوحدة النمطية حيث يتم استخدام عنصر التحكم هذا.

استخدم في النماذج التي تحركها القوالب


كل شيء بسيط هنا ، باستخدام الربط ثنائي الاتجاه عبر ngModel ، نحصل على تغيير في العرض عندما يتغير النموذج والعكس:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control [(ngModel)]="controlValue"></counter-control> ` }) class ParentComponent { controlValue = 10; } 

استخدام في أشكال رد الفعل


كما هو مذكور في بداية المقالة ، قم بإنشاء مثيل للنموذج التفاعلي عبر FormBuilder ، وقم بتقديم html واستمتع:

 import { Component, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; @Component({ selector: 'parent-component', template: ` <form [formGroup]="form"> <counter-control formControlName="counter"></counter-control> </form> ` }) class ParentComponent implements OnInit { form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ counter: 5 }); } } 

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

المواد:

مقال عن الضوابط المخصصة من Pascal Precht.
من الاحواض الاشكال في الزاوي.
سلسلة من المقالات حول rxjs.

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


All Articles