קרה לכם שרציתם להעביר מערך של מידע שהגיע מהשרת למערך של רכיבים בתוך טופס כדי שהמשתמש יוכל לשנות אותם ולשלוח חזרה לשרת?
בטוח שזה קרה לכם 🤣, אם לא, אז זה עוד יקרה.
בואו נראה איך עושים את זה ואחרי זה נעבור על השורות ונבין מה קורה שם:
תחילה נבין את ההגיון ואת הקשר בין המידע לטופס
@Input() externalLinksData: IExternalLinksInfo[] = [ // formArray { // formGroup link: 'https://www.google.com', // formControl title: 'www.google.com', // formControl id: '1q' // formControl }, { // formGroup link: 'https://www.walla.co.il', // formControl title: 'walla', // formControl id: '2w' // formControl }, { // formGroup link: 'https://www.ynet.co.il', // formControl title: 'ynet', // formControl id: '3e' // formControl }, { // formGroup link: 'https://www.ynet.co.il', // formControl title: 'ynet!!', // formControl id: '4r' // formControl }, ];
אם נסתכל על הדברים ככה, זה הרבה יותר מובן איך לבנות את הטופס.
- המערך = formArray
- כל אובייקט במערך = formGroup
- כל מפתח (KEY) במערך = formControl
כמובן שהאובייטים יכולים להיות מסובכים יותר, אבל אם תעבדו ככה תראו שזה הגיוני ולא קשה בכלל.
עכשיו נראה את כל הרכיב
export class PopupExternalLinksComponent implements OnInit { @Input() externalLinksData: IExternalLinksInfo[] = [ // formArray { // formGroup link: 'https://www.google.com', // formControl title: 'www.google.com', // formControl id: '1q' // formControl }, { // formGroup link: 'https://www.walla.co.il', // formControl title: 'walla', // formControl id: '2w' // formControl }, { // formGroup link: 'https://www.ynet.co.il', // formControl title: 'ynet', // formControl id: '3e' // formControl }, { // formGroup link: 'https://www.ynet.co.il', // formControl title: 'ynet!!', // formControl id: '4r' // formControl }, ]; private newExternalLinksData: IExternalLinksInfo[] = []; private delExternalLinksData: IExternalLinksInfo[] = []; @Output() closeEmitter: EventEmitter<boolean> = new EventEmitter<boolean>(); @Output() saveEmitter: EventEmitter<IExternalLinksInfo[]> = new EventEmitter<IExternalLinksInfo[]>(); @Output() deletedLinksEmitter: EventEmitter<IExternalLinksInfo[]> = new EventEmitter<IExternalLinksInfo[]>(); form: FormGroup = {} as FormGroup; get links(): FormArray { const linksData = this.form.get("links") as FormArray; return linksData; } constructor(private fb: FormBuilder) { } ngOnInit(): void { this.newExternalLinksData = [...this.externalLinksData]; this.form = new FormGroup({ links: this.fb.array(this.newExternalLinksData.map(item => { return this.fb.group({ title: new FormControl(item.title, { validators: [Validators.required] }), link: new FormControl(item.link, { validators: [Validators.required] }), id: new FormControl(item.id), }); })), }); } onAdd() { const linksData = this.form.get("links") as FormArray; linksData.push(new FormGroup({ title: new FormControl('', { validators: [Validators.required] }), link: new FormControl('', { validators: [Validators.required] }), id: new FormControl(''), })) } onDelete(idx: number) { const linksData = this.form.get("links") as FormArray; if (linksData.controls[idx].value['id'] !== '') { this.delExternalLinksData.push(linksData.controls[idx].value) } linksData.removeAt(idx); } onCancel() { this.closeEmitter.emit(false); } onSave() { if (this.form.valid) { this.saveEmitter.emit(this.form.value); if (this.delExternalLinksData.length > 0) { this.deletedLinksEmitter.emit(this.delExternalLinksData); } } } }
<main class="main"> <form [formGroup]="form"> <ul class="links-list" formArrayName="links"> <li class="links-item" *ngFor="let linkInfo of links.controls;index as idx"> <div [formGroupName]="idx"> <label class="label"> כותרת: <input type="text" class="normal-input" formControlName="title"> </label> <label class="label"> קישור: <input type="text" class="normal-input" formControlName="link"> </label> <button class="links-delete" type="button" aria-label="מחק" (click)="onDelete(idx)">מחק</button> </div> </li> <li class="add-more"><button type="button" (click)="onAdd()">הוספה</button></li> </ul> <div class="sub-footer"> <div class="action-wrapper"> <button class="submit" type="button" (click)="onSave()" [disabled]="!form.valid">אישור</button> <button class="cancel" type="button" (click)="onCancel()">ביטול</button> </div> </div> </form> </main>
רכיב זה מציג רשימת לינקים שמגיעה מה-"אבא", ניתן לערוך את הלינקים ואת השם שלהם, למחוק אותם וליצור חדשים.
בסיום, אנחנו שולחים את כל הלינקים ובנוסף מבצעים שליחה של הלינקים שיש למחוק מבסיס הנתונים.
בשורה 38 אנחנו מגדירים את ה-form שלנו כקבוצה שמכילה מזהה אחד בשם links.
בשורה 39 אנחנו מגדירים ש-links הוא מערך מסוג formArray , במקרה שלנו, אנחנו משתמשים ב-formBuilder ולכן אנחנו כותבים את זה כך – this.fb.array.
בנוסף אנחנו רצים על המידע שלנו בעזרת map ומחזירים פורמט מתאים לטופס, זאת אומרת, שכל אובייקט הופך ל-formGroup וכל key הופך ל-formControl.
שורה 40 מחזירה את האובייקט בצורה החדשה שלו.
שורות 41-43 הופכות כל KEY ל-CONTROL מתאים שיכול לתקשר עם הטופס ועם ה-HTML.
שימו לב שהמבנה נשאר זהה ב-HTML:
שורה 2 מגדירה את הטופס שלנו
שורה 3 מגדירה את ה-ul כמערך בטופס בשם links
שורה 4 עושה לולאה על links (על המערך שבו controls)
שורה 5 מגדירה קבוצה על פי הערך של idx (כמו לפנות לתא במערך)
שורות 8,12 מבצעות קשר ל-control על פי השם שלו
בשורה 18 יש לנו כפתור הוספה שבכל לחיצה הולך ל-TS ומפעיל פונקציה שמושכת את המערך של ה-formGroups ומוסיפה לו אחד נוסף..
כל פעם ש-linksData משתנה , הלולאה ngFor רצה שוב ומרעננת את הנתונים.
לסיכום:
אנחנו יכולים לראות שמבנה הנתונים, מבנה הטופס ב-TS ומבנה הטופס ב-HTML די תואמים אחד לשני.
זה אומר שבמקרים יותר מסובכים צריך רק להבין מה חלוקת המבנה ואיך להמיר אותו למבנה הטופס.