RxJS را با سیگنال های زاویه ای در برنامه Pokemon جایگزین کنید

معرفی
من یک برنامه ساده پوکمون را در Angular 15 و RxJS نوشتم تا URL های تصویر یک پوکمون خاص را نمایش دهد. در این مورد، من میخواهم RxJS را با سیگنالهای Angular جایگزین کنم تا کدهای واکنشی سادهتر شوند. هنگامی که بازآفرینی کد کامل شد، متد ngOnInit هیچ کد RxJs ندارد و می تواند حذف شود. علاوه بر این، ViewChild اضافی است و دیگر NgIf و AsyncPipe وارد نمی شود.
مراحل به شرح زیر خواهد بود:
- یک سیگنال برای ذخیره شناسه فعلی پوکمون ایجاد کنید
- یک سیگنال محاسباتی ایجاد کنید که URL های تصویر پوکمون را بسازد
- الگوی درون خطی را بهروزرسانی کنید تا به جای آن از سیگنال و سیگنال محاسبهشده استفاده کنید
- واردات NgIf و AsyncPipe را حذف کنید
کامپوننت قدیمی پوکمون با کدهای RxJS
// pokemon.component.ts
...omitted import statements...
@Component({
selector: 'app-pokemon',
standalone: true,
imports: [AsyncPipe, NgIf],
template: `
<h1>
Display the first 100 pokemon images
</h1>
<div>
<label>Pokemon Id:
<span>{{ btnPokemonId$ | async }}</span>
</label>
<div class="container" *ngIf="images$ | async as images">
<img [src]="images.frontUrl" />
<img [src]="images.backUrl" />
</div>
</div>
<div class="container">
<button class="btn" #btnMinusTwo>-2</button>
<button class="btn" #btnMinusOne>-1</button>
<button class="btn" #btnAddOne>+1</button>
<button class="btn" #btnAddTwo>+2</button>
</div>
`,
styles: [`...omitted due to brevity...`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent implements OnInit {
@ViewChild('btnMinusTwo', { static: true, read: ElementRef })
btnMinusTwo!: ElementRef<HTMLButtonElement>;
@ViewChild('btnMinusOne', { static: true, read: ElementRef })
btnMinusOne!: ElementRef<HTMLButtonElement>;
@ViewChild('btnAddOne', { static: true, read: ElementRef })
btnAddOne!: ElementRef<HTMLButtonElement>;
@ViewChild('btnAddTwo', { static: true, read: ElementRef })
btnAddTwo!: ElementRef<HTMLButtonElement>;
btnPokemonId$!: Observable<number>;
images$!: Observable<{ frontUrl: string, backUrl: string }>;
ngOnInit() {
const btnMinusTwo$ = this.createButtonClickObservable(this.btnMinusTwo, -2);
const btnMinusOne$ = this.createButtonClickObservable(this.btnMinusOne, -1);
const btnAddOne$ = this.createButtonClickObservable(this.btnAddOne, 1);
const btnAddTwo$ = this.createButtonClickObservable(this.btnAddTwo, 2);
this.btnPokemonId$ = merge(btnMinusTwo$, btnMinusOne$, btnAddOne$, btnAddTwo$)
.pipe(
scan((acc, value) => {
const potentialValue = acc + value;
if (potentialValue >= 1 && potentialValue <= 100) {
return potentialValue;
} else if (potentialValue < 1) {
return 1;
}
return 100;
}, 1),
startWith(1),
shareReplay(1),
);
const pokemonBaseUrl = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon';
this.images$ = this.btnPokemonId$.pipe(
map((pokemonId: number) => ({
frontUrl: `${pokemonBaseUrl}/shiny/${pokemonId}.png`,
backUrl: `${pokemonBaseUrl}/back/shiny/${pokemonId}.png`
}))
);
}
createButtonClickObservable(ref: ElementRef<HTMLButtonElement>, value: number) {
return fromEvent(ref.nativeElement, 'click').pipe(map(() => value));
}
}
من کامپوننت Pokemon را بازنویسی می کنم تا RxJS را با سیگنال های Angular جایگزین کنم، ngOnInit را بی فایده کنم و آن را حذف کنم.
ابتدا، من یک سیگنال برای ذخیره شناسه فعلی پوکمون ایجاد می کنم
// pokemon-component.ts
currentPokemonId = signal(1);
سپس، قالب درون خطی را تغییر میدهم تا رویداد کلیک را به عناصر دکمه اضافه کنم تا سیگنال فعلی PokemonId را بهروزرسانی کنم.
Before (RxJS)
<div class="container">
<button class="btn" #btnMinusTwo>-2</button>
<button class="btn" #btnMinusOne>-1</button>
<button class="btn" #btnAddOne>+1</button>
<button class="btn" #btnAddTwo>+2</button>
</div>
After (Signal)
<div class="container">
<button class="btn" (click)="updatePokemonId(-2)">-2</button>
<button class="btn" (click)="updatePokemonId(-1)">-1</button>
<button class="btn" (click)="updatePokemonId(1)">+1</button>
<button class="btn" (click)="updatePokemonId(2)">+2</button>
</div>
در نسخه سیگنال، من متغیرهای قالب را حذف می کنم به طوری که مؤلفه برای پرس و جو از HTMLButtonElement به ViewChild نیاز ندارد.
readonly min = 1;
readonly max = 100;
updatePokemonId(delta: number) {
this.currentPokemonId.update((pokemonId) => {
const newId = pokemonId + delta;
return Math.min(Math.max(this.min, newId), this.max);
});
}
وقتی دکمه کلیک می شود، updatePokemonId
مجموعه ها currentPokemonId
به مقداری بین 1 تا 100.
در مرحله بعد، الگوی درون خطی را بیشتر تغییر می دهم تا جایگزین شود images$
قابل مشاهده با imageUrls
سیگنال محاسبه شده و btnPokemonId$
قابل مشاهده با currentPokemonId
Before (RxJS)
<div>
<label>Pokemon Id:
<span>{{ btnPokemonId$ | async }}</span>
</label>
<div class="container" *ngIf="images$ | async as images">
<img [src]="images.frontUrl" />
<img [src]="images.backUrl" />
</div>
</div>
After (Signal)
<div>
<label>Pokemon Id:
<span>{{ currentPokemonId() }}</span>
</label>
<div class="container">
<img [src]="imageUrls().front" />
<img [src]="imageUrls().back" />
</div>
</div>
در نسخه سیگنال فراخوانی می کنم currentPokemonId()
برای نمایش شناسه فعلی پوکمون. imageUrls
یک سیگنال محاسبه شده است که URL های جلو و عقب پوکمون را برمی گرداند.
const pokemonBaseUrl = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon';
imageUrls = computed(() => ({
front: `${pokemonBaseUrl}/shiny/${this.currentPokemonId()}.png`,
back: `${pokemonBaseUrl}/back/shiny/${this.currentPokemonId()}.png`
}));
پس از اعمال این تغییرات، قالب درون خطی به لوله async و ngIf متکی نیست و می توان آنها را از آرایه واردات حذف کرد.
کامپوننت جدید پوکمون با استفاده از سیگنال های زاویه ای
// pokemon.component.ts
import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core';
const pokemonBaseUrl = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon';
@Component({
selector: 'app-pokemon',
standalone: true,
template: `
<h2>
Use Angular Signal to display the first 100 pokemon images
</h2>
<div>
<label>Pokemon Id:
<span>{{ currentPokemonId() }}</span>
</label>
<div class="container">
<img [src]="imageUrls().front" />
<img [src]="imageUrls().back" />
</div>
</div>
<div class="container">
<button class="btn" (click)="updatePokemonId(-2)">-2</button>
<button class="btn" (click)="updatePokemonId(-1)">-1</button>
<button class="btn" (click)="updatePokemonId(1)">+1</button>
<button class="btn" (click)="updatePokemonId(2)">+2</button>
</div>
`,
styles: [`...omitted due to brevity...`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent {
readonly min = 1;
readonly max = 100;
currentPokemonId = signal(1);
updatePokemonId(delta: number) {
this.currentPokemonId.update((pokemonId) => {
const newId = pokemonId + delta;
return Math.min(Math.max(this.min, newId), this.max);
});
}
imageUrls = computed(() => ({
front: `${pokemonBaseUrl}/shiny/${this.currentPokemonId()}.png`,
back: `${pokemonBaseUrl}/back/shiny/${this.currentPokemonId()}.png`
}));
}
نسخه جدید وابستگی صفر به کدهای RxJS دارد، رابط NgOnInit را پیاده سازی نمی کند و ngOnInit
روش. بسیاری از خطوط کد را حذف می کند تا خواندن و درک آن آسان تر شود.
این است و من برنامه Pokemon را برای جایگزینی RxJS با سیگنال های Angular بازنویسی کرده ام.
این پایان پست وبلاگ است و امیدوارم از مطالب خوشتان بیاید و تجربه یادگیری من در Angular و سایر فناوری ها را دنبال کنید.
منابع: