برنامه نویسی

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 و سایر فناوری ها را دنبال کنید.

منابع:

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا