برنامه نویسی

زمان استفاده از اپراتورهای «concatMap»، «mergeMap»، «switchMap» و «exhaustMap» در ساختن CRUD با NgRx

چند روز پیش، من با افکت‌های NgRx در یک برنامه کار می‌کردم، جایی که باید داده‌ها را از فروشگاه دریافت می‌کردم و آن‌ها را با تماس‌های سرویس شامل وظایف ناهمزمان ترکیب می‌کردم.

هنگام کار با جریان های ترکیبی، مهم است که اپراتور مناسب را انتخاب کنید. بستگی به عملیاتی دارد که می خواهیم انجام دهیم. شما می توانید یکی را انتخاب کنید concatMap، exhaustMap، mergeMap، و switchMap.

بنابراین، من تعطیلات هستم، بنابراین چند دقیقه وقت می‌گذارم تا چیزی بسازم که باید بین هر کدام انتخاب کنم و البته با استفاده از API و کار async با افکت‌ها و تغییر حالت در کاهنده‌ام.

ایده این مقاله این است که با NgRx در فرم خود بمانید و سعی کنید بدانید کدام و چه زمانی از آن عملگرها استفاده می شود، پس بیایید به شناسه بپردازیم!

این مقاله بخشی از مجموعه من در مورد یادگیری NgRx است. اگر می خواهید دنبال کنید، لطفا آن را بررسی کنید.

پروژه

من می خواهم در برنامه خود بخشی ایجاد کنم تا مکان های مورد علاقه خود را در منورکا ذخیره کنم. من از mockAPI.io استفاده خواهم کرد، این یک سرویس عالی برای ساخت API جعلی و مدیریت عملیات CRUD است.

مخزن را شبیه سازی کنید start-with-ngrx. این پروژه شامل نمونه هایی از پست های قبلی من است.

https://github.com/danywalls/start-with-ngrx.git
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به شعبه تغییر دهید crud-ngrx، که شامل تنظیمات زیر است:

  • NgRx با DevTools نصب و پیکربندی شد.

  • آماده پیکربندی با MockAPi.io APi

  • این PlacesService برای افزودن، به‌روزرسانی، حذف و دریافت مکان‌ها.

  • این PlaceComponentکامپوننت خالی برای بارگذاری روی places مسیر مسیر

هدف

هدف این است که با ساختن یک CRUD با NgRx و درک زمان استفاده از عملگرهای خاص RxJS، آنچه را که در مقالات قبلی آموخته‌ایم تمرین کنیم. در این مقاله به موارد زیر خواهیم پرداخت:

  • یک حالت برای مکان ها ایجاد کنید.

  • اقداماتی را برای عملیات CRUD ایجاد کنید.

  • انتخابگرها را برای دریافت مکان ها ایجاد کنید.

  • از افکت‌ها برای ایجاد، به‌روزرسانی، حذف و دریافت کنش‌ها استفاده کنید.

  • به‌روزرسانی کنید و مکان‌ها را از API حذف کنید.

بیا شروع کنیم!

اگر فقط به یادگیری در مورد اپراتورها علاقه دارید، به بخش پیاده سازی بروید.

ایجاد وضعیت مکان ها

ما می‌خواهیم وضعیت مکان‌ها را مدیریت کنیم که باید موارد زیر را ذخیره کند:

  • فهرستی از مکان‌ها از API.

  • مکان انتخاب شده برای ویرایش یا حذف.

  • یک پیام در حال بارگیری هنگام بارگیری داده یا انجام یک عمل.

  • در صورتی که هنگام افزودن، به‌روزرسانی یا حذف خطایی وجود داشته باشد، پیام خطا.

با در نظر گرفتن این نکات، اجازه دهید یک فایل جدید ایجاد کنیم places/state/places.state.ts و اضافه کنید PlaceState تعریف را با یک نوع یا رابط (هر کدام که ترجیح می دهید) و وضعیت اولیه را برای مکان ها اعلام کنید.

کد نهایی به شکل زیر است:

import { Place } from '../../../entities/place.model';

export type PlacesState = {
  places: ArrayPlace>;
  placeSelected: Place | undefined;
  loading: boolean;
  error: string | undefined;
};

export const placesInitialState: PlacesState = {
  error: '',
  loading: false,
  placeSelected: undefined,
  places: [],
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کامل! ما حالت اولیه را برای مکان ها داریم. زمان ایجاد اقدامات است!

Actions را ایجاد کنید

مشابه پست قبلی خود، از آن استفاده خواهیم کرد createActionGroup ویژگی برای گروه بندی اقدامات ما مربوط به PlaceState. ما دو نوع اقدام داریم: اقداماتی که توسط کاربر در UI راه اندازی می شود و اقدامات مربوط به فرآیند API. بنابراین، من ترجیح می دهم اقدامات را به دو نوع تقسیم کنم: PlacePageActions و PlaceApiActions.

ابتدا اقدامات را از روی تعریف می کنیم PlacePage برای انجام موارد زیر:

  • درخواست بارگذاری مکان ها: load Places

  • افزودن/به‌روزرسانی مکان: مکانی را با شیء Place اضافه کنید

  • حذف مکان: شناسه مکان را برای حذف ارسال کنید

  • مکان را انتخاب و لغو انتخاب کنید.

این places.actions.ts کد فایل به شکل زیر است:

import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Place } from '../../../entities/place.model';

export const PlacesPageActions = createActionGroup({
  source: 'Places',
  events: {
    'Load Places': emptyProps(),
    'Add Place': props{ place: Place }>(),
    'Update Place': props{ place: Place }>(),
    'Delete Place': props{ id: string }>(),
    'Select Place': props{ place: Place }>(),
    'UnSelect Place': emptyProps(),
  },
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما اقداماتی برای انجام از اثرات داریم. این کنش‌ها با API مرتبط هستند و هنگام بارگیری، افزودن، به‌روزرسانی یا حذف با موفقیت یا شکست، به درخواست و پاسخ رسیدگی می‌کنند.

کد نهایی به شکل زیر است:

export const PlacesApiActions = createActionGroup({
  source: 'PlaceAPI',
  events: {
    'Load Success': props{ places: ArrayPlace> }>(),
    'Load Fail': props{ message: string }>(),
    'Add Success': props{ place: Place }>(),
    'Add Failure': props{ message: string }>(),
    'Update Success': props{ place: Place }>(),
    'Update Failure': props{ message: string }>(),
    'Delete Success': props{ id: string }>(),
    'Delete Failure': props{ message: string }>(),
  },
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما اقدامات خود را داریم، پس وقت آن است که کاهش دهنده را ایجاد کنیم تا وضعیت خود را به روز کنیم!

کاهنده

کاهنده مسئول به روز رسانی وضعیت ما بر اساس اقدامات است. در برخی موارد، می‌خواهیم وضعیت خود را هنگام راه‌اندازی یک عمل یا بر اساس نتیجه یک عمل در افکت به‌روزرسانی کنیم.

مثلا اولین اقدام loadPlaces توسط PlaceComponent. در آن لحظه، من می خواهم صفحه بارگیری را نشان دهم، بنابراین تنظیم کردم loading به true. در موارد دیگر، مانند loadSuccess عمل، آن را از اثر زمانی که ما آمده است PlaceService داده ها را برمی گرداند.

هر اکشنی را برای ایجاد تغییرات در وضعیت مورد نیاز خود اجرا کنید، من قصد ندارم هر کدام را توضیح دهم اما کد نهایی به نظر می رسد:

import { createReducer, on } from '@ngrx/store';
import { placesInitialState } from './places.state';
import { PlacesApiActions, PlacesPageActions } from './places.actions';

export const placesReducer = createReducer(
  placesInitialState,
  on(PlacesPageActions.loadPlaces, (state) => ({
    ...state,
    loading: true,
  })),
  on(PlacesPageActions.selectPlace, (state, { place }) => ({
    ...state,
    placeSelected: place,
  })),
  on(PlacesPageActions.unSelectPlace, (state) => ({
    ...state,
    placeSelected: undefined,
  })),

  on(PlacesApiActions.loadSuccess, (state, { places }) => ({
    ...state,
    places: [...places],
  })),

  on(PlacesApiActions.loadFailure, (state, { message }) => ({
    ...state,
    loading: false,
    error: message,
  })),
  on(PlacesApiActions.addSuccess, (state, { place }) => ({
    ...state,
    loading: false,
    places: [...state.places, place],
  })),
  on(PlacesApiActions.addFailure, (state, { message }) => ({
    ...state,
    loading: false,
    message,
  })),
  on(PlacesApiActions.updateSuccess, (state, { place }) => ({
    ...state,
    loading: false,
    placeSelected: undefined,
    places: state.places.map((p) => (p.id === place.id ? place : p)),
  })),
  on(PlacesApiActions.updateFailure, (state, { message }) => ({
    ...state,
    loading: false,
    message,
  })),
  on(PlacesApiActions.deleteSuccess, (state, { id }) => ({
    ...state,
    loading: false,
    placeSelected: undefined,
    places: [...state.places.filter((p) => p.id !== id)],
  })),
  on(PlacesApiActions.deleteFailure, (state, { message }) => ({
    ...state,
    loading: false,
    message,
  })),
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما کاهنده خود را آماده واکنش به اقدامات هستیم. حالا قسمت هیجان انگیز می آید: جلوه ها! بیایید آن را انجام دهیم!

تاثیر

افکت به کنش‌ها گوش می‌دهد، وظایف همگام را انجام می‌دهد و سایر اقدامات را ارسال می‌کند. مهم است که بدانید از کدام اپراتور RxJS استفاده کنید—concatMap، exhaustMap، mergeMap، یا switchMap– برای جلوگیری از ایجاد شرایط مسابقه در کد خود.

شرایط مسابقه چیست؟

شرایط مسابقه زمانی اتفاق می‌افتد که چندین کار ناهمزمان (مشاهده‌پذیر) به طور همزمان اجرا می‌شوند و به روش‌هایی که ما نمی‌خواهیم با یکدیگر تداخل می‌کنند. این می تواند باعث رفتار غیرمنتظره یا اشکالات شود زیرا زمان بندی این وظایف به خوبی کنترل نمی شود.

exhaustMap: اگر درخواستی در حال انجام است، درخواست های جدید را نادیده می گیرد.

concatMap: هر درخواست را یکی پس از دیگری به ترتیب پردازش می کند.

mergeMap: به همه درخواست‌ها اجازه می‌دهد تا همزمان اجرا شوند، اما برای جلوگیری از مشکلات باید پاسخ‌ها را به درستی مدیریت کنید.

switchMap: در صورت ورود درخواست جدید، درخواست قبلی را لغو می کند. این تضمین می کند که فقط آخرین درخواست پردازش می شود.

بیایید از هر یک در اثر خود استفاده کنیم!

اگر می‌خواهید درباره این اپراتورها بیشتر بدانید، به شدت توصیه می‌کنم ویدیوهای @decodedfrontend را بررسی کنید

ExhaustMap

exhaustMap درخواست‌های جدید را نادیده می‌گیرد، اگر درخواستی از قبل در حال انجام باشد، برای سناریوهایی مفید است که فقط اولین درخواست باید پردازش شود، و درخواست‌های بعدی باید تا زمانی که اولین درخواست تکمیل شود نادیده گرفته شوند. به عنوان مثال لیست مکان ها را دریافت کنید.

import { Actions, createEffect, ofType } from '@ngrx/effects';
import { inject } from '@angular/core';
import { PlacesService } from '../../../services/places.service';
import { PlacesApiActions, PlacesPageActions } from './places.actions';
import { catchError, exhaustMap, map, of } from 'rxjs';

export const loadPlacesEffect = createEffect(
  (actions$ = inject(Actions), placesService = inject(PlacesService)) => {
    return actions$.pipe(
      ofType(PlacesPageActions.loadPlaces),
      exhaustMap(() =>
        placesService.getAll().pipe(
          map((places) => PlacesApiActions.loadSuccess({ places })),
          catchError((error) =>
            of(PlacesApiActions.loadFailure({ message: error })),
          ),
        ),
      ),
    );
  },
  { functional: true },
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ConcatMap

concatMap به ما کمک می‌کند تا کنش‌ها را ترسیم کنیم و قابل مشاهده‌هایمان را به ترتیب در یک مشاهده‌پذیر ادغام کنیم، اما قبل از ادامه دادن به مورد بعدی منتظر می‌ماند تا هر یک کامل شود. زمانی که می‌خواهیم مطمئن شویم همه چیز به ترتیب اعلام‌شده پیش می‌رود، ایمن‌ترین اپراتور است. برای مثال، اگر مکانی را به‌روزرسانی می‌کنیم، می‌خواهیم اولین به‌روزرسانی قبل از راه‌اندازی مکان دیگری تکمیل شود.

export const updatePlaceEffect$ = createEffect(
  (actions$ = inject(Actions), placesService = inject(PlacesService)) => {
    return actions$.pipe(
      ofType(PlacesPageActions.addPlace),
      concatMap(({ place }) =>
        placesService.update(place).pipe(
          map((apiPlace) =>
            PlacesApiActions.updateSuccess({ place: apiPlace }),
          ),
          catchError((error) =>
            of(PlacesApiActions.updateFailure({ message: error })),
          ),
        ),
      ),
    );
  },
  { functional: true },
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

MergeMap

این mergeMap اپراتور بدون حفظ نظم سریع اجرا می شود. این اجازه می دهد تا همه درخواست ها به طور همزمان اجرا شوند، اما برای جلوگیری از شرایط مسابقه باید پاسخ ها را به درستی مدیریت کنیم. این برای افزودن و حذف اقدامات عالی است.

افکت افزودن

export const addPlacesEffect$ = createEffect(
  (actions$ = inject(Actions), placesService = inject(PlacesService)) => {
    return actions$.pipe(
      ofType(PlacesPageActions.addPlace),
      mergeMap(({ place }) =>
        placesService.add(place).pipe(
          map((apiPlace) => PlacesApiActions.addSuccess({ place: apiPlace })),
          catchError((error) =>
            of(PlacesApiActions.addFailure({ message: error })),
          ),
        ),
      ),
    );
  },
  { functional: true },
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اثر حذف

من دو اثر برای deleteAction آن را تحریک می کند deleteSuccess اقدام برای به روز رسانی ایالت


export const deletePlaceEffect$ = createEffect(
  (actions$ = inject(Actions), placesService = inject(PlacesService)) => {
    return actions$.pipe(
      ofType(PlacesPageActions.deletePlace),
      mergeMap(({ id }) =>
        placesService.delete(id).pipe(
          map((id_response) =>
            PlacesApiActions.deleteSuccess({ id: id_response }),
          ),
          catchError((error) =>
            of(PlacesApiActions.deleteFailure({ message: error })),
          ),
        ),
      ),
    );
  },
  { functional: true },
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اما وقتی کاربر مورد را حذف می‌کند، باید دوباره داده‌ها را دریافت کنم، بنابراین باید به اکشن deleteSuccess گوش دهم تا دوباره داده‌ها را دریافت کنم و وضعیت را تازه کنم.

export const deletePlaceSuccessEffect$ = createEffect(
  (actions$ = inject(Actions), placesService = inject(PlacesService)) => {
    return actions$.pipe(
      ofType(PlacesApiActions.deleteSuccess),
      mergeMap(() =>
        placesService.getAll().pipe(
          map((places) => PlacesApiActions.loadSuccess({ places })),
          catchError((error) =>
            of(PlacesApiActions.loadFailure({ message: error.message })),
          ),
        ),
      ),
    );
  },
  { functional: true },
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

SwitchMap چطور؟

switchMap زمانی استفاده کنید که می‌خواهید مطمئن شوید که فقط آخرین درخواست پردازش می‌شود و درخواست‌های قبلی لغو می‌شوند. ایده آل برای سناریوهایی مانند تکمیل خودکار یا جستجوی زنده. این کمک می کند تا مشاهده پذیرهای فعال را با یک مورد جدید لغو کنید. در سناریوی من موردی ندارم، اما ذکر آن برای مراقبت مهم است.

ثبت اثرات و کاهش دهنده

زمان ثبت ایالت جدیدم فرا رسیده است placesReducer و placesEffectsرا باز کنید app.config.ts،

  • افزودن کلید جدید در provideStore، places: placesReducer

  • وارد کردن تمام جلوه ها از places.effects.ts ، اضافه کردن به provideEffects تابع.

import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideStore } from '@ngrx/store';
import { homeReducer } from './pages/home/state/home.reducer';
import { placesReducer } from './pages/places/state/places.reducer';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authorizationInterceptor } from './interceptors/authorization.interceptor';
import { provideEffects } from '@ngrx/effects';
import * as homeEffects from './pages/home/state/home.effects';
import * as placesEffects from './pages/places/state/places.effects';

export const appConfig = {
  providers: [
    provideRouter(routes),
    provideStore({
      home: homeReducer,
      places: placesReducer, //register placesReducer
    }),
    provideStoreDevtools({
      name: 'nba-app',
      maxAge: 30,
      trace: true,
      connectInZone: true,
    }),
    provideEffects([homeEffects, placesEffects]), //the placesEffects
    provideAnimationsAsync(),
    provideHttpClient(withInterceptors([authorizationInterceptor])),
  ],
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Mm… خوب، ما اثر داریم اما چگونه داده ها را در کامپوننت دریافت می کنیم؟ من باید یک انتخاب کننده ایجاد کنم تا حالت را به دست بیاورم، بیایید آن را انجام دهیم!

انتخابگرها

فایل را ایجاد کنید places.selector.ts، با استفاده از createFeatureSelector تابع، از نوع PlaceState استفاده کرده و یک نام تعریف کنید.

const selectPlaceState = createFeatureSelectorPlacesState>('places');
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بعد، با استفاده از selectPlaceState، استفاده کنید createSelector تابعی برای ایجاد انتخابگر برای بخشی در وضعیت ما، مانند places، loading، error و activePlace و صادرات آن انتخابگر مصرف در کامپوننت.

کد به نظر می رسد:

import { createFeatureSelector, createSelector } from '@ngrx/store';
import { PlacesState } from './places.state';

const selectPlaceState = createFeatureSelectorPlacesState>('places');

const selectPlaces = createSelector(
  selectPlaceState,
  (placeState) => placeState.places,
);

const selectPlaceSelected = createSelector(
  selectPlaceState,
  (placeState) => placeState.placeSelected,
);
const selectLoading = createSelector(
  selectPlaceState,
  (placeState) => placeState.loading,
);
const selectError = createSelector(
  selectPlaceState,
  (placeState) => placeState.error,
);

export default {
  placesSelector: selectPlaces,
  selectPlaceSelected: selectPlaceSelected,
  loadingSelector: selectLoading,
  errorSelector: selectError,
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

استفاده از Actions و Selectors

مراحل نهایی استفاده از انتخابگرها و ارسال اکشن ها از کامپوننت ها، باز است places.component.ts ، به فروشگاه تزریق کنید. بعد اعلام کنید place$، error$ و placeSelected$ برای ذخیره مقدار از انتخابگرها.


  store = inject(Store);
  places$ = this.store.select(PlacesSelectors.placesSelector);
  error$ = this.store.select(PlacesSelectors.errorSelector);
  placeSelected$ = this.store.select(PlacesSelectors.selectPlaceSelected);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما باید مکان‌ها را نمایش دهیم، در مورد من کارت مکان و فرم مکان جزء را دارم.

بیایید روی آن کار کنیم!

محل کارت

مکان را به عنوان ویژگی ورودی دریافت می کند و دو عمل را حذف و انتخاب می کند.

import { Component, inject, input } from '@angular/core';
import { Place } from '../../entities/place.model';
import { Store } from '@ngrx/store';
import { PlacesPageActions } from '../../pages/places/state/places.actions';

@Component({
  selector: 'app-place-card',
  standalone: true,
  imports: [],
  templateUrl: './place-card.component.html',
  styleUrl: './place-card.component.scss',
})
export class PlaceCardComponent {
  place = input.requiredPlace>();
  store = inject(Store);

  edit() {
    this.store.dispatch(PlacesPageActions.selectPlace({ place: this.place() }));
  }

  remove() {
    this.store.dispatch(PlacesPageActions.deletePlace({ id: this.place().id }));
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در قالب، ورودی مکان را متصل کرده و دو دکمه برای فراخوانی روش ویرایش و حذف اضافه کنید.

نشانه گذاری html به نظر می رسد:

class="column">
class="card place-card">
class="card-image">
class="image is-4by3"> [alt]="place().description" [src]="place().avatar">
class="card-content">
class="media">
class="media-content">

class="title is-4">{{ place().name }}

class="subtitle is-6">Visited on: {{ place().createdAt }}

class="content"> {{ place().description }}.
Price: {{ place().price }}

class="buttons">

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید با مکان-فرم ادامه دهیم، کمی متفاوت است زیرا شکل را بگیرید selectedPlace از حالت و ارسال اقدام به روز رسانی.

import { Component, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { FormsModule } from '@angular/forms';
import { AsyncPipe, JsonPipe } from '@angular/common';
import PlacesSelectors from '../../pages/places/state/places.selectors';
import { PlacesPageActions } from '../../pages/places/state/places.actions';
import { Place } from '../../entities/place.model';

@Component({
  selector: 'app-place-form',
  standalone: true,
  imports: [FormsModule, JsonPipe, AsyncPipe],
  templateUrl: './place-form.component.html',
  styleUrl: './place-form.component.scss',
})
export class PlaceFormComponent {
  store = inject(Store);
  placeSelected$ = this.store.select(PlacesSelectors.selectPlaceSelected);

  delete(id: string) {
    this.store.dispatch(PlacesPageActions.deletePlace({ id }));
  }

  save(place: Place, name: string) {
    this.store.dispatch(
      PlacesPageActions.updatePlace({
        place: {
          ...place,
          name,
        },
      }),
    );
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در قالب، در selectedPlace مشترک شوید و برای آسان کردن آن و کوتاه نگه داشتن مقاله، یک پیوند ورودی به selectedPlace.name اضافه کنید، با استفاده از متغیرهای الگو، یک مرجع به نام ورودی #placeName ایجاد کنید.

در دکمه Save کلیک می کنیم، محل انتخاب شده و نام جدید را پاس می کنیم.

من می توانم کمی بهتر باشم، اما مقاله کمی طولانی تر شده است، اگر راه بهتری می دانید با راه حل خود نظر دهید یا یک روابط عمومی ارسال کنید و مطمئناً کد شما را اضافه می کنم و روی آن ذکر می کنم!.

@if (placeSelected$ | async; as selectedPlace) {

  div class="field">
    label class="label" for="placeName">Place Name/label>
    div class="control">
      input id="placeName" #placeName [value]="selectedPlace.name" class="input" placeholder="Place Name" type="text">
    /div>
  /div>

  div class="field is-grouped">
    div class="control">
      button (click)="save(selectedPlace, placeName.value)" class="button is-primary">Save/button>
    /div>
    div class="control">
      button (click)="delete(selectedPlace.id)" class="button is-danger">Delete/button>
    /div>
  /div>

}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نهایت در places.component من از کارت مکان و فرم مکان در ترکیب با انتخابگرها استفاده می کنم.

ما سه چیز کلیدی را ایجاد می کنیم:

  • در error$ مشترک شوید تا نشان دهید آیا یکی از آنها وجود دارد یا خیر.

  • در مکان‌ها مشترک شوید و با @for ترکیب کنید تا مکان‌ها را نشان دهید.

  • مشترک شدن در placeSelected$ برای نشان دادن مودال زمانی که کاربر یکی را انتخاب می‌کند و با کلیک روی دکمه بستن، متد onClose() را فعال می‌کند.

کد نهایی به نظر می رسد:

@if (error$ | async; as error) {
  

Ups we found a error {{ error }} } @if (places$ | async; as places) {
class="columns is-multiline is-flex is-flex-wrap-wrap"> @for (place of places; track place) { [place]="place"/> }
} @if (placeSelected$ | async; as selectedPlace) {
class="modal is-active">

class="modal-background">

}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه

در این پروژه، نحوه ایجاد یک برنامه CRUD با استفاده از NgRx و عملگرهای مختلف RxJS برای مدیریت اقدامات ناهمزمان را بررسی کردیم. ما همچنین مدیریت درخواست ها و پاسخ های HTTP از یک API را تمرین کردیم. با این حال، من احساس می کنم جایی برای پیشرفت وجود دارد.

نکاتی برای بهبود:

برای پرداختن به این مشکلات، من قصد دارم پروژه را با ترکیب موجودیت‌های NgRx با روتر NgRx برای ساده‌سازی کد و بهبود قابلیت نگهداری، بازسازی کنم.

منابع:

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

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

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

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