برنامه نویسی

الگوهای پیشرفته NGRX برای برنامه های زاویه ای سازمانی

به قسمت نهایی سری Deep Dive NGRX ما خوش آمدید! در قسمت های 1 و 2 ، ما اصول زاویه ای و NGRX و الگوهای حالت میانی را پوشانده ایم. اکنون ، ما در حال مقابله با چیزهای آبدار هستیم. الگوهای پیشرفته ای که وقتی برنامه زاویه ای سازمانی شما شروع به رشد می کند ، سلامت شما را نجات می دهد. (⏱ زمان خواندن است: 9.5 دقیقه)

اگر در کاربردهای در مقیاس بزرگ با چالش های پیچیده مدیریت دولت روبرو هستید ، احتمالاً درد مشاهده های درهم و برهم ، محاسبات اضافی و مؤلفه هایی را که بدون هیچ دلیل خوبی دوباره ارائه می دهند ، احساس کرده اید. بیایید این مشکلات را به همراه برخی از الگوهای NGRX آزمایش شده توسط نبرد که در ده ها پروژه سازمانی استفاده کردم ، حل کنیم.

بهینه سازی عملکرد با انتخاب کنندگان

قدرت انتخاب کننده های یاد شده

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

// Feature state interface
export interface DashboardState {
  metrics: Metric[];
  loading: boolean;
  error: string | null;
}

// Basic selectors
export const selectDashboardState = createFeatureSelector<DashboardState>('dashboard');
export const selectAllMetrics = createSelector(
  selectDashboardState,
  (state) => state.metrics
);

// Derived selectors with memoization benefits
export const selectActiveMetrics = createSelector(
  selectAllMetrics,
  (metrics) => metrics.filter(metric => metric.active)
);

// Complex calculations that won't recompute unnecessarily
export const selectMetricsSummary = createSelector(
  selectActiveMetrics,
  (activeMetrics) => {
    // This saves you from running expensive logic every time Angular blinks,
    // Especially important when rendering dashboards or charts
    return {
      total: activeMetrics.reduce((sum, metric) => sum + metric.value, 0),
      average: activeMetrics.length ? 
        activeMetrics.reduce((sum, metric) => sum + metric.value, 0) / activeMetrics.length : 0,
      count: activeMetrics.length
    };
  }
);
حالت تمام صفحه را وارد کنید

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

این امر مانع از مجدداً مؤلفه های غیر ضروری می شود و تشخیص تغییر Angular را لاغر می کند. کاربران شما هنگامی که به سرعت در تعامل با رابط های داده سنگین هستند ، تفاوت را متوجه می شوند.

ترکیب انتخاب کننده برای تحولات داده پیچیده

هنگامی که پیچیدگی برنامه شما افزایش می یابد ، می خواهید انتخاب کننده های کوچک و متمرکز ایجاد کنید که می توانید با هم آهنگسازی کنید:

// Combining data from multiple slices of state
export const selectUserPermissions = createSelector(
  authSelectors.selectCurrentUser,
  roleSelectors.selectAllRoles,
  (user, roles) => {
    const userRole = roles.find(role => role.id === user?.roleId);
    return userRole?.permissions || [];
  }
);

// Building on existing selectors for dashboard-specific permissions
export const selectUserDashboardPermissions = createSelector(
  selectUserPermissions,
  (permissions) => permissions.filter(p => p.resource === 'dashboard')
);
حالت تمام صفحه را وارد کنید

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

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

رسیدگی به احراز هویت و امنیت با NGRX

بیایید واقعی باشیم احراز هویت یکی از آن مواردی است که از نظر مفهوم ساده است اما سریع کثیف می شود. NGRX یک روش تمیز برای مدیریت همه قطعات متحرک به شما می دهد.

طراحی وضعیت احراز هویت

در اینجا یک حالت AUTH عملی وجود دارد که تمام پایه ها را پوشش می دهد:

export interface AuthState {
  user: User | null;
  token: string | null;
  loading: boolean;
  error: string | null;
  lastAuthAttempt: Date | null;
}

export const initialAuthState: AuthState = {
  user: null,
  token: null,
  loading: false,
  error: null,
  lastAuthAttempt: null
};
حالت تمام صفحه را وارد کنید

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

اقدامات احراز هویت

من دوست دارم با اقدامات AUTH خود خاص باشم ، بنابراین اشکال زدایی آسانتر در جاده است:

export const login = createAction(
  '[Auth] Login',
  props<{ username: string; password: string }>()
);

export const loginSuccess = createAction(
  '[Auth] Login Success',
  props<{ user: User; token: string }>()
);

export const loginFailure = createAction(
  '[Auth] Login Failure',
  props<{ error: string }>()
);

export const checkAuthStatus = createAction('[Auth] Check Status');
export const logout = createAction('[Auth] Logout');
حالت تمام صفحه را وارد کنید

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

اثرات احراز هویت برای مدیریت توکن

اینجا جایی است که جادوی واقعی اتفاق می افتد. جلوه هایی که چرخه عمر توکن را کنترل می کنند:

@Injectable()
export class AuthEffects {
  login$ = createEffect(() => 
    this.actions$.pipe(
      ofType(AuthActions.login),
      exhaustMap(action => 
        // Note: exhaustMap is used here to avoid parallel login attempts
        this.authService.login(action.username, action.password).pipe(
          map(response => {
            // Store token in secure storage
            this.tokenService.storeToken(response.token);
            return AuthActions.loginSuccess({
              user: response.user,
              token: response.token
            });
          }),
          catchError(error => of(AuthActions.loginFailure({ 
            error: error.message || 'Authentication failed' 
          })))
        )
      )
    )
  );

  logout$ = createEffect(() => 
    this.actions$.pipe(
      ofType(AuthActions.logout),
      tap(() => {
        // Clear token on logout
        this.tokenService.removeToken();
        this.router.navigate(['/login']);
      })
    ),
    { dispatch: false }
  );

  // Effect to check auth status on app initialization
  checkAuth$ = createEffect(() => 
    this.actions$.pipe(
      ofType(AuthActions.checkAuthStatus),
      switchMap(() => {
        const token = this.tokenService.getToken();
        if (!token) {
          return of(AuthActions.logout());
        }

        return this.authService.validateToken(token).pipe(
          map(user => AuthActions.loginSuccess({ user, token })),
          catchError(() => of(AuthActions.logout()))
        );
      })
    )
  );

  constructor(
    private actions$: Actions,
    private authService: AuthService,
    private tokenService: TokenService,
    private router: Router
  ) {}
}
حالت تمام صفحه را وارد کنید

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

من برنامه های زیادی را دیده ام که در آن نشانه ها به طور متناقض در سراسر مؤلفه ها اداره می شوند. این رویکرد متمرکز به شما یک منبع حقیقت برای دولت Auth می دهد.

نگهبانان مسیر با ایالت NGRX

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

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(
    private store: Store,
    private router: Router
  ) {}

  canActivate(): Observable<boolean> {
    return this.store.select(selectIsAuthenticated).pipe(
      tap(isAuthenticated => {
        if (!isAuthenticated) {
          this.router.navigate(['/login']);
        }
      }),
      take(1)
    );
  }
}

@Injectable({
  providedIn: 'root'
})
export class RoleGuard implements CanActivate {
  constructor(
    private store: Store,
    private router: Router
  ) {}

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    const requiredRole = route.data.role;

    return this.store.select(selectUserRole).pipe(
      map(userRole => userRole === requiredRole),
      tap(hasAccess => {
        if (!hasAccess) {
          this.router.navigate(['/unauthorized']);
        }
      }),
      take(1)
    );
  }
}
حالت تمام صفحه را وارد کنید

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

این نگهبانان به راحتی از طریق Observables Mock Store قابل آزمایش هستند ، که به گرفتن رگرسیون Auth کمک می کند. من نمی توانم به شما بگویم که با انجام آزمایشات محکم در اطراف این نگهبانان ، چند سوراخ امنیتی از آن جلوگیری کرده ام.

مدیریت نهاد برای جمع آوری داده ها

اگر در حال ساختن یک برنامه سازمانی هستید ، احتمالاً با مجموعه های زیادی ، کاربران ، محصولات ، سفارشات سر و کار دارید ، آن را نامگذاری می کنید. در @ngrx/entity بسته بهترین دوست شما در اینجا است.

تنظیم آداپتورهای موجودیت

export interface User {
  id: string;
  name: string;
  email: string;
  roleId: string;
  active: boolean;
}

export interface UserState extends EntityState<User> {
  selectedUserId: string | null;
  loading: boolean;
  error: string | null;
}

export const adapter = createEntityAdapter<User>({
  selectId: (user) => user.id,
  sortComparer: (a, b) => a.name.localeCompare(b.name) // Optional sort by name
});

export const initialState: UserState = adapter.getInitialState({
  selectedUserId: null,
  loading: false,
  error: null
});
حالت تمام صفحه را وارد کنید

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

این تنظیم به O (1) جستجوی می دهد و لیست UI را بسیار ارزان می کند. کاربران شما وقتی می توانند هزاران رکورد را بدون تاخیر می گذرانند ، از شما تشکر می کنند.

کاهش دهنده های موجود برای عملیات مشترک

آداپتور روشهای یاور را به شما می دهد که بالابر سنگین را کنترل می کنند:

export const userReducer = createReducer(
  initialState,

  // Load operations
  on(UserActions.loadUsers, (state) => ({
    ...state,
    loading: true,
    error: null
  })),
  on(UserActions.loadUsersSuccess, (state, { users }) => 
    adapter.setAll(users, { ...state, loading: false })
  ),

  // Add/update/remove operations
  on(UserActions.addUser, (state, { user }) => 
    adapter.addOne(user, state)
  ),
  on(UserActions.updateUser, (state, { update }) => 
    adapter.updateOne(update, state)
  ),
  on(UserActions.deleteUser, (state, { id }) => 
    adapter.removeOne(id, state)
  ),

  // Selection state
  on(UserActions.selectUser, (state, { id }) => ({
    ...state,
    selectedUserId: id
  }))
);
حالت تمام صفحه را وارد کنید

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

انتخاب کننده های موجود برای دسترسی به داده های کارآمد

آداپتور که در اینجا همه جمع می شود ، انتخاب کننده های بهینه شده را از جعبه به شما می دهد:

export const { selectIds, selectEntities, selectAll, selectTotal } = 
  adapter.getSelectors(createFeatureSelector<UserState>('users'));

// Extended selectors
export const selectSelectedUserId = createSelector(
  createFeatureSelector<UserState>('users'),
  (state) => state.selectedUserId
);

export const selectSelectedUser = createSelector(
  selectEntities,
  selectSelectedUserId,
  (entities, selectedId) => selectedId ? entities[selectedId] : null
);

export const selectActiveUsers = createSelector(
  selectAll,
  (users) => users.filter(user => user.active)
);
حالت تمام صفحه را وارد کنید

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

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

اشکال زدایی و عیب یابی برنامه های NGRX

هنگامی که همه چیز اشتباه می شود (و آنها خواهند کرد) ، شما نیاز به دید در وضعیت خود دارید.

ادغام redux devtools

پسوند redux devtools یک چیز مطلق است:

@NgModule({
  imports: [
    StoreModule.forRoot(reducers),
    StoreDevtoolsModule.instrument({
      maxAge: 25, // Retains last 25 states
      logOnly: environment.production, 
      autoPause: true, // Pauses recording actions when the browser tab is not active
      trace: !environment.production, // Include stack trace for dispatched actions
      traceLimit: 75, // Maximum stack trace frames to be stored
      connectOutsideZone: true // Connect outside Angular's zone for better performance
    })
  ]
})
export class AppModule {}
حالت تمام صفحه را وارد کنید

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

این باعث می شود ساعتهای بی شماری از اشکال زدایی شما را نجات دهد. قادر به سفر از طریق تغییرات دولتی مانند داشتن یک ابرقدرت هنگام ردیابی اشکالات است.

متا کاهش دهنده های سفارشی برای ورود به سیستم و اشکال زدایی

برای مواقعی که به دید اضافی نیاز دارید ، متا کاهش دهنده ها می توانند سلاح مخفی شما باشند:

export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
  return function(state, action) {
    const nextState = reducer(state, action);

    console.group(action.type);
    console.log(`%c prev state`, 'color: #9E9E9E; font-weight: bold', state);
    console.log(`%c action`, 'color: #03A9F4; font-weight: bold', action);
    console.log(`%c next state`, 'color: #4CAF50; font-weight: bold', nextState);
    console.groupEnd();

    return nextState;
  };
}

export const metaReducers: MetaReducer<State>[] = !environment.production ? [debug] : [];

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, { metaReducers })
  ]
})
export class AppModule {}
حالت تمام صفحه را وارد کنید

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

من با دیدن دقیقاً آنچه بین دولتها تغییر کرده است ، بسیاری از اشکالات پیچیده را حل کرده ام. فقط به یاد داشته باشید که این را در تولید غیرفعال کنید!

گسترش معماری دولتی با رشد برنامه ها

الگوی ترکیب حالت

با رشد تیم شما ، می خواهید وضعیت خود را به تکه های قابل کنترل تقسیم کنید:

// Root state interface
export interface AppState {
  router: RouterReducerState<any>;
  auth: AuthState;
}

// Extended with lazy-loaded feature states
export interface State extends AppState {
  users?: UserState;
  dashboard?: DashboardState;
  reports?: ReportState;
}
حالت تمام صفحه را وارد کنید

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

فدراسیون ماژول پویا با NGRX

برای کاربردهای بزرگ ، از روش پیش بینی NGRX در ماژول های تنبل استفاده می کنید:

@NgModule({
  imports: [
    CommonModule,
    StoreModule.forFeature('users', userReducer),
    EffectsModule.forFeature([UserEffects]),
    // ...other module imports
  ],
  declarations: [
    UserListComponent,
    UserDetailComponent,
    // ...other components
  ]
})
export class UserModule {}
حالت تمام صفحه را وارد کنید

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

من این رویکرد را در تیم های بزرگ ارزشمند پیدا کردم. هر تیم ویژگی می تواند در حالی که هنوز با فروشگاه جهانی خوب بازی می کند ، دارای بخش دولتی خود باشد.

عادی سازی حالت برای روابط داده پیچیده

اگر داده های شما روابط پیچیده ای دارند ، عادی سازی باعث سردرد شما می شود:

// Instead of nested data:
const badlyNestedState = {
  departments: [
    {
      id: 'dept1',
      name: 'Engineering',
      employees: [
        { id: 'emp1', name: 'Alice', projects: [...] },
        { id: 'emp2', name: 'Bob', projects: [...] }
      ]
    }
  ]
};

// Use normalized state with references:
const normalizedState = {
  departments: {
    ids: ['dept1', 'dept2'],
    entities: {
      'dept1': { id: 'dept1', name: 'Engineering', employeeIds: ['emp1', 'emp2'] },
      'dept2': { id: 'dept2', name: 'Marketing', employeeIds: ['emp3', 'emp4'] }
    }
  },
  employees: {
    ids: ['emp1', 'emp2', 'emp3', 'emp4'],
    entities: {
      'emp1': { id: 'emp1', name: 'Alice', projectIds: ['proj1', 'proj2'] },
      'emp2': { id: 'emp2', name: 'Bob', projectIds: ['proj1'] },
      // ...other employees
    }
  },
  projects: {
    ids: ['proj1', 'proj2', 'proj3'],
    entities: {
      'proj1': { id: 'proj1', name: 'Dashboard Redesign' },
      // ...other projects
    }
  }
};
حالت تمام صفحه را وارد کنید

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

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

نتیجه گیری: جمع کردن همه اینها

در طول این سری سه قسمتی ، ما از اصول اولیه NGRX به این الگوهای پیشرفته رفته ایم که به مقیاس برنامه های زاویه ای سازمانی شما کمک می کند. من از این تکنیک ها در برنامه های کاربردی با صدها مؤلفه و ده ها توسعه دهنده استفاده کرده ام و آنها برای کنترل پیچیدگی بسیار مهم بوده اند.

قدرت واقعی زمانی حاصل می شود که این الگوهای را با هم ترکیب می کنید: انتخاب کننده های یاد شده که داده های موجودیت عادی را برای انجام اجزای سازنده تغذیه می کنند ، همه در حالی که امنیت را با حالت AUTH و نگهبانان NGRX حفظ می کنند. این یک چیز زیبا است وقتی همه چیز با هم جمع می شود!

با افزایش برنامه شما ، این غذای اصلی را به یاد داشته باشید:

  1. از انتخاب کنندگان استراتژیک استفاده کنید برای جلوگیری از مجدداً غیر ضروری
  2. منطق احراز هویت را متمرکز کنید در اثرات NGRX برای اطمینان از امنیت مداوم
  3. اهرم @ngrx/نهاد برای تمام نیازهای مدیریت مجموعه شما
  4. ابزارهای اشکال زدایی را پیکربندی کنید برای گرفتن زودهنگام مسائل
  5. حالت خود را تقسیم کنید با رشد برنامه خود به ماژول های ویژگی

در سفر NGRX چه الگوهایی پیدا کرده اید؟ چه ضد الگوی دیده اید؟
من دوست دارم در مورد تجربیات شما در نظرات بشنوم. و اگر این سریال را با ارزش دیدید ، آن را با یک توسعه دهنده زاویه ای دیگر با مدیریت دولت به اشتراک بگذارید!

برنامه نویسی مبارک!

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

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

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

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