יצירת Directive מותאם

אפשר ליצור directive שהפעולה שלהם מותאמת להגדרות שלנו.

יצירת Directive חדש

על מנת לייצר Directive נשתמש בשורת הפקודה.

ng g directive directives/class

מה שנקבל הוא הקובץ הבסיסי הבא:

קובץ class.directive.ts

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

@Directive({
  selector: '[appClass]'
})
export class ClassDirective {

  constructor() { }

}

כדי להשתמש ב-directive הזה, נשתמש בשם appClass.

נניח לדוגמא שאנחנו רוצים לשנות את צבע הרקע באלמנט שבו אנחנו משתמשים ב-directive שיצרנו. כדי להחיל את הפעולה, אנחנו צריכים לזהות את האלמנט שאנחנו נמצאים בו. בשביל זה נוסיף לייבוא את ElementRef ונשתמש בו. משתנה element יקבל את האלמנט שעליו הופעל ה-directive.

קובץ class.directive.ts

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[appClass]'
})
export class ClassDirective {
  constructor(private element: ElementRef) {
    this.element.nativeElement.style.backgroundColor = "orange";
  }
}

קובץ app.component.html

<h5 appClass class="mt-3">{{ images[currentPage].title }}</h5>

עכשיו כותרת התמונה תופיע עם רקע כתום.

קריאת משתנים ב-directive

מה שראינו זה פעולה פשוטה של directive. נניח שאנחנו רוצים לתת למשתמש אפשרות לבחור את צבע הרקע של האלמנט.

כדי להעביר מידע מהורה לילד, שלחנו ערך של משתנה בקריאה לקומפוננטה, וקבלנו ערך של Input בקומפוננטת הילד:

//In the parent html
<app-card [title]=" 'Some Title' "></app-card>

//In the child ts
import {Input} from 'angular'
class childComponent{
  @Input() title: string;
}

אותו העקרון עובד גם בשליחת משתנה ל-directive.

קובץ app.component.html

<h5 appClass [backgroundColor]="'yellow'" class="mt-3">{{ images[currentPage].title }}</h5>

עכשיו לכאורה אפשר לשנות את הצבע שמתקבל ב-constructor.

קובץ class.directive.ts

export class ClassDirective {
  @Input() backgroundColor: string;

  constructor(private element: ElementRef) {
    this.element.nativeElement.style.backgroundColor = this.backgroundColor;
  }
}

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

export class ClassDirective {
  constructor(private element: ElementRef) {
  }

  @Input() set backgroundColor(color: string){
    this.element.nativeElement.style.backgroundColor = color;
  }
}

אם רוצים שה-directive יעבוד כמו directives אורגנים שהמשתנה נשלח ישירות לשם ה-directive ולא בנפרד, נוכל לקרוא לפונקציית set עם השם של ה-directive עצמו.

קובץ class.directive.ts

export class ClassDirective {
  constructor(private element: ElementRef) {
  }

  @Input() set appClass(color: string){
    this.element.nativeElement.style.backgroundColor = color;
  }
}

קובץ app.component.html

<h5 [appClass]="'yellow'" class="mt-3">{{ images[currentPage].title }}</h5>

זה עובד, רק שלא לגמרי ברור לפי השם מה הפונקציה עושה. אם רוצים לשמור על השם של הפונקציה set backgroundColor לקריאות טובה יותר, אבל בכל זאת להשתמש ב-appClass עבור השימוש החיצוני כדי שיהיה נוח וברור יותר, אפשר להגדיר את הפונקציה עם alias:

קובץ class.directive.ts

@Input('appClass') set backgroundColor(color: string){
    this.element.nativeElement.style.backgroundColor = color;
}

Directive מבני מותאם אישית

Structural directive הוא directive שמשפיע על המבנה של התוכנית, למשל ngFor או ngIf.

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

<ul *appTimes="5">
  <li>...</li>
</ul>

יצירת ה-directive:

ng g directive directives/times

כצעד ראשון נייבא את הספריות הנדרשות ליצירת Structural directive.

קובץ times.directive.ts

import { Directive, TemplateRef, ViewContainerRef, Input } from '@angular/core';

ניצור אובייקטים לשימוש בסיפריות.

export class TimesDirective {
  constructor(
    private viewContainer: ViewContainerRef,
    private templateRef: TemplateRef<any>
  ) { }
}

ה-viewContainer מתייחס לאלמנטים שעליהם הפעלנו את ה-directive. אובייקט templateRef מתייחס לאובייקטים שבתוך האובייקט שהפעלנו עליו על directive.

export class TimesDirective {
  constructor(
    private viewContainer: ViewContainerRef,
    private templateRef: TemplateRef<any>
  ) { }

  @Input('appTimes') set render(times: number){
    //Clear any prev view to recreate
    this.viewContainer.clear();

    //Look at the view container and try to create new html by the template ref content
    for(let i = 0; i < times; i++){
      this.viewContainer.createEmbeddedView(this.templateRef, {});
    }
  }
}

אפשר לנסות שימוש ב-directive על ידי:

קובץ app.component.html

<ul *appTimes="5">
  <li>Content</li>
</ul>

נראה איך ה-directive החדש עובד בתוך הדוגמא של גלריית התמונות. הלולאה של רשימת התמונות לא משתמשת באובייקט התמונה, אנחנו משתמשים בה רק עבור האינדקס, עבור לולאה אפשר להשתמש ב-appTimes החדש.

זה הקוד הקודם של הלולאה:

קובץ app.component.html

<ng-container *ngFor="let image of images; let i = index">
        <li
          class="page-item"
          [ngClass]="{ active: i === currentPage }"
          *ngIf="checkWindowIndex(i)"
        >
          <a class="page-link" href="#" (click)="currentPage = i">{{ i + 1 }}</a>
        </li>
</ng-container>

כדי לעקוב אחרי האינדקס נוסיף את האפשרות הזאת בקוד ה-directive.

קובץ times.directive.ts

export class TimesDirective {
  constructor(
    private viewContainer: ViewContainerRef,
    private templateRef: TemplateRef<any>
  ) { }

  @Input('appTimes') set render(times: number){
    //Clear any prev view to recreate
    this.viewContainer.clear();

    //Look at the view container and try to create new html by the template ref content
    for(let i = 0; i < times; i++){
      this.viewContainer.createEmbeddedView(this.templateRef, {
        index: i
      });
    }
  }
}

הקוד של הלולאה יראה כך אחרי השינוי:

קובץ app.component.html

<ng-container *appTimes="images.length; let i = index">
        <li
          class="page-item"
          [ngClass]="{ active: i === currentPage }"
          *ngIf="checkWindowIndex(i)"
        >
          <a class="page-link" href="#" (click)="currentPage = i">{{ i + 1 }}</a>
        </li>
</ng-container>

ניווט במאמר

מאמרים אחרונים

Weekly Tutorial