typescript - מה המטרה של providedIn עם הדקורטור Injectable כאשר מייצרים שירותים ב-Angular 6?
כאשר מייצרים שירותים ב-Angular CLI, הוא מוסיף מטאדטה נוספת עם תכונה 'provided in' עם ברירת מחדל של 'root' עבור דקורטור Injectable.
@Injectable({
providedIn: 'root',
})
מה בדיוק עושה providedIn? אני מניח שזה מאפשר שהשירות יהיה זמין כמו שירות מסוג 'גלובלי' לכל האפליקציה, אבל האם לא יהיה יותר נקי להצהיר על שירותים כאלה במערך ה-provider של ה-AppModule?
תשובה #1
providedIn: 'root' היא הדרך הקלה והיעילה ביותר לספק שירותים מאז Angular 6:
- השירות יהיה זמין בכל האפליקציה כיחיד ללא צורך להוסיפו למערך הספקים של מודול (כמו Angular <= 5).
- אם השירות משמש אך ורק בתוך מודול שנטען בעצלות, הוא ייטען בעצלות יחד עם אותו מודול
- אם זה לעולם לא ישומש, זה לא ייכלל בבנייה (מנוקה עץ).
למידע נוסף כדאי לקרוא את התיעוד ושאלות נפוצות על NgModule
דרך אגב :
- אם אינך רוצה סינגלטון ברחבי היישום, השתמש במקום זאת במערך של ספק של רכיב.
- אם אתה רוצה להגביל את ההיקף כך שאף מפתח אחר לא ישתמש בשירות שלך מחוץ למודול מסוים, השתמש במערך
providersשל NgModule במקום.
תשובה #2
מהמסמכים
מהו דקורטור Injectable?
מסמן מחלקה כזמינה ל-Injector ליצירה.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class UserService {
}
השירות עצמו הוא מחלקה שה-CLI ייצר והיא מקושטת ב-@Injectable().
מה בדיוק עושה providedIn?
קובע אילו מזריקים יספקו את הניתן להזרקה, על ידי קישורו ל-@NgModule או ל-InjectionType אחר, או על ידי ציון שהניתן להזרקה הזה צריך להיות מסופק במזרק ה-'root', שיהיה מזרק ברמת היישום ברוב האפליקציות.
providedIn: Type<any> | 'root' | null
providedIn: 'root'
כאשר אתה מספק את השירות ברמת השורש, Angular יוצר מופע יחיד ומשותף של השירות ומזריק אותו לכל מחלקה שביקשה אותו. הרשמת הספק בפרטי המטה-נתונים של @Injectable() גם מאפשרת ל-Angular למטב את האפליקציה על ידי הסרת השירות מהאפליקציה המהודרת אם הוא אינו בשימוש.
providedIn: מודול
ניתן גם לציין ששירות יסופק ב-@NgModule מסוים. לדוגמה, אם אינך רוצה ששירות יהיה זמין ליישומים אלא אם הם מייבאים מודול שיצרת, ניתן לציין שהשירות יסופק במודול
import { Injectable } from '@angular/core';
import { UserModule } from './user.module';
@Injectable({
providedIn: UserModule,
})
export class UserService {
}
שיטה זו מועדפת מכיוון שהיא מאפשרת Tree-shaking (Tree shaking הוא שלב בתהליך בנייה שמסיר קוד שאינו משומש מבסיס הקוד) של השירות אם אף דבר לא מזריק אותו.
אם לא ניתן לציין בשירות איזה מודול צריך לספק אותו, ניתן גם להצהיר על ספק עבור השירות בתוך המודול:
import { NgModule } from '@angular/core';
import { UserService } from './user.service';
@NgModule({
providers: [UserService],
})
export class UserModule {
}
תשובה #3
זוהי תגובה חדשה שמתעדכנת עם Angular 9+ (לאחר Ivy) וצריכה להיות נכונה ב-2022.
לסיכום: הכל קשור לשליטה בכמה מופעים של השירות שלך ייווצרו, ואיפה הם צריכים להיות זמינים לאחר שנוצרו.
טרמינולוגיה:
ניתן להזרקה - כל מחלקה שמעוטרת ב-
@Injectable, לדוגמה שירות.מזריק - מחלקת Angular המסוגלת לספק Injectables למחלקות שמתחתיה. (זה כולל את כל הרכיבים והמודולים.)
טווח/רמת המזרק - טווח כל מופעי המחלקה שנמצאים "מתחת" למזרק מסוים.
היררכיית מזרקים - עץ מועדף של תחומי מזרק, מסודר בסדר
פלטפורמה -> שורש -> מודול -> רכיב.הזריקה היא מסופקת - מופע של ההזרקה יינתן למחלקות שמתחת לרמת המזרק הספציפית הזו, בכל פעם שהן יבקשו אותו.
Injectable הוא מודבק - בונה מחלקה ביקש לקבל כמה מופעים של השירות, ולכן Angular תנסה לתת לו את המופע הקרוב ביותר שניתן למצוא בהיררכיית המזרק.
ניקוי-עצים - אופטימיזציה שמתרחשת אוטומטית הודות לקומפילטור של Angular. כאשר הוא מזהה שקוד מסוים אינו בשימוש, הקוד הזה מוסר מהקומפילציה הסופית של האפליקציה (או קומפילציה של מודול טעון-במצב עצל מסוים).
מונחים אחרים שכדאי שכבר תדע: מחלקה, מופע, מודול, רכיב, מודולים נטענים באופן עצלני/נלהב.
ש: מה בדיוק עושה providedIn?
זו הגדרה שמחליטה אילו מכניסים צריכים לספק את החומר הניתן להזרקה שלך.
נניח שאנחנו יוצרים שירות הניתן להזרקה בשם MyService, ונעבור על מה שכל האפשרויות עושות.
providedIn: Type<any> | 'root' | 'platform' | 'any' | null
providedIn: 'platform'
Angular יוצר ומספק מופע משותף יחיד של MyService לכל יישומי Angular בדף. (זה רלוונטי רק במקרים שימוש מתקדמים, אם אתה משתמש בארכיטקטורת מיקרו-Frontend.)
providedIn: 'root'
Angular ייצור מופע משותף יחיד של MyService ויספק אותו לכל המחלקות באפליקציה.
providedIn: 'any'/מיושן מאז Angular v15
אנגולר תיצור מופע משותף יחיד של MyService ותספק אותו לכל המחלקות במודולים שמוטענים באופן מיידי.
עם זאת, כל מודול נטען בעצלות יספק את המופע שלו, החדש והנפרד של MyService (שאז יהיה זמין רק במחלקות בתוך אותו מודול).
providedIn: MyModule/מיושן מאז Angular v15
אנגולר יווצר מופע של MyService רק אם MyModule נטען.
אם MyModule ייטען במפורש, אותו מופע יהיה זמין לכל מודולים אחרים הטוענים במפורש מעתה והלאה. (שימו לב שזה למעשה זהה ל-providedIn: 'root'.)
עם זאת, אם MyModule נטען באופן lazy loaded, אז מופע זה יינתן רק עבור מחלקות בתוך MyModule, בכל פעם שהוא נטען.
providedIn: MyComponent/מיושן מאז Angular v15
אנגולר תיצור מופע חדש ורענן של MyService בכל פעם ש-MyComponent יאובטח.
מופע MyService זה יינתן רק לצאצאים של אותו MyComponent מופע ספציפי, ויהרס ברגע שמופע הקומפוננטה ייהרס. (שימו לב שזה אומר שמופע MyService חדש ייווצר בכל פעם שהקומפוננטה הזו מוצגת.)
providedIn: null
MyService יכול להיות מופעל רק כאשר הוא נוסף למערך providers במודול או רכיב מסוים.
בכל פעם שהמודול/רכיב זה מאופסן, הוא ייצור מופע חדש של MyService ויספק אותו רק בתוך התחום הספציפי שלו. (ראו את התיאור המלא של מערך ה-providers למטה.)
ש: מה עושה מערך providers?
כל מזריק ניתן להגדיר עם מערך providers:
@NgModule({
providers: [MyService],
})
@Component({
providers: [MyService],
})
ניתן להוסיף את כל ההזרקות למערך providers, בלי קשר להגדרת providedIn שלהן.
הוספת MyService למערך providers תגרום למזרק ליצור ולספק מופע נפרד לחלוטין שלו למחלקות בהיקף שלו. (ההיקף זהה בדיוק כמו שתואר בדוגמאות providedIn: MyModule ו-providedIn: MyComponent למעלה.)
שיטה זו של מתן אינה תומכת ב-tree-shaking. השירות תמיד ייכלל בקומפילציה, גם אם אף אחד לא משתמש בו. (ראה את ההערות על tree-shaking למטה.)
ש: למה אשתמש במערך providers ובנוסף ב-providedIn בו זמנית?
מקרה שימוש לדוגמה עשוי להיות אם MyService הוא providedIn: 'root' וכבר יש לו מופע משותף, אבל אתה רוצה שהמודול/רכיב שלך יהיה לו מופע נפרד משלו.
הערות נוספות:
ש: איך הגדרות providedIn/providers משפיעות על tree-shaking?
Injectable הוגדר עם providedIn ינותק אם הוא לא מוזרק על ידי אף מחלקה (בטעינה מיידית או עצלה) בטווח המזריק שהוקצה לו.
עם זאת, תכשיר להזרקה שמוקצה למערך providers במודול/רכיב מסוים לעולם לא ייעלם מהעץ, גם אם הוא לא מוזרק בשום מקום.
כדי לגרום ל-tree-shaking להיות הכי יעיל, עליך לשאוף תמיד להשתמש ב-providedIn במקום במערך providers.
ש: למה אני אשתמש ב-providedIn: 'root' אם אני חושב ששימוש במערך ה-providers ב-AppModule נראה נקי יותר?
כפי שהוסבר לעיל, ההבדל המרכזי הוא שבין השיטות השונות, providedIn תומך ב-tree-shaking, ואילו מערך providers אינו תומך בכך.
מלבד זאת, זו החלטה ארכיטקטונית: אם נגדיר את providedIn ישירות בקובץ Injectable, ה-Injectable בעל ההחלטה כיצד הוא צריך להיות מסופק. הבחנה מי בעל החוזה יש לה השלכות משמעותיות על אפליקציות גדולות וצוותים שצריכים ליצור שיתוף פעולה בין מאות מודולים.
ש: האם יש הבדל בין הגדרת המערך providers: [MyService] ב-AppComponent או ב-AppModule?
כן. MyService יסופק במודולים שנטענים באיחור רק אם תעשה זאת ב-AppModule, ולא ב-AppComponent.
(זה בגלל שמודולים המוטענים רק בעת צורך מסתמכים על Router, שמיובא ב-AppModule, בשכבת מזרק אחת מעל AppComponent.)
תשובה #4
אם אתה משתמש ב- providedIn, ה-injectable נרשם כספק של המודול מבלי להוסיף אותו לספקי המודול.
מ‑Docs
השירות עצמו הוא מחלקה שה-CLI יצר ושמסומנת עם @Injectable. כברירת מחדל, הדקורטור הזה מוגדר עם מאפיין providedIn, שמייצר ספק עבור השירות. במקרה זה, providedIn: 'root' מציין שהשירות צריך להיות מסופק ב-root injector.
תשובה #5
ראה הסבר מצוין מאת @Nipuna,
הייתי רוצה להאריך זאת על ידי הוספת דוגמאות.
אם אתה משתמש רק במעטר Injectable בלי התכונה providedIn, כמו,
@Injectable()
אז יהיה עליך לכתוב את שם השירות במערך providers של המודול המתאים.
כמו זה;
data.service.ts ↴
import { Injectable } from '@angular/core';
@Injectable()
export class DataService {
constructor() {}
// Code . . .
}
app.module.ts ↴
import { AppComponent } from './app.component';
import { DataService } from './core/data.service';
@NgModule({
declarations: [AppComponent],
providers: [DataService], // ⟵ LOOK HERE WE PROVIDED IT
imports: [...],
bootstrap: [AppComponent],
})
export class AppModule {}
אבל, אם אתה משתמש ב-providedIn: 'root', כך:
data.service.ts ↴
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class DataService {
constructor() {}
// Code . . .
}
אז המודול שלנו ייראה כך:
app.module.ts ↴
import { AppComponent } from './app.component';
import { DataService } from './core/data.service';
@NgModule({
declarations: [AppComponent],
providers: [],
imports: [...],
bootstrap: [AppComponent],
})
export class AppModule {}
תראה, הפעם לא הוספתי את DataService במערך ה-providers, כי זה לא נחוץ.
נהלים טובים
זה יכול להיות שימושי, מדריכי Angular
ספק שירות עם מזרק השורש של האפליקציה במקשט @Injectable של השירות.
למה? המזרק של אנגולר הוא היררכי.
למה? כאשר אתה מספק את השירות למזרק השורש, אותה דוגמת שירות משותפת וזמינה בכל מחלקה שצריכה את השירות. זה אידיאלי כאשר שירות חולק שיטות או מצב.
למה? כאשר אתה רושם שירות בדקורטור @Injectable של השירות, כלי אופטימיזציה כגון אלו בהם משתמשים בבניות הפרודקשן של Angular CLI יכולים לבצע tree shaking ולהסיר שירותים שלא נמצאים בשימוש על ידי היישום שלך.
למה? זה לא אידיאלי כאשר שני רכיבים שונים צריכים מופעים שונים של שירות. בתרחיש כזה עדיף לספק את השירות ברמת הרכיב שצריך את המופע החדש והנפרד.
תשובה #6
providedIn אומר ל-Angular שהמזרק השורש אחראי על יצירת מופע של השירות שלך. שירותים הניתנים בדרך זו נגישים אוטומטית לכל היישום ואין צורך לרשום אותם באף מודול.
שירותי מחלקה יכולים לפעול כספקים עצמם, ולכן הגדרתן במעטר @Injectable היא כל הרשומה שאתה צריך.
