Angular 2 - ארכיטקטורה


    אנגולר זה Framework לבניית אפליקציות תוך שימוש ב-HTML ו-JavaScript או TypeScript. אנגולר זה פרויקט קוד פתוח שמפותח ומתוחזק ע"י גוגל וקהילה רחבה של מפתחים.
    כתיבת אפליקציית אנגולר מתבצעת ע"י כתיבת HTML שבתוכו משולבים תוספות של אנגולר, כתיבת components כדי לשלוט על ה-templates, כתיבה של services כדי לשלוט על הלוגיקה, ושילוב של components ו- services ליצירת modules.
    הארכיטקטורה של אנגולר (מתוך האתר הרשמי, גם הדוגמאות שלהלן יהיו מאותו האתר):

    באיור לעיל ניתן לראות את שמונה אבני הבניין הראשיים של אנגולר 2:
    1. Modules
    2. Components
    3. Templates
    4. Metadata
    5. Data binding
    6. Directives
    7. Services
    8. Dependency injection

     1.      Modules

    באנגולר מסמנים module ע"י @NgModule. בכל אפליקציית אנגולר יש לפחות module אחד. באפליקציות קטנות יכול להיות רק module אחד (שהוא ה-root module), אבל ברוב האפליקציות יהיו מספר modules. כל module מיועד לפיצ'ר מסוים (feature module), או לסט של פיצ'רים באותו תחום. כל module הוא class עם decorator של  @NgModule.
    במאמר מוסגר נציין ש- decorator זה בעצם פונקציה שמתאימה את ה-class של JS לטובת אנגולר. באנגולר יש המון סוגים של decorator שמוסיפים metadata ל-class כך שאנגולר יידע מה לעשות עם כל class.
    ה- NgModule decorator, זו פונקציה שלוקחת אובייקט metadata שמתאר את ה-module. השדות העיקריים  באובייקט ה-metadata הם:
    ·         declarations – מכיל את ה-view classes שמשתייכים ל-module הזה. באנגולר יש שלוש סוגים של view classes, שהם: components, directives ו-pipes.
    ·         exports – מכיל רשימה מתוך ה-declarations שצריכה להיות זמינה לשימוש ב-templates של components של modules אחרים.
    ·         imports – מכיל רשימה מתוך ה-declarations שיוצאו (נכללו ברשימת exports) מ-modules אחרים ושנצרכים לטובת ה-templates ב-components שב-module הנוכחי.
    ·         providers – מכיל רשימה של services שנמצאים ב-module הנוכחי. ה-services יהיו זמינים בכל האפליקציה (ולא רק ב-module הנוכחי).
    ·         bootstrap – מכיל את ה-view הראשי של האפליקציה (נקרא גם root component). השדה bootstrap צריך להיות רק ב-module הראשי (root module).
    דוגמא ל-root module בסיסי:
    app/app.module.ts:
    import { NgModule }      from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    @NgModule({
      imports:      [ BrowserModule ],
      providers:    [ Logger ],
      declarations: [ AppComponent ],
      exports:      [ AppComponent ],
      bootstrap:    [ AppComponent ]
    })
    export class AppModule { }

    בד"כ הצורה שבה משתמשים ב-root module כ-Module הראשוני היא:

    app/main.ts:
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { AppModule } from './app.module';
     
    platformBrowserDynamic().bootstrapModule(AppModule);

    ספריות אנגולר

    אנגולר זה אוסף של JS modules (יש הבדל בין JS modules ל-angular modules. כל קובץ JS הוא בעצם JS module). כל ספריה של אנגולר מתחילה עם @angular. כדי להשתמש בספריה של אנגולר צריך לייבא אותה ע"י import לדוגמא:
    import { Component } from '@angular/core';
    גם angular modules מיבאים בצורה דומה. לדוגמא:
    import { BrowserModule } from '@angular/platform-browser';

    2.      Components

    ה-component שולט על חלק מהתצוגה, גם מבחינת המראה וגם מבחינת הלוגיקה. מגדירים את הלוגיקה של ה- component בתוך class, וה-class מתקשר עם התצוגה ע"י API של properties ו-methods.

    3.      Templates

    את התצוגה של ה- component מגדירים בשדה של ה-template שלו. ה-template זה בעצם קטע של HTML שמגדיר לאנגולר איך לרנדר את התצוגה. ב-template משלבים בטקסט ה-HTML גם שימוש בקוד של אנגולר. לדוגמא:
    <h2>Hero List</h2>

    <p><i>Pick a hero from the list</i></p>
    <ul>
       <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
          {{hero.name}}
       </li>
    </ul>

    <hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>

    בדוגמא זו ניתן לראות שימוש ב-*ngFor, ובכל מיני סוגריים שהם חלק מה-syntax של אנגולר כמו {{hero.name}}, (click), [hero]. כמו כן ניתן לראות שימוש בתג חדש שיצרנו <hero-detailed> שמייצג את ה-component שיצרנו HeroDetailComponent.

    4.      Metadata

    ה-metadata מכיל מידע שמסביר לאנגולר מה לעשות עם ה-class. ב-TypeScript מצמידים metadata ל-class ע"י decorator. לדוגמא:
    @Component({
      moduleId: module.id,
      selector:    'hero-list',
      templateUrl: 'hero-list.component.html',
      providers:  [ HeroService ]
    })
    export class HeroListComponent implements OnInit {
    /* . . . */
    }
    בדוגמא זו ניתן לראות component decorator שמזהה את ה-class שכתוב לאחריו כ-component class. ה-decorator מקבל configuration object שמכיל את המידע הנצרך לאנגולר כדי ליצור את ה-component ואת ה-view שלו:
    ·         moduleId – קובע את ה-base address ל-URLs היחסיים, כמו templateUrl.
    ·         selector – זהו CSS selector שמייצג את ה-component הנוכחי. בכל מקום ב-HTML שיופיע ה-selector הזה - <hero-list>, אנגולר יכניס לשם את ה-view של ה-component הזה.
    ·         templateUrl – מיקום יחסי של HTML template של ה-component.
    ·         providers – מערך של services שנדרשים ל-component הנוכחי מה-dependency injection.
    אפשר לסכם שה-metadata מספק לאנגולר רשימה של אבני הבניין שנדרשים ל-component.
    ה-template יחד עם ה-metadata וה-component מתארים את ה-view.

    5.      Data binding

    אנגולר מספק מספר אפשרויות של data binding בין ה-component ל-DOM:
    1.      מה-component ל-DOM
    2.      מה-DOM ל-component
    3.      לשני הכיוונים (two way data binding)
    האיור הבא, מהאתר הרישמי, מציג את אפשרויות ה-data binding:

    לדוגמא:
    <li>{{hero.name}}</li>
    <hero-detail [hero]="selectedHero"></hero-detail>
    <li (click)="selectHero(hero)"></li>

    ·         שימוש בסוגריים מסולסלים כפולים, {{hero.name}}, נקרא interpolation. צורה זו היא דוגמא של data binding מספר 1. הערך של ה-hero.name property יוצג באלמנט <li>.
    ·         שימוש בסוגריים מרובעים, [hero], נקרא property binding. גם צורה זו היא דוגמא של data binding מספר 1. במקרה הזה הערך של selectedHero יועבר ל-hero property, ולאחר מכן יוצג ב-DOM ע"י HeroDetailedComponent.
    ·         שימוש בסוגריים עגולות, (click), נקרא event binding. צורה זו היא דוגמא של data binding מספר 2. כאן מועבר מידע (במקרה הזה event) מה-DOM אל ה-component.
    ה-data binding מספר 3, שנקרא two way data binding, משלב property binding יחד עם event binding. צורת הישמוש היא ע"י סוגריים עגולים בתוך סוגריים מרובעים, לדוגמא:
    <input [(ngModel)]="hero.name">
    ב- two way data binding אם ה-property משתנה ב-component אז הוא ישתנה גם ב-DOM (לדוגמא ב-input box), כמו ב-property binding. ואם המשתמש שינה את הערך ב-DOM אז השינוי יעבור גם ל-component, כמו ב-event binding.
    באנגולר עושים הרבה שימוש ב-binding בתקשורת בין ה-template ל-component. כמו כן, משתמשים בזה גם בתקשורת בין component אבא ל-component בן.

    6.      Directives

    ה-directive הוא class עם decorator של @directive. ה-component הוא בעצם directive עם template, אך כיון שהוא כל כך חשוב באריכטקטורה של אנגולר, ישנה הפרדה בהסבר בים component ל-directive.
    ישנם שני סוגים נוספים של directive:
    ·         Structural directive
    ·         Attribute directive
    ה-structural directive משנה את התצוגה ע"י הוספה, הסרה או החלפה של אלמנטים ב-DOM. לדוגמא:
    <li *ngFor="let hero of heroes"></li>
    <hero-detail *ngIf="selectedHero"></hero-detail>
    ה-*ngFor אומר לאנגולר לתת <li> אחד לכל hero מרשימת heros.
    ה-*ngIf מציג את ה-heroDetail component רק אם ה-hero הנבחר קיים.
    ה-attribute directive משנה את התצוגה או את ההתנהגות של אלמנט קיים. ב-template ה-directive הזה נראה כמו HTML attributes רגילים, ולכן זה נקרא attribute directive.
    ה-ngModel directive, שנותן לנו two way data binding,  הוא דוגמא ל-attribute directive. ה-ngModel משנה את ההתנהגות של אלמנטים קיימים ע"י השמה של ערכים לתצוגה של האלמנט (ל-<input> למשל), וע"י תגובה ל-event של שינוי.
    <input [(ngModel)]="hero.name">
    באנגולר יש עוד כמה directives, לדוגמא: ngSwitch, ngStyle, ngClass. כמו כן יש אפשרות לכתוב directive בעצמנו.

    7.      Services

    ה-service זה class שמספק לנו שירות מסוים. למשל: logging service, data service, tax calculator, application configuration.
    ה-services זה אחד מאבני היסוד של אנגולר. ה-components הם הצרכנים הראשים של services. דוגמא ל-service פשוט:
    export class Logger {
      log(msg: any)   { console.log(msg); }
      error(msg: any) { console.error(msg); }
      warn(msg: any)  { console.warn(msg); }
    }
    ה-components אמורים להיות יחסית קטנים. ב-component לא מיישמים את התקשורת עם השרת, לא עושים input validation וגם לא מיישמים כתיבה ל-console. כל זה ודברים דומים לכך מיישמים ב-services.
    התפקיד של ה-component זה לדאוג לחויית ההמשתמש. הוא מאגד את ה-view (ממומש ב-template) יחד עם הלוגיקה. אבל אנגולר לא כופה את הארכיטקטורה הזו, וגם אם ה-component יטפל בהכל בעצמו מבחינת אנגולר זה יהיה בסדר. אנגולר עוזר במימוש הארכיטקטורה הזו ע"י כך שהוא עושה את ה-services זמינים לכל ה-components בעזרת ה-dependency injection.

    8.      Dependency injection

    ה- dependency injection זו הדרך לספק לכל class חדש את כל הדברים שהוא תלוי בהם. רוב ה-dependencies הם services. אנגולר יודע מה ה-services הדרושים ל-component מתוך הפרמטרים שב-constructor. לדוגמא:
    constructor(private service: HeroService) { }
    ה-class הזה צריך את HeroService.
    כאשר אנגולר יוצר component הוא בהתחלה מבקש מה-injector את ה-services הדרושים. ה-injector מבחינתו מתחזק container של services instances שהוא יצר עד כה. אם ה-service המבוקש קיים כבר ב-container אז אנגולר קורא ל-constructor של ה-component עם כל ה-services הדרושים. אם לאחד ה-services עדיין אין instance ב-container אז ה-injector יוצר instance של ה-service ושומר אותו ב-container לפני שהוא חוזר לאנגולר. רק לאחר שכל ה-services הדרושים שמורים ב-container רק אז אנגולר קורא ל-constructor של ה-component עם כל ה-services הדרושים.
    כאשר ה-injector צריך ליצור instance של service מסוים, הוא משתמש במערך providers שהוא זה שיכול ליצור instance של service. ניתן לכתוב את ה-providers ב-modules או ב-components. באופן כללי, עדיף לכתוב את כל ה-providers ב-root module. כך שאותו instance של service יהיה קיים בכל האפליקציה.
    ניתן גם לכתוב providers בתוך component. המשמעות היא שנוצר instance חדש של service לכל instance חדש של ה-component.
    כמה נקודות חשובות לגבי ה-dependency injection:
    ·         ה-DI הוא חלק מאנגולר ונמצא בשימוש בכל האפליקציה
    ·         ה-DI מייצר ומתחזק container של services instances
    ·         ה-DI יכול ליצור instance חדש של service ע"י שימוש ב-provider.

    9.      סיכום

    ·         למדנו את שמונה אבני הבנין של אנגולר, הכוללים את:
    ·         Modules
    ·         Components
    ·         Templates
    ·         Metadata
    ·         Data binding
    ·         Directives
    ·         Services
    ·         Dependency injection
    וזה ידע בסיסי טוב כדי להתחיל ולכתוב אפליקציות אנגולר 2.
    כמובן שיש עוד הרבה מה ללמוד. להלן מספר נושאים חשובים באנגולר:
    Animations – ניתן ליצור component לאנימציה גם בלי ידע נרחב באנימציות, ע"י שימוש בספרית האנימציה של אנגולר.
    Change detection – הצורה שבה אנגולר מחליט שהערך של-property של component מסוים השתנה, ומתי לעדכן את המסך.
    Events – גם components וגם services יכולים לשלוח events. כמו כן, כל מנגנוני פרסום ה-event והרשמה ל-event גם כלולים כאן.
    Forms - תומך בתרחישי הכנסת נתונים מורכבים עם וולידצית HTML-based.
    HTTP – נועד לצורך יצירת קשר עם השרת כדי לקבל מידע, לשמור מידע, ולקלוט את ה-server side actions ע"י ה-HTTP client.
    Lifecycle hooks – מאפשר להתחבר לנקודות מרכזיות במחזור החיים של components, מיצירת ה-component ועד לסגירתו ע"י שימוש ב-lifecycle hook interface.
    Pipes – משמשים לשיפור ה-UX בכך שהם משנים תצוגה של ערכים מסוימים. לדוגמא, הצגת ערך כספי:
    price | currency:'USD':true
    יציג 42.33$.
    Router – ניווט מדף לדף בתוך ה-client בלי לעזוב את ה-browser.
    Testing – שימוש ב-Angular Testing Platform לצורך הרצת unit tests על האפליקציה.

    בקיצור, יש עוד הרבה מה ללמוד, אז קדימה חבל על הזמן...