من به یک مدیر دولتی در انگولار نیاز ندارم یا فقط ورود او را به تاخیر می اندازم؟

هنگامی که یک Angular App میسازیم، ارتباط بین مؤلفهها چیزی است که باید مراقب آن باشیم. می توانیم با رویدادهای ورودی و خروجی استفاده از والدین به فرزند را شروع کنیم. فقدان ارتباط ورودی و خروجی تنها ارتباط والدین به فرزند یا برعکس است، اما زمانی که برنامه با مسیریابی و بیشتر از اجزای والدین و فرزندان شروع به رشد می کند. از این رو، گام بعدی استفاده از خدمات Angular با است Rxjs
.
سرویسهای Rxjs در برنامههای کوچک با حالت کم به خوبی کار میکنند. تزریق یک سرویس با a BehaviorSubject
برای همگام نگه داشتن آن، آن را مشترک کنید، اما اگر برنامه شما دارای نهادها، تنظیمات و پیکربندیهای کاربر زیادی است و این تغییرات باید در چندین مؤلفه منعکس یا واکنش نشان دهند، فقط استفاده از سرویسها کمی سخت میشود که نگهداری شود یا شاید نباشد.
ما باید از مسئولیت یک جزء یا زمان بازسازی آن آگاه باشیم. ایجاد یک مؤلفه جدید برای یک محدوده خاص و مراقبت از زمانی که یک مؤلفه دارای خدمات زیادی تزریق می شود یک پرچم قرمز است. بیایید یک مثال کوچک نشان دهیم.
سناریو
ما باید یک برنامه با بخش های زیر خانه، نمایه، سفارشات و پرداخت بسازیم.
-
هر بخش باید اجازه ذخیره داده ها در حالت را بدهد.
-
خانه باید داده های هر بخش را ارائه دهد.
-
هر سفارش تخفیف از موجودی.
ما از Rxjs Behavior Subject برای حفظ حالت و برخی از عملگرهای rxjs برای ساده سازی و ترکیب برخی از عملگرها استفاده خواهیم کرد.
راه اندازی پروژه
ابتدا با استفاده از Angular CLI، پروژه را ایجاد کنید:
> ng new angular-and-states
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
پس از اتمام، از ترمینال به دایرکتوری جدید بروید و دو صفحه ایجاد کنید. home
و settings
با استفاده از Angular/CLI و اجرای دستور ng g c
و نام جزء
PS C:\Users\dany.paredes\Desktop\angular-and-states> ng g c /pages/payments
CREATE src/app/pages/payments/payments.component.html (23 bytes)
CREATE src/app/pages/payments/payments.component.spec.ts (640 bytes)
CREATE src/app/pages/payments/payments.component.ts (283 bytes)
CREATE src/app/pages/payments/payments.component.css (0 bytes)
UPDATE src/app/app.module.ts (774 bytes)
PS C:\Users\dany.paredes\Desktop\angular-and-states> ng g c /pages/orders
CREATE src/app/pages/orders/orders.component.html (21 bytes)
CREATE src/app/pages/orders/orders.component.spec.ts (626 bytes)
CREATE src/app/pages/orders/orders.component.ts (275 bytes)
CREATE src/app/pages/orders/orders.component.css (0 bytes)
UPDATE src/app/app.module.ts (862 bytes)
PS C:\Users\dany.paredes\Desktop\angular-and-states> ng g c /pages/profile
CREATE src/app/pages/profile/profile.component.html (22 bytes)
CREATE src/app/pages/profile/profile.component.spec.ts (633 bytes)
CREATE src/app/pages/profile/profile.component.ts (279 bytes)
CREATE src/app/pages/profile/profile.component.css (0 bytes)
UPDATE src/app/app.module.ts (954 bytes)
برای پیمایش، ناوبری جزء را با استفاده از همان دستور اما در مسیر ایجاد کنید components
:
ng g c /components/navigation
نشانه گذاری navigation.component.html را با استفاده از دستورالعمل ها برای پیمایش به روز کنید:
<a [routerLink]="['']">Home</a>
<a [routerLink]="['payments']">Payments</a>
<a [routerLink]="['orders']">Orders</a>
<a [routerLink]="['profile']">Profile</a>
سپس علامت گذاری پیش فرض را در قسمت حذف کنید app.component.html
و علامت گذاری زیر را با اضافه کنید <router-outlet></router-outlet>
<h1>App</h1>
<app-navigation></app-navigation>
<router-outlet></router-outlet>
اضافه کردن مسیرها در app-routing.module.ts
const routes: Routes = [
{
component: HomeComponent,
path: ''
},
{
component: OrdersComponent,
path: 'orders'
},
{
component: PaymentsComponent,
path: 'payments'
},
{
component: ProfileComponent,
path: 'profile'
}
];
دستور را ذخیره و اجرا کنید ng s -o
، و برنامه باید مؤلفه خانه را با پیمایش ارائه دهد.
افزودن ایالت با خدمات
ما باید وضعیت را برای هر بخش ذخیره کنیم، و برای هر یک از “پروفایل”، “سفارش ها” و “پرداخت ها” یک سرویس ایجاد کنیم.
با استفاده از angular/cli
اجرا کن ng g s /services/profile
برای به اشتراک گذاری داده های مربوط به نمایه بین مؤلفه ها با استفاده از BehaviorSubject
.
درباره BehaviorSubject بیشتر بخوانید
دو ویژگی ایجاد کنید: –nameSubject$
ss یک نمونه از BehaviorSubject
، و با مقدار null مقداردهی اولیه می شود.
- این
name$
اموال عمومی که فاش می کندnameSubject
به عنوان یک قابل مشاهده
@Injectable({
providedIn: 'root'
})
export class ProfileService {
private nameSubject = new BehaviorSubject<string | null>(null);
public name$ = this.nameSubject.asObservable()
public saveName(name: string) {
const message = `Hi ${name} `
this.nameSubject.next(message);
}
}
فرآیند را برای PaymentService
، که موجودی حساب را نگه می دارد.
ng g s /services/payments
CREATE src/app/services/payments.service.spec.ts (367 bytes)
CREATE src/app/services/payments.service.ts (137 bytes)
ایجاد دو ویژگی، paymentSubject$
شروع با تعادل در 0
و paymentBalance$
و دو روش اضافه کنید:
import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class PaymentsService {
private paymentSubject$ = new BehaviorSubject<number | null>(null);
public paymentBalance$ = this.paymentSubject.asObservable()
public updateBalance(balance: number) {
const currentBalance = this.paymentSubject.getValue();
if (currentBalance) {
const totalBalance = currentBalance - balance;
this.paymentSubject$.next(totalBalance);
}
}
public addBalance(balance: number) {
this.paymentSubject$.next(balance);
}
}
در نهایت، ایجاد کنید OrdersServices
برای انجام کلیه سفارشات
ng g s /services/orders
CREATE src/app/services/orders.service.spec.ts (357 bytes)
CREATE src/app/services/orders.service.ts (135 bytes)
این سرویس حاوی آرایه ای از orderIds است.
import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs/internal/BehaviorSubject';
@Injectable({
providedIn: 'root'
})
export class OrdersService {
private orderSubject = new BehaviorSubject<number[]>([]);
public orders$ = this.orderSubject.asObservable()
public addOrder(orderId: number) {
const orders = [...this.orderSubject.value, orderId]
this.orderSubject.next(orders);
}
}
ما در حال حاضر ایالات خدمات خود را داریم. در مرحله بعد، از این خدمات در کامپوننت ها استفاده خواهیم کرد.
استفاده از وضعیت خدمات در کامپوننت ها
ما خدماتی برای ذخیره داده ها داریم. قدم بعدی ما استفاده از آن در هر صفحه است. فرآیند تزریق سرویس به کامپوننت و استفاده از روش و موضوع برای دریافت ارزش از سرویس است.
این ProfileComponent
، امکان ذخیره نام او، ذخیره آن در قابل مشاهده و ارائه آن را فراهم می کند saveName
روش از سرویس
ابتدا سرویس را تزریق کنید ProfileService
، و یک متغیر جدید اضافه کنید name$
برای پیوند با قابل مشاهده name$
از سرویس
بعد، یک روش جدید اضافه کنید، save
، با نام پارامتر، در بدنه متد، برای استفاده از saveName
از سرویس
کد نهایی به شکل زیر است:
import { ProfileService } from './../../services/profile.service';
import { Component, inject, OnInit } from '@angular/core';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css'],
})
export class ProfileComponent {
name$ = this.profileService.name$;
constructor(private profileService: ProfileService) {}
save(name: string) {
this.profileService.saveName(name);
}
}
در نشانه گذاری HTML مؤلفه، یک ورودی جدید با متغیر الگو #name اضافه کنید تا به مقدار ورودی دسترسی پیدا کنید، و یک دکمه برای فراخوانی اضافه کنید. save
روش، عبور از متغیر name.value
به روش اشاره دارد.
برای ارائه نام، از لوله استفاده کنید async
اشتراک در name$
قابل مشاهده برای نشان دادن nameSaved
در خدمت
کد نهایی به شکل زیر است:
<h3>Please type your Name:</h3>
<input #name type="text">
<button (click)="save(name.value)">Save</button>
<span *ngIf="(name$ | async ) as nameSaved">{{nameSaved}}</span>
ما قبلاً وضعیت را برای بخش نمایه تنظیم کردیم، تغییرات را ذخیره کردیم و برنامه را با آن اجرا کردیم ng s -o
. برنامه را به بخش نمایه باز می کند، نام شما را اضافه می کند و بین بخش های دیگر حرکت می کند و نام را ذخیره می کند.
برای پرداخت همین مراحل را تکرار کنید.
جزء پرداخت
import {PaymentsService} from './../../services/payments.service';
import {Component} from '@angular/core';
@Component({
selector: 'app-payments',
templateUrl: './payments.component.html',
styleUrls: ['./payments.component.css']
})
export class PaymentsComponent {
balance$ = this.paymentService.paymentBalance$;
constructor(private paymentService: PaymentsService) {
}
updateBalance(balance: HTMLInputElement) {
this.paymentService.addBalance(balance.valueAsNumber);
}
}
نشانه گذاری HTML را با ورودی و دکمه به روز کنید و در تعادل$ قابل مشاهده مشترک شوید.
<h2>Add balance:</h2>
<input #payment type="number">
<button (click)="updateBalance(payment)">Update</button>
<span *ngIf="(balance$ | async ) as balance">You current Balance is: {{balance$ | currency}}</span>
جزء سفارشات
فرآیند را با دستورات تکرار کنید اما با تغییرات جزئی، تنها تفاوت این است که سفارشات یک آرایه هستند و ما از ngFor
برای ارائه تمام بخش ها
import { Component, OnInit } from '@angular/core';
import { OrdersService } from 'src/app/services/orders.service';
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html',
styleUrls: ['./orders.component.css'],
})
export class OrdersComponent {
orders$ = this.ordersService.orders$;
constructor(private ordersService: OrdersService) {}
addOrder(order: HTMLInputElement) {
if (order.value) {
this.ordersService.addOrder(order.valueAsNumber);
order.value = '';
}
}
}
<h2>Add your order</h2>
<input #order type="number">
<button (click)="addOrder(order)">add order</button>
<div *ngIf="(orders$ | async ) as orders">
Your current active orders are:
<div *ngFor="let order of orders">
{{order}}
</div>
</div>
در نهایت، ما یک حالت در موجودیتهای خود داریم و چالش بعدی خواندن مقدار هر سرویس برای نمایش دادهها در مؤلفه خانه است.
ایالات را ترکیب کنید و همگام سازی کنید
ما در برنامه خود سه مقدار داریم:
-
مانده پرداخت
-
اطلاعات پروفایل
-
سفارشات
اولین چالش ما هنگام اضافه کردن یک سفارش جدید در OrderComponent
برای تخفیف موجودی در paymentService
.
در orders.component
، باید به نکات زیر اشاره کنیم:
-
اضافه کردن
paymentService
وordersService
در سازنده -
یک متغیر جدید برای ذخیره سازی اعلام کنید
currentBalance
. -
را ویرایش کنید
addOrder
برای به روز رسانی موجودی با2
هنگام ارسال سفارش
کد نهایی به شکل زیر است:
import {PaymentsService} from '../../services/payments.service';
import {Component} from '@angular/core';
import {OrdersService} from 'src/app/services/orders.service';
import {startWith} from 'rxjs/operators';
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html',
styleUrls: ['./orders.component.css'],
})
export class OrdersComponent {
orders$ = this.ordersService.orders$;
currentBalance$ = this.paymentService.paymentBalance$
constructor(
private ordersService: OrdersService,
private paymentService: PaymentsService
) {
}
addOrder(order: HTMLInputElement) {
this.ordersService.addOrder(order.valueAsNumber);
this.paymentService.updateBalance(2);
order.value = '';
}
}
در نشانه گذاری HTML، اشتراک را اضافه کنید currentBalance$
. اگر نه، الگو را نشان دهید noBalance
و دکمه را غیرفعال کنید اگر balance= 0 باشد، کد نهایی به شکل زیر است:
<div *ngIf="currentBalance$ | async as balance; else noBalance">
<h2>You have {{balance | currency }} as balance, add your orders</h2>
<input #order type="number">
<button (click)="addOrder(order)" [disabled]="balance <= 0">add order</button>
</div>
<ng-template #noBalance>
<h2> Insuficient Balance, please add.</h2>
</ng-template>
<div *ngIf="orders$ | async as orders">
You have ({{orders.length}}) orders.
<div *ngFor="let order of orders">
{{order}}
</div>
</div>
تغییرات را ذخیره کنید و برنامه دوباره بارگیری شود. یک موجودی اولیه اضافه کنید، و وقتی سفارش را اضافه می کنید، موجودی برای هر سفارش.
همه ایالات را ترکیب کنید
چالش با Orders ساده بود، ما یک سرویس واحد اضافه کردیم، اما چه چیزی می خواهیم از همه سرویس ها داده دریافت کنیم؟
Rxj ها چند عملگر را برای ترکیب Observables ارائه می دهند merge
و concat
، اما در سناریوی ما از آن استفاده می کنیم combineLatest
.
ما هر قابل مشاهده را در یک شی واحد با استفاده از عملگر نقشه ترکیب می کنیم و آن را در قالب مصرف می کنیم.
-
تزریق کنید
ProfileService
،OrdersService
، وPaymentsService
-
استفاده کنید
combineLatest
اپراتور برای ترکیب هر داده خدمات در یک شی واحد و مشترک شدن در قالب
import { Component } from '@angular/core';
import { combineLatest } from 'rxjs';
import { OrdersService } from 'src/app/services/orders.service';
import { PaymentsService } from 'src/app/services/payments.service';
import { ProfileService } from 'src/app/services/profile.service';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
})
export class HomeComponent {
customerStatus$ = combineLatest([
this.paymentService.paymentBalance$,
this.orderService.orders$,
this.profileService.name$,
]).pipe(
map(([balance, orders, profileName]) => ({ balance, orders, profileName }))
);
constructor(
private profileService: ProfileService,
private orderService: OrdersService,
private paymentService: PaymentsService
) { }
}
نشانه گذاری HTML subscribe
به قابل مشاهده با استفاده از pipe async نام، تعادل و دستورات را ارائه می دهد.
<div *ngIf="customerStatus$ | async as customerStatus">
<p>Hey! {{customerStatus.profileName}}, your balance is {{ customerStatus.balance | currency }}
in {{customerStatus.orders.length}}</p>
<div *ngFor="let order of customerStatus.orders">
{{ order }}
</div>
</div>
تغییرات را ذخیره کنید و زمانی که هر یک از قابل مشاهدهها مؤلفه اصلی را منتشر میکند، مرورگر دوباره بارگیری میشود تا دادههای هر یک را دریافت کند.
ما سه تزریق در home.component.ts
برای ارائه اطلاعات جزء این کار می کند اما سر و صدای بیش از حد در home.component.ts
; شاید بتوانیم آن را ساده کنیم.
موضوع رفتار را متمرکز کنید
یکی از راه حل ها ایجاد است appService
; تمام وضعیت مورد نیاز برنامه ما را ارائه می دهد. به جای تزریق همه خدمات به هر صفحه، ما یک نقطه ورود داریم. شروع کنیم:
-
یک سرویس جدید ایجاد کنید
AppService
-
تزریق کنید
ProfileService
،OrdersService
، وPaymentsService
-
استفاده کنید
combineLatest
اپراتور برای ترکیب هر داده سرویس در یک شی واحد و نمایش آن در ویژگیcustomerInfo$
-
دو خاصیت اضافه کنید
orders$
وbalance$
. -
روش را اضافه کنید
addOrder
برای به روز رسانی موجودی و سفارشات.
import {Injectable} from '@angular/core';
import {ProfileService} from "./profile.service";
import {OrdersService} from "./orders.service";
import {PaymentsService} from "./payments.service";
import {map} from 'rxjs/internal/operators/map';
import {combineLatest} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AppService {
public customerAndBalance$ = combineLatest([
this.paymentService.paymentBalance$,
this.profileService.name$,
]).pipe(
map(([balance, name]) => ({balance, name}))
);
customer$ = this.profileService.name$;
orders$ = this.orderService.orders$;
balance$ = this.paymentService.paymentBalance$;
constructor(
private profileService: ProfileService,
private orderService: OrdersService,
private paymentService: PaymentsService
) {
}
//comment with ngJörger
addOrder(order: number) {
this.orderService.addOrder(order);
this.paymentService.updateBalance(1);
}
}
در home.component.html
خدمات را حذف کرده و اضافه کنید appService
در سازنده، و به روز رسانی مرجع customerStatus
برای اشاره به customerInfo$
روش از appService
.
ذخیره کنید، و همه چیز همانطور که انتظار می رود به کار خود ادامه می دهد.
import {Component} from '@angular/core';
import {HomeService} from "./home.service";
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
})
export class HomeComponent {
customerStatus$ = this.appService.customerInfo$;
constructor(private appService: AppService) {
}
}
سرویس خانه وابستگی های کمتری دارد، بنابراین کد ما تمیز به نظر می رسد. ما می توانیم راه حل خود را ساده کرده و مسئولیتی را به یک جزء خاص واگذار کنیم.
مسئولیت جزء
صفحه اصلی دارای دو بخش است که در صفحات تکرار می شود، نام کاربری با موجودی و لیست سفارشات.
بیایید با شروع کنیم OrderListComponent
، با استفاده از Angular/CLI برای تولید کامپوننت جدید ng g c /components/orderlist
:
-
تزریق کنید
AppService
در سازنده -
یک را اعلام کنید
orders$
قابل مشاهده برای خواندن تمام سفارشات در خدمات. -
نشانه گذاری HTML را باز کنید و در آن مشترک شوید
orders$
با استفاده از لوله قابل مشاهده استasync
و با آن تکرار کنیدngFor
.
کد نهایی به شکل زیر است:
import {Component} from '@angular/core';
import {AppService} from "../../services/app.service";
@Component({
selector: 'app-order-list',
templateUrl: './order-list.component.html',
styleUrls: ['./order-list.component.css']
})
export class OrderListComponent {
orders$ = this.appService.orders$;
constructor(private appService: AppService) {
}
}
<div *ngIf="orders$ | async as orders">
You have ({{orders.length}}) orders.
<div *ngFor="let order of orders">
{{order}}
</div>
</div>
فرآیند دقیق با پیام مشتری یک مؤلفه را ایجاد می کند Angular/CLI
.
-
دستور را اجرا کنید
ng g c /components/customer-message
-
تزریق کنید
AppService
در سازنده -
الف را اعلام کنید
customerInfo$
قابل مشاهده برای خواندنcustomerAndBalance$
ارزش. -
نشانه گذاری HTML را باز کنید و در آن مشترک شوید
customerInfo$
با استفاده از لوله قابل مشاهده استasync
.
کد نهایی به شکل زیر است:
import {Component} from '@angular/core';
import {AppService} from "../../services/app.service";
@Component({
selector: 'app-customer-message',
templateUrl: './customer-message.component.html',
styleUrls: ['./customer-message.component.css']
})
export class CustomerMessageComponent {
public customerInfo$ = this.appService.customerAndBalance$;
constructor(private appService: AppService) {
}
}
<div *ngIf="customerInfo$ | async as customer">
<div *ngIf="customer.name && customer.balance; else updateInfo">
<p>Hey! {{customer.name}}, your balance is {{ customer.balance | currency }}</p>
</div>
</div>
<ng-template #updateInfo>
Please add your name and balance
</ng-template>
ما قبلا AppService را تزریق کرده ایم orderList
و customerMessage
، که به ساده سازی برنامه و بازسازی سایر مؤلفه ها کمک می کند.
اجزای Refactor
ما دو جزء برای ساده کردن خانه و سفارشات داریم.
کامپوننت Home مانند یک ظرف برای customerMessage
و orderList
اجزاء، بنابراین اجازه می دهد تا تمیز کنیم:
-
تزریق appService را از سازنده حذف کنید.
-
در قالب، از مولفه های customer-message و listOrder استفاده کنید.
کد نهایی به شکل زیر است:
import {Component} from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
})
export class HomeComponent {
}
<app-customer-message></app-customer-message>
<app-order-list></app-order-list>
مولفه Orders با استفاده از AppService و جزء orderlist ساده می شود.
-
تزریق کنید
AppService
در سازنده -
الف را اعلام کنید
balance$
قابل مشاهده برای خواندن مقدار تعادل از AppService. -
را به روز کنید
addOrder
روش برای فراخوانی همان ازappService
. -
نشانه گذاری HTML را باز کنید و در آن مشترک شوید
balance$
با استفاده از لوله قابل مشاهده استasync
. -
اضافه کردن
customer
وorderlist
جزء برای نمایش داده های کاربر و لیست سفارشات.
کد نهایی به شکل زیر است:
import {Component} from '@angular/core';
import {AppService} from "../../services/app.service";
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html',
styleUrls: ['./orders.component.css'],
})
export class OrdersComponent {
balance$ = this.appService.balance$
constructor(
private appService: AppService
) {
}
addOrder(order: HTMLInputElement) {
this.appService.addOrder(order.valueAsNumber)
order.value = '';
}
}
<div *ngIf="balance$ | async as balance; else noBalance">
<app-customer-message></app-customer-message>
<input #order type="number">
<button (click)="addOrder(order)" [disabled]="balance <= 0">add order</button>
</div>
<ng-template #noBalance>
<h2> Insufficient Balance, please add.</h2>
</ng-template>
<app-order-list></app-order-list>
آنچه می آموزم
مثال کوچکی بود. ما وقت داشتیم که بدون زمان، بازار، سرعت تیم و فشار شرکت، مرور کنیم و وقت بگذاریم.
-
از Rxjs Observable استفاده کنید، که به ما اجازه می دهد تا یک برنامه واکنشی را سریع و بدون پیچیدگی زیاد بسازیم.
-
وقتی هر موجودیتی خدمات خود را برای حفظ وضعیت دارد، اگر یک جزء به همه اطلاعات نیاز داشته باشد، نیاز به تزریق همه آنها دارد.
-
برای کاهش مقدار تزریق، ما همه خدمات “پل” را برای ارتباط با موضوعات رفتاری متعددی مانند
AppService
-
ما باید تشخیص دهیم که کدام مؤلفه ها می توانند به صورت ایزوله کار کنند، عملکردی را بدون وابستگی های زیاد ارائه دهیم و مؤلفه های خود را ساده کنیم.
-
استفاده از الگوی هوشمند و اجزای dump به ساده سازی ترکیب اجزا کمک می کند.
آیا به مدیر دولتی نیاز دارم؟
اگر برنامه شما یک MVP یا کوچک مانند این است، BehaviorSubject مشکلی ندارد. من میخواهم کمی وقت بگذارم تا دوباره همان برنامه را با استفاده از یک مدیر ایالتی بسازم NgRx یا NGXS.
به زودی میبینمت!
عکس از Agê Barros در Unsplash