RSS

Angular 2.0 Forms Deep Dive

08 ינו

Angular 2.0 Forms Deep Dive

 

אנגולר 2.0 שיכתב מחדש את הקוד שמטפל בטפסים. בפוסט זה אני רוצה לצלול לתוך הקוד של אנגולר 2.0 ולהסביר את השינויים שנעשו בתחום הטפסים. ממליץ בחום, לפני שאתם קוראים את פוסט זה לקרוא קודם את המאמרים הבאים:

1. המאמר שבאתר הרשמי של אנגולר. (Developer Guides – Forms)

2. את הפרק The Ultimate Guide to Forms in Angular 2 בספר ng-book 2.0, הפרק חינם.

מאמרים אלו ילמדו אתכםאותכם איך עובדים עם Angular Forms, אך לא איך זה עובד "מתחת למכסה המנוע".

מטרת פוסט זה להסביר איך הדברים עובדים.

 

חלוקה לשכבות:

clip_image002

 ציור 1

השכבה העליונה מיצגת את ה- Form Directives. באנגולר 2.0 יש כ- 15 Directives Form שתפקידם לתת לנו גישה לשיכבה של ה- Control’s ששם יש את הלוגיקה האמתית.

 

אני אתחיל באבא ("הלב") של כל המחלקות והוא AbstractControl.

 

הלב של התשתית הוא AbstractControl, שכבת ה- Control’s:

clip_image004

אפשר להקביל את AbstractControl ל- NgModelController מגירסת אנגולר 1.0.

ה- AbstractControl אחראי על מספר יכולות: ( הקוד מוצג רק בצורה חלקית למען התמקדות בעיקר )

1.     מעקב אחרי שינויים ועידכון ה- class המתאים על ה-DOM. כמו שהיה באנגולר 1.0.

get value(): any {…}

get status(): string {…}
get valid(): boolean
{…}

get pristine(): boolean {…}
get dirty(): boolean
{…}

get touched(): boolean {…}
get untouched(): boolean
{…}

clip_image006

2.     בדיקת תקינות ורשימת טעויות:

private _runValidator() {...}
private _runAsyncValidator(emitEvent: boolean): void
{...}

get errors(): {[key: string]: any
} {...}
get pending(): boolean
{...}
setErrors(...):
void
{...}
getError(errorCode:
string, path: string[] = null): any
{...}
hasError(errorCode:
string, path: string[] = null): boolean
{...}

 

3.     מעקב אחרי שינויים של ערכים וסטאטוס, מצריך שימוש בספריה Rx:

get valueChanges(): Observable<any> {...}
get statusChanges(): Observable<any
> {...}

 

 

4.      חיפוש (Find) פקדים אחרים בטופס.

מה אין כאן לעומת ה- NgModelController מגירסת אנגולר 1.0:

1.     אין תמיכה ב- formatters ו- parsers, לא צריך. ברגע שעוברים לעבוד עם properties (object.defineproperty)  אפשר להכניס את הלוגיקה לתוך ה- get ו-set ולא מערבים בכך את שכבת ה-UI, כלמור יותר קל לביצוע Unit Testing.

2.     אין תמיכה ב- debounce. נוצר בעיקר בגלל בעיות ביצועים. אין בעיות ביצועים באנגולר 2.0 אז לא צריך את זה J. עכשיו ברצינות… ע"י שימוש ב- valueChanges והטכנולוגיה Rx ניתן לקבל את אותו אפקט וגם יותר קל לביצוע Unit Testing.

איך המחלקה AbstractControl קשורה לטופס שלי?

clip_image008

ציור 2

השימוש ב-directives Form בתבנית יוצר מופעים של המחלקה AbstractControl. המחלקה זו משמשת "כצינור" בין המחלקה שלי לתבנית, שדות ה-inputs השונים.

אנחנו יכולים לפתח טופס בשני שיטות:

1.     Model Driven

2.     Template Driven

Model Driven:

בגישה זו אנחנו בונים את עץ הפקדים בעצמנו ע"י קוד ולא ע"י Template. אנחנו יכולים להשתמש במחלקה FormBuilder שתעזור לנו. ראו קוד:

clip_image010

Figure 3

עכשיו אתם בטח מקללים… שיטה זו נועדה לחובבי ה- Unit Testing. פיתוח ב- Model Driven מאפשרת לי הפרדה יותר טובה בין שכבת ה-UI לשיכבת הלוגיקה של הטופס. שימו לב שה- Directives שנמצאים על ה-Template רק עוטפים (הכלה) את המחלקות של השכבה של הפקדים, במילים פשוטות את AbstractControl.

יכולת זו היא חדשה לאנגולר 2.0.

Template Driven:

גישה זו היא הגישה המוכרת מאנגולר 1.0, אך גם בה יש הרבה חידושים. להל"ן ה-directives שעובדים לרשותינו:

clip_image012

כמו שאתם רואים, העץ מתחלק לשתים, קובצת השדה (input) וקובצת הטופס (form). אני אתחיל מהאבא, AbstractControlDirective.

ה- AbstractControlDirective עוטף את AbstractControl ( מכיל אותו ) וחושף property אחד חדש path.  לסכום לא עושה הרבה. גם ה- NgControl לא עושה הרבה (abstract class), הוא רק מוסיף לנו את הגישה ל- validators.

קחו אוויר מתחילים לצלול J

NgModel Directive:

בפעם הראשונה שמפעילים את המתודה ngOnChanges בדרקטיב NgModel מתבצעת קריאה למתודה setUpControl (נמצאת בקובץ shared).

מתודה זו מגדירה את הקשר הדו כיווני. להל"ן הקוד של המתודה.

export function setUpControl(control: Control, dir: NgControl): void {

 

 

  control.validator      = Validators.compose([control.validator, dir.validator]);

  control.asyncValidator = Validators.composeAsync([control.asyncValidator,
                                                              dir.asyncValidator]
);

 

  dir.valueAccessor.writeValue(control.value);

 

  // view -> model

  dir.valueAccessor.registerOnChange(newValue => {

    dir.viewToModelUpdate(newValue);

    control.updateValue(newValue, {emitModelToViewChange: false});

    control.markAsDirty();

  });

 

  // model -> view

  control.registerOnChange(newValue => dir.valueAccessor.writeValue(newValue));

 

  // touched

  dir.valueAccessor.registerOnTouched(() => control.markAsTouched());

}

המתודה הזו מאוד חשובה להבנה. למה היא לא בתוך המחלקה AbstractControlDirective ?

מה זה valueAccessor? הוא הדבק בין ה- DOM ל- Directive. להל"ן הקוד.

@Directive({

  selector:

      'input:not([type=checkbox])[ngControl],

input:not([type=checkbox])[ngFormControl],

input:not([type=checkbox])[ngModel],

textarea[ngControl],

textarea[ngFormControl],

textarea[ngModel],

[ngDefaultControl]',

 

  host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},

  bindings: [DEFAULT_VALUE_ACCESSOR]

})

export class DefaultValueAccessor implements ControlValueAccessor {

  onChange = (_) => {};

  onTouched = () => {};

 

  constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}

 

  writeValue(value: any): void {

    var normalizedValue = isBlank(value) ? " : value;

    this._renderer.setElementProperty(this._elementRef, 'value', normalizedValue);

  }

 

  registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }

  registerOnTouched(fn: () => void): void { this.onTouched = fn; }

}

כמו שאתם רואים, הוא מאזין לשני אירועים blur ו- onChange. יש כמה מחלקות כאלו למקרים שונים של UI.

clip_image014

אם ננסה להשוות את זה לאנגולר 1.0, זה הכלאה של ng-model-option והיכולת להגדיר render ב- ngModelController, ראו פוסט נחמד בנושא.

 

אז מה היה לנו כאן?  <input type="text" [(ngModel)]="name">

כאשר onChange של input נורה מתחיל ה-value של input לעבור את התהליכים הבאים:

1.     עידכון הערך ב-Directive (viewModel).

2.     ה-Directive יורה את האירוע ngModelChange.

3.     ה-control מעדכן את שדה ה- _value.

4.     ה- controlמריץ את הבדיקות (validators)

5.     ה- controlמעדכן את ה-status

6.     ה- controlיורה את האירועים valueChanges ו-statusChanges

7.     הטופס חוזר על סעיפים 4-6

8.     השדה שלנו (name) מתעדכן בעקבות סעיף 2.

 

לכיון השני, כלומר שהשדה שלנו משתנה:

1.     ngOnChange מופעל על ה-Directive (NgModel).

2.     מעדכנים את ה-DOM.

3.     ה- controlמריץ את הבדיקות (validators)

4.     ה- controlמעדכן את ה-status

5.     ה- controlיורה את האירועים valueChanges ו-statusChanges

6.     הטופס חוזר על סעיפים 4-6

7.     הערך ב-model מעודכן ל-viewModel

טבלת השוואה לאנגולר 1.0

ngModelController

NgModel

Control

My Component

$viewValue

viewModel

 

 

$modelValue

Model

value

 

$formatters

אין

אין

Get property

$parsers

אין

אין

Set property

$validators

validators         

validator          

 

$asyncValidators

asyncValidators

asyncValidator

 

$render

valueAccessor

 

 

$rollbackViewValue

אין

אין

 

debounce

אין

אין

 

$error

 

errors

 

$pristine

 

pristine

 

$dirty

 

dirty

 

$valid

 

valid

 

$invalid

 

אין

 

$touched

 

touched

 

$untouched

 

untouched

 

$pending

 

pending

 

אין

 

status

 

אין

 

valueChanges

 

אין

 

statusChanges

 

 

 

שאלה: מה ההבדל בין NgModel ל- NgControl ול- NgFormControl?

תשובה: סתכלו בקוד על ה- selector של כל דרקטיב.

clip_image016

1.     כאשר עובדים רק עם NgModel הוא יוצר בעצמו את האוביקט Control. אין לנו גישה לאוביקט זה דרך ה-Template. כלומר אין לנו צורך לעבוד עם כל המידע שיש ב-control בצד ה-template.

 

2.     כאשר עובדים עם NgControl מקבלים בצד ה-template גישה ל-control ואוביקט זה הוא גם NgModel, כלומר NgModel משמש כ-attribute של הדרקטיב NgControl.

 

3.     כאשר עובדים עם NgFormControl מקבלים את שני העולמות. אנחנו יוצרים את המופע של control בקוד וגם מקבלים עליו גישה ב-template. אפשר לומר שזו הכלאה של סעיפים 1 ו-2.

 

שאלה: מה ההבדל בין NgForm ל- NgFormModel?

תשובה:

שאלה: ExpertAs מה זה?

תשובה: כאשר רוצים referance ל-Form Directive צריך להגדיר משתנה ולהתחל אותו בערך שמוגדר בתוכנה ExprtAs על ה- DirectiveMetadata. ראו טבלה.

How to use in Template

ExportAs value

Directive

<input [(ngModel)]    =”name” #c=”ngForm”/>

ngForm

NgModel

<input ngControl         =”name” #c=”ngForm”/>

ngForm

NgControl

<input ngFormControl=”name” #c=”ngForm”/>

ngForm

NgFormControl

<form                                                      #f=”ngForm”/>

ngForm

NgForm

<form ngFormModel   =”myForm”   #f=”ngForm”/>

ngForm

NgFormModel

<form ngControlGroup=”myForm”  #f=”ngForm”/>

ngForm

NgControlGroup

 

השורות שצבועות בורוד לא יוצרות control אוביקט אלה מתחברות לאוביקט קיים שנמצא על ה-component שלנו, זה סגנון ה- model Driven. כל השאר יוצרים את ה-control בעצמם ואין לנו גישה עליו מה- component שלנו, זה סגנון ה- Template Driven.

שאלה: מה חסר? מה לא גיליתי לכם? איפה מעדכנים את ה- Classes של ה-DOM? (ng-valid, ng-dirty…)

תשובה: ב- NgControlStatus Directive. לקח לי זמן למצוא את הממזר הקטן J

מהסתכלות על הקוד אנחנו מבינים שאנחנו יכולים לבנות בקלות NgControlBootstrapStatus שבמקום ה- CSS Classes של אנגולר אפשר לשים שמות של מחלקות של Bootstrap, כמו למשל, במקום ng-valid לשנות ל- success.

סיכום:

להיכנס לקוד של אנגולר זה קשה… אין מספיק תיעוד, מסמכים של ארכיטקטורה ולכן חייבים להשקיע המון זמן עד שמוצאים את כל הנקודות החשובות כדי להבין איך הדברים עובדים. אני מקווה שמפתחים שיש להם את הסקרנות לחקור את הדברים, קיבלו כאן בצורה מרוכזת את הדגשים והמקומות החשובים בקוד של אנגולר כדי להבין איך אנגולר טפסים עובד.

אשמח לקבל פידבקים… או תיקונים.

 

בקורס הקורב שלי על אנגולר אני מכניס פרק חדש על אנגולר 2.0 ואיך מכינים את הקוד של אנגולר 1.0 למעבר לגירסה 2.0.

תאריכים: ב-25,27,31 לינואר ו-3 לפברואר. לפרטים נוספים הירשמו באתר הקורס ng-course.org.

מודעות פרסומת
 
תגובה אחת

פורסם ע"י ב- ינואר 8, 2016 ב- Angular 2.0

 

תגובה אחת ל-“Angular 2.0 Forms Deep Dive

  1. יעקב

    ינואר 8, 2016 at 3:42 pm

    Is there english translation of this article
    ?

     

כתיבת תגובה

הזינו את פרטיכם בטופס, או לחצו על אחד מהאייקונים כדי להשתמש בחשבון קיים:

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s

 
%d בלוגרים אהבו את זה: