لقد مر الوقت الكافي منذ إصدار Angular المحدث. حاليا ، تم الانتهاء من العديد من المشاريع. منذ "البدء" ، انتقل العديد من المطورين بالفعل إلى الاستخدام الهادف لهذا الإطار ، وقدراته ، وتعلموا كيفية تجاوز المخاطر. قام كل مطور و / أو فريق إما بتشكيل أدلة أسلوبهم وأفضل الممارسات الخاصة بهم ، أو يستخدمون الآخرين. ولكن في نفس الوقت ، غالبًا ما يتعين عليك التعامل مع الكثير من الكود الزاوي ، والذي لا يستخدم العديد من ميزات هذا الإطار و / أو مكتوب بأسلوب AngularJS.
تقدم هذه المقالة بعض ميزات وميزات استخدام الإطار الزاوي ، والتي ، وفقًا لرأي المتواضع للمؤلف ، لا يتم تغطيتها بشكل كاف في الأدلة أو لا يتم استخدامها من قبل المطورين. تتناول المقالة استخدام طلبات HTTP "المعترضين" ، واستخدام حراس الطريق لتقييد الوصول إلى المستخدمين. يتم إعطاء بعض التوصيات لاستخدام RxJS وإدارة حالة التطبيق. كما يتم تقديم بعض التوصيات حول تصميم رمز المشروع ، والتي ستجعل رمز المشروع أكثر نظافة وأكثر قابلية للفهم. يأمل المؤلف أن تكون هذه المقالة مفيدة ليس فقط للمطورين الذين بدأوا للتو في التعرف على Angular ، ولكن أيضًا للمطورين ذوي الخبرة.
العمل مع HTTP
يتم إنشاء أي تطبيق ويب للعميل حول طلبات HTTP إلى الخادم. يناقش هذا الجزء بعض ميزات الإطار الزاوي للعمل مع طلبات HTTP.
استخدام اعتراضات
في بعض الحالات ، قد يكون من الضروري تعديل الطلب قبل وصوله إلى الخادم. أو تحتاج إلى تغيير كل إجابة. بدءًا من Angular 4.3 ، تم إصدار HttpClient جديد. أضافت القدرة على اعتراض طلب باستخدام اعتراضات (نعم ، تم إرجاعها أخيرًا فقط في الإصدار 4.3 !، وكانت هذه واحدة من أكثر الميزات المفقودة المتوقعة لـ AngularJs التي لم يتم ترحيلها إلى Angular). هذا هو نوع من البرامج الوسيطة بين http-api والطلب الفعلي.
قد تكون المصادقة إحدى حالات الاستخدام الشائعة. للحصول على رد من الخادم ، غالبًا ما تحتاج إلى إضافة نوع من آلية المصادقة إلى الطلب. يتم حل هذه المهمة باستخدام اعتراضات بكل بساطة:
import { Injectable } from "@angular/core"; import { Observable } from "rxjs/Observable"; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from @angular/common/http"; @Injectable() export class JWTInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { req = req.clone({ setHeaders: { authorization: localStorage.getItem("token") } }); return next.handle(req); } }
نظرًا لأن التطبيق يمكن أن يحتوي على اعتراضات متعددة ، يتم تنظيمها في سلسلة. العنصر الأول يسمى الإطار الزاوي نفسه. بعد ذلك ، نحن مسؤولون عن إرسال الطلب إلى المعترض التالي. للقيام بذلك ، نسمي طريقة معالجة العنصر التالي في السلسلة بمجرد الانتهاء. نحن نربط المعترض:
import { BrowserModule } from "@angular/platform-browser"; import { NgModule } from "@angular/core"; import { AppComponent } from "./app.component"; import { HttpClientModule } from "@angular/common/http"; import { HTTP_INTERCEPTORS } from "@angular/common/http"; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, HttpClientModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule {}
كما ترون ، فإن اتصال وتنفيذ اعتراضات بسيطة للغاية.
تتبع التقدم
إحدى ميزات HttpClient
هي القدرة على تتبع تقدم الطلب. على سبيل المثال ، إذا كنت بحاجة إلى تنزيل ملف كبير ، فربما تريد إبلاغ المستخدم عن عملية التنزيل. للحصول على التقدم ، يجب عليك تعيين خاصية reportProgress
للكائن HttpRequest
إلى true
. مثال على الخدمة التي تنفذ هذا النهج:
import { Observable } from "rxjs/Observable"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { HttpRequest } from "@angular/common/http"; import { Subject } from "rxjs/Subject"; import { HttpEventType } from "@angular/common/http"; import { HttpResponse } from "@angular/common/http"; @Injectable() export class FileUploadService { constructor(private http: HttpClient) {} public post(url: string, file: File): Observable<number> { var subject = new Subject<number>(); const req = new HttpRequest("POST", url, file, { reportProgress: true }); this.httpClient.request(req).subscribe(event => { if (event.type === HttpEventType.UploadProgress) { const percent = Math.round((100 * event.loaded) / event.total); subject.next(percent); } else if (event instanceof HttpResponse) { subject.complete(); } }); return subject.asObservable(); } }
ترجع طريقة النشر Observable
أن تمثل تقدم التنزيل. كل ما هو مطلوب الآن هو عرض تقدم التحميل في المكون.
التوجيه باستخدام حارس الطريق
يتيح لك التوجيه تعيين طلبات التطبيق إلى موارد محددة داخل التطبيق. في كثير من الأحيان ، من الضروري حل مشكلة تحديد رؤية المسار الذي توجد فيه مكونات معينة ، اعتمادًا على بعض الشروط. في هذه الحالات ، لدى Angular آلية تقييد انتقالية. كمثال ، هناك خدمة ستنفذ حارس الطريق. لنفترض في تطبيق مصادقة مستخدم تطبيق باستخدام JWT. نسخة مبسطة من الخدمة تتحقق مما إذا كان المستخدم مصرحًا به يمكن تمثيلها على أنها:
@Injectable() export class AuthService { constructor(public jwtHelper: JwtHelperService) {} public isAuthenticated(): boolean { const token = localStorage.getItem("token");
لتطبيق حارس المسار ، يجب عليك تنفيذ واجهة CanActivate
، والتي تتكون من وظيفة canActivate
واحدة.
@Injectable() export class AuthGuardService implements CanActivate { constructor(public auth: AuthService, public router: Router) {} canActivate(): boolean { if (!this.auth.isAuthenticated()) { this.router.navigate(["login"]); return false; } return true; } }
يستخدم تطبيق AuthGuardService
خدمة AuthGuardService
الموضحة أعلاه للتحقق من تفويض المستخدم. تُرجع طريقة canActivate
قيمة منطقية يمكن استخدامها في حالة تنشيط المسار.
الآن يمكننا تطبيق حارس الطريق الذي تم إنشاؤه على أي طريق أو مسار. للقيام بذلك ، عند الإعلان عن Routes
نحدد خدمتنا ، التي ترث واجهة CanActivate
، في قسم canActivate
:
export const ROUTES: Routes = [ { path: "", component: HomeComponent }, { path: "profile", component: UserComponent, canActivate: [AuthGuardService] }, { path: "**", redirectTo: "" } ];
في هذه الحالة ، يحتوي مسار /profile
على قيمة التكوين الاختيارية canActivate
. يتم تمرير AuthGuard
الموضحة سابقًا كوسيطة لهذه الخاصية canActivate
. بعد ذلك ، سيتم استدعاء طريقة canActivate
كل مرة يحاول فيها شخص الوصول إلى مسار /profile
. إذا تم التصريح للمستخدم ، فسيتمكن من الوصول إلى مسار /profile
، وإلا فسيتم إعادة توجيهه إلى مسار /login
.
يجب أن تدرك أن canActivate
لا يزال يسمح لك بتنشيط المكون على هذا المسار ، ولكنه لا يسمح لك بالتبديل إليه. إذا كنت بحاجة إلى حماية تنشيط وتحميل المكون ، فيمكننا في هذه الحالة استخدام canLoad
. يمكن تنفيذ CanLoad
عن طريق القياس.
طبخ RxJS
تم بناء Angular على رأس RxJS. RxJS هي مكتبة للعمل مع تدفقات البيانات غير المتزامنة والقائمة على الأحداث باستخدام تسلسلات يمكن ملاحظتها. RxJS هو تطبيق JavaScript لواجهة برمجة تطبيقات ReactiveX. بالنسبة للجزء الأكبر ، ترتبط الأخطاء التي تحدث عند العمل مع هذه المكتبة بمعرفة سطحية بأساسيات تنفيذها.
استخدام غير متزامن بدلاً من الاشتراك في الأحداث
يستخدم عدد كبير من المطورين الذين لم يأتوا إلا مؤخرًا لاستخدام Angular Framework وظيفة subscribe
في Observable
لتلقي البيانات وحفظها في المكون:
@Component({ selector: "my-component", template: ` <span>{{localData.name}} : {{localData.value}}</span>` }) export class MyComponent { localData; constructor(http: HttpClient) { http.get("api/data").subscribe(data => { this.localData = data; }); } }
بدلاً من ذلك ، يمكننا الاشتراك من خلال القالب باستخدام أنبوب غير متزامن:
@Component({ selector: "my-component", template: ` <p>{{data.name | async}} : {{data.value | async}}</p>` }) export class MyComponent { data; constructor(http: HttpClient) { this.data = http.get("api/data"); } }
من خلال الاشتراك من خلال قالب ، نتجنب حدوث تسرب للذاكرة لأن Angular تلغي الاشتراك تلقائيًا من Observable
عندما ينكسر أحد المكونات. في هذه الحالة ، بالنسبة لطلبات HTTP ، لا يوفر استخدام الأنابيب المتزامنة أي مزايا عمليًا ، باستثناء شيء واحد - سيؤدي إلغاء التزامن إلى إلغاء الطلب إذا لم تعد البيانات مطلوبة ، ولن يكمل معالجة الطلب.
لا Observables
استخدام العديد من ميزات Observables
عند الاشتراك يدويًا. يمكن توسيع السلوك Observables
من خلال التكرار (على سبيل المثال ، إعادة المحاولة في طلب http) أو التحديث المستند إلى المؤقت أو التخزين المؤقت مسبقًا.
استخدم $
للدلالة على الملاحظات
تتعلق الفقرة التالية بتصميم رموز مصدر التطبيق ويتبع من الفقرة السابقة. لتمييز Observable
عن المتغيرات البسيطة ، غالبًا ما يمكنك سماع النصيحة لاستخدام علامة " $
" في اسم المتغير أو الحقل. ستزيل هذه الحيلة البسيطة الارتباك في المتغيرات عند استخدام المتزامن.
import { Component } from "@angular/core"; import { Observable } from "rxjs/Rx"; import { UserClient } from "../services/user.client"; import { User } from "../services/user"; @Component({ selector: "user-list", template: ` <ul class="user_list" *ngIf="(users$ | async).length"> <li class="user" *ngFor="let user of users$ | async"> {{ user.name }} - {{ user.birth_date }} </li> </ul>` }) export class UserList { public users$: Observable<User[]>; constructor(public userClient: UserClient) {} public ngOnInit() { this.users$ = this.client.getUsers(); } }
متى تلغي الاشتراك (إلغاء الاشتراك)
السؤال الأكثر شيوعًا الذي يواجهه المطور عند التعرف على Angular لفترة وجيزة هو متى لا تزال بحاجة إلى إلغاء الاشتراك ، ومتى لا تفعل ذلك. للإجابة على هذا السؤال ، تحتاج أولاً إلى تحديد نوع Observable
التي يتم استخدامها حاليًا. في Angular هناك نوعان من Observable
- محدود Observable
، ينتج بعضها محددًا ، والبعض الآخر ، على التوالي ، عددًا غير محدود من القيم.
Observable
Http
Observable
مضغوط ، والمستمعون / المستمعون لأحداث DOM لا حصر لهم.
إذا تم الاشتراك في قيم Observable
لانهائية يدويًا (بدون استخدام أنبوب غير متزامن) ، فيجب إجراء رد بدون فشل. إذا قمنا بالاشتراك يدويًا في برنامج محدود قابل للملاحظة ، فليس من الضروري إلغاء الاشتراك ، وسوف تتولى RxJS ذلك. في حالة Observables
المدمجة Observables
يمكننا إلغاء الاشتراك إذا كان لـ Observable
وقت تنفيذ أطول من اللازم ، على سبيل المثال ، طلب HTTP متعدد.
مثال Observables
المدمجة:
export class SomeComponent { constructor(private http: HttpClient) { } ngOnInit() { Observable.timer(1000).subscribe(...); this.http.get("http://api.com").subscribe(...); } }
مثال للملاحظات اللانهائية
export class SomeComponent { constructor(private element : ElementRef) { } interval: Subscription; click: Subscription; ngOnInit() { this.interval = Observable.interval(1000).subscribe(...); this.click = Observable.fromEvent(this.element.nativeElement, "click").subscribe(...); } ngOnDestroy() { this.interval.unsubscribe(); this.click.unsubscribe(); } }
أدناه ، بمزيد من التفصيل هي الحالات التي تحتاج فيها إلى إلغاء الاشتراك
- من الضروري إلغاء الاشتراك من النموذج ومن عناصر التحكم الفردية التي اشتركت فيها:
export class SomeComponent { ngOnInit() { this.form = new FormGroup({...}); this.valueChangesSubs = this.form.valueChanges.subscribe(...); this.statusChangesSubs = this.form.statusChanges.subscribe(...); } ngOnDestroy() { this.valueChangesSubs.unsubscribe(); this.statusChangesSubs.unsubscribe(); } }
- جهاز التوجيه وفقًا للوثائق ، يجب على Angular إلغاء الاشتراك بنفسها ، ولكن هذا لا يحدث . لذلك ، لتجنب المزيد من المشاكل ، نكتب بأنفسنا:
export class SomeComponent { constructor(private route: ActivatedRoute, private router: Router) { } ngOnInit() { this.route.params.subscribe(..); this.route.queryParams.subscribe(...); this.route.fragment.subscribe(...); this.route.data.subscribe(...); this.route.url.subscribe(..); this.router.events.subscribe(...); } ngOnDestroy() {
- تسلسل لا نهاية لها. الأمثلة هي تسلسلات تم إنشاؤها باستخدام
interva()
أو مستمعي الأحداث (fromEvent())
:
export class SomeComponent { constructor(private element : ElementRef) { } interval: Subscription; click: Subscription; ngOnInit() { this.intervalSubs = Observable.interval(1000).subscribe(...); this.clickSubs = Observable.fromEvent(this.element.nativeElement, "click").subscribe(...); } ngOnDestroy() { this.intervalSubs.unsubscribe(); this.clickSubs.unsubscribe(); } }
TakeUntil و TakeWhile
لتبسيط العمل مع Observables اللانهائية في RxJS ، هناك وظيفتان takeUntil
- takeUntil
و takeWhile
. إنهم يؤدون نفس الإجراء - إلغاء الاشتراك من Observable
في نهاية بعض الشروط ، والفرق هو فقط في القيم المقبولة. يقبل takeWhile
قيمة boolean
، takeUntil
Subject
.
takeWhile
سبيل المثال:
export class SomeComponent implements OnDestroy, OnInit { public user: User; private alive: boolean = true; public ngOnInit() { this.userService .authenticate(email, password) .takeWhile(() => this.alive) .subscribe(user => { this.user = user; }); } public ngOnDestroy() { this.alive = false; } }
في هذه الحالة ، عندما يتم تغيير العلم alive
، سيتم إلغاء الاشتراك في Observable
. في هذا المثال ، قم بإلغاء الاشتراك عندما يتم إتلاف المكون.
takeUntil
سبيل المثال:
export class SomeComponent implements OnDestroy, OnInit { public user: User; private unsubscribe: Subject<void> = new Subject(void); public ngOnInit() { this.userService.authenticate(email, password) .takeUntil(this.unsubscribe) .subscribe(user => { this.user = user; }); } public ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); } }
في هذه الحالة ، لإلغاء الاشتراك من Observable
نبلغ أن subject
يأخذ القيمة التالية ويكملها.
سيؤدي استخدام هذه الوظائف إلى تجنب التسريبات وتبسيط العمل مع إلغاء الاشتراك من البيانات. ما الوظيفة التي يجب استخدامها؟ يجب أن تسترشد الإجابة على هذا السؤال بالتفضيلات الشخصية والمتطلبات الحالية.
إدارة الدولة في التطبيقات الزاوية @ ngrx / store
في كثير من الأحيان ، عند تطوير التطبيقات المعقدة ، نواجه الحاجة إلى تخزين الحالة والاستجابة لتغيراتها. هناك العديد من المكتبات للتطبيقات المطورة في إطار ReactJs والتي تسمح لك بالتحكم في حالة التطبيق والرد على تغييراته - Flux ، Redux ، Redux-saga ، إلخ. للتطبيقات الزاويّة ، هناك حاوية حالة قائمة على RxJS مستوحاة من Redux - @ ngrx / store. الإدارة السليمة لحالة التطبيق ستوفر المطور من العديد من المشاكل مع التوسع الإضافي للتطبيق.
لماذا Redux
يضع Redux نفسه كحاوية حالة يمكن التنبؤ بها لتطبيقات JavaScript. Redux مستوحى من Flux و Elm.
يقترح Redux التفكير في التطبيق كحالة أولية يمكن تعديلها من خلال سلسلة من الإجراءات ، والتي يمكن أن تكون طريقة جيدة لبناء تطبيقات ويب معقدة.
لا يرتبط Redux بأي إطار عمل محدد ، وعلى الرغم من أنه تم تطويره من أجل React ، فإنه يمكن استخدامه مع Angular أو jQuery.
الفرضيات الرئيسية لـ Redux:
- متجر واحد لحالة التطبيق بالكامل
- للقراءة فقط
- يتم إجراء التغييرات بواسطة الدالات "الخالصة" ، والتي تخضع للمتطلبات التالية:
- يجب ألا تجري مكالمات خارجية عبر شبكة أو قاعدة بيانات ؛
- إرجاع قيمة تعتمد فقط على المعلمات التي تم تمريرها ؛
- الحجج غير قابلة للتغيير ، أي لا يجب أن تغير الوظائف ؛
- استدعاء دالة نقية بنفس الوسيطات يعيد دائمًا نفس النتيجة ؛
مثال على وظيفة إدارة الدولة:
// counter.ts import { ActionReducer, Action } from "@ngrx/store"; export const INCREMENT = "INCREMENT"; export const DECREMENT = "DECREMENT"; export const RESET = "RESET"; export function counterReducer(state: number = 0, action: Action) { switch (action.type) { case INCREMENT: return state + 1; case DECREMENT: return state - 1; case RESET: return 0; default: return state; } }
يتم استيراد المخفض في الوحدة الرئيسية للتطبيق واستخدام StoreModule.provideStore(reducers)
الزاوي:
// app.module.ts import { NgModule } from "@angular/core"; import { StoreModule } from "@ngrx/store"; import { counterReducer } from "./counter"; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore({ counter: counterReducer }) ] }) export class AppModule { }
بعد ذلك ، يتم تقديم خدمة Store
في المكونات والخدمات الضرورية. يتم استخدام الوظيفة store.select () لتحديد حالة "شريحة":
// app.component.ts ... interface AppState { counter: number; } @Component({ selector: "my-app", template: ` <button (click)="increment()">Increment</button> <div>Current Count: {{ counter | async }}</div> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset Counter</button>` }) class AppComponent { counter: Observable<number>; constructor(private store: Store<AppState>) { this.counter = store.select("counter"); } increment() { this.store.dispatch({ type: INCREMENT }); } decrement() { this.store.dispatch({ type: DECREMENT }); } reset() { this.store.dispatch({ type: RESET }); } }
@ ngrx / store-router
في بعض الحالات ، من الملائم ربط حالة التطبيق بالطريق الحالي للتطبيق. بالنسبة لهذه الحالات ، توجد الوحدة النمطية @ ngrx / router-store. لكي يستخدم التطبيق router-store
لحفظ الحالة ، ما routerReducer
سوى توصيل routerReducer
وإضافة مكالمة إلى RouterStoreModule.connectRoute
في الوحدة الرئيسية للتطبيق:
import { StoreModule } from "@ngrx/store"; import { routerReducer, RouterStoreModule } from "@ngrx/router-store"; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore({ router: routerReducer }), RouterStoreModule.connectRouter() ], bootstrap: [AppComponent] }) export class AppModule { }
الآن أضف RouterState
إلى الحالة الرئيسية للتطبيق:
import { RouterState } from "@ngrx/router-store"; export interface AppState { ... router: RouterState; };
بالإضافة إلى ذلك ، يمكننا الإشارة إلى الحالة الأولية للتطبيق عند الإعلان عن المتجر:
StoreModule.provideStore( { router: routerReducer }, { router: { path: window.location.pathname + window.location.search } } );
الإجراءات المدعومة:
import { go, replace, search, show, back, forward } from "@ngrx/router-store"; // store.dispatch(go(["/path", { routeParam: 1 }], { query: "string" })); // store.dispatch(replace(["/path"], { query: "string" })); // store.dispatch(show(["/path"], { query: "string" })); // store.dispatch(search({ query: "string" })); // store.dispatch(back()); // store.dispatch(forward());
UPD: اقترح التعليق أن هذه الإجراءات لن تكون متاحة في الإصدار الجديدngrx ، للنسخة الجديدة https://github.com/ngrx/platform/blob/master/MIGRATION.md#ngrxrouter-store
سيزيل استخدام حاوية الحالة العديد من المشكلات عند تطوير تطبيقات معقدة. ومع ذلك ، من المهم جعل إدارة الدولة بسيطة قدر الإمكان. في كثير من الأحيان ، يجب على المرء أن يتعامل مع التطبيقات التي يوجد فيها تداخل مفرط للدول ، مما يعقد فقط فهم التطبيق.
منظمة كود
التخلص من التعبيرات الضخمة في import
يدرك العديد من المطورين حالة تكون فيها التعبيرات في import
معقدة إلى حد ما. هذا ملحوظ بشكل خاص في التطبيقات الكبيرة حيث يوجد العديد من المكتبات القابلة لإعادة الاستخدام.
import { SomeService } from "../../../core/subpackage1/subpackage2/some.service";
ما هو السيء في هذا الرمز؟ إذا كنت بحاجة إلى نقل المكون الخاص بنا إلى دليل آخر ، فلن تكون التعبيرات في import
صالحة.
في هذه الحالة ، سيسمح لنا استخدام الأسماء المستعارة بالابتعاد عن التعبيرات الضخمة في import
وجعل الكود الخاص بنا أكثر نظافة. من أجل إعداد المشروع لاستخدام الأسماء المستعارة ، تحتاج إلى إضافة خصائص tsconfig.json
والمسار في tsconfig.json
:
/ tsconfig.json { "compilerOptions": { ... "baseUrl": "src", "paths": { "@app/*": ["app/*"], "@env/*": ["environments/*"] } } }
مع هذه التغييرات ، من السهل إدارة المكونات الإضافية:
import { Component, OnInit } from "@angular/core"; import { Observable } from "rxjs/Observable"; import { SomeService } from "@app/core"; import { environment } from "@env/environment"; import { LocalService } from "./local.service"; @Component({ }) export class ExampleComponent implements OnInit { constructor( private someService: SomeService, private localService: LocalService ) { } }
في هذا المثال ، SomeService
استيراد SomeService
مباشرةً من @app/core
بدلاً من تعبير ضخم (مثل @app/core/some-package/some.service
). هذا ممكن بفضل إعادة تصدير المكونات العامة في ملف index.ts
الرئيسي. من المستحسن إنشاء ملف index.ts
لكل حزمة تحتاج فيها إلى إعادة تصدير جميع الوحدات العامة:
// index.ts export * from "./core.module"; export * from "./auth/auth.service"; export * from "./user/user.service"; export * from "./some-service/some.service";
الوحدات الأساسية والمشتركة والميزات
من أجل إدارة أكثر مرونة لمكونات التطبيق ، يوصى في كثير من الأحيان في الأدبيات وموارد الإنترنت المختلفة لنشر رؤية مكوناتها. في هذه الحالة ، يتم تبسيط إدارة مكونات التطبيق. يتم استخدام الفصل التالي بشكل شائع: الوحدات الأساسية والمشتركة والميزات.
Coremodule
الغرض الأساسي من CoreModule هو وصف الخدمات التي سيكون لها مثيل واحد للتطبيق بأكمله (أي تنفيذ نمط المفرد). غالبًا ما تتضمن هذه الخدمة خدمة تفويض أو خدمة للحصول على معلومات المستخدم. مثال CoreModule:
import { NgModule, Optional, SkipSelf } from "@angular/core"; import { CommonModule } from "@angular/common"; import { HttpClientModule } from "@angular/common/http"; import { SomeSingletonService } from "./some-singleton/some-singleton.service"; @NgModule({ imports: [CommonModule, HttpClientModule], declarations: [], providers: [SomeSingletonService] }) export class CoreModule { constructor( @Optional() @SkipSelf() parentModule: CoreModule ) { if (parentModule) { throw new Error("CoreModule is already loaded. Import only in AppModule"); } } }
Sharedmodule
تصف هذه الوحدة مكونات بسيطة. لا تقوم هذه المكونات باستيراد أو حقن تبعيات من وحدات أخرى في منشئيها. يجب أن يتلقوا جميع البيانات من خلال السمات الموجودة في قالب المكون. لا تعتمد SharedModule
على باقي تطبيقاتنا ، كما أنها مكان مثالي لاستيراد وإعادة تصدير مكونات Angular Material أو مكتبات واجهة المستخدم الأخرى.
import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; import { FormsModule } from "@angular/forms"; import { MdButtonModule } from "@angular/material"; import { SomeCustomComponent } from "./some-custom/some-custom.component"; @NgModule({ imports: [CommonModule, FormsModule, MdButtonModule], declarations: [SomeCustomComponent], exports: [ CommonModule, FormsModule, MdButtonModule, SomeCustomComponent ] }) export class SharedModule { }
المميزات
هنا يمكنك تكرار دليل النمط الزاوي. يتم إنشاء FeatureModule منفصل لكل وظيفة تطبيق مستقلة. يجب أن يقوم FeatureModule باستيراد الخدمات فقط من CoreModule
. إذا كانت هناك حاجة إلى وحدة نمطية لاستيراد خدمة من وحدة نمطية أخرى ، فمن الممكن أن يتم نقل هذه الخدمة إلى CoreModule
.
في بعض الحالات ، هناك حاجة لاستخدام الخدمة فقط من قبل بعض الوحدات وليس هناك حاجة لتصديرها إلى CoreModule
. في هذه الحالة ، يمكنك إنشاء SharedModule
خاص ، والذي سيتم استخدامه فقط في هذه الوحدات.
, — , - , , CoreModule
, SharedModule
.
, . , . , , .
المراجع
- https://github.com/ngrx/store
- http://stepansuvorov.com/blog/2017/06/angular-rxjs-unsubscribe-or-not-unsubscribe/
- https://medium.com/@tomastrajan/6-best-practices-pro-tips-for-angular-cli-better-developer-experience-7b328bc9db81
- https://habr.com/post/336280/
- https://angular.io/docs