CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ngrx--store

RxJS powered Redux state management for Angular applications

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

standalone-providers.mddocs/

Standalone Providers

Standalone providers enable modern Angular standalone application configuration using environment providers for root and feature state management without traditional NgModules.

Capabilities

Provide Store

Configures the root store for standalone Angular applications using environment providers.

/**
 * Provides root store configuration for standalone applications
 * @param reducers - Map of reducers or injection token for reducers
 * @param config - Root store configuration options
 * @returns EnvironmentProviders for root store setup
 */
function provideStore<T, V extends Action = Action>(
  reducers?: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,
  config?: RootStoreConfig<T, V>
): EnvironmentProviders;

Usage Examples:

import { bootstrapApplication } from "@angular/platform-browser";
import { provideStore } from "@ngrx/store";
import { AppComponent } from "./app.component";
import { reducers, metaReducers } from "./app.reducers";

// Bootstrap standalone application with store
bootstrapApplication(AppComponent, {
  providers: [
    provideStore(reducers, {
      metaReducers,
      runtimeChecks: {
        strictStateImmutability: true,
        strictActionImmutability: true,
        strictStateSerializability: true,
        strictActionSerializability: true,
        strictActionWithinNgZone: true,
        strictActionTypeUniqueness: true
      }
    }),
    // other providers
  ]
});

// Alternative with injection token
export const ROOT_REDUCERS = new InjectionToken<ActionReducerMap<AppState>>('Root Reducers');

bootstrapApplication(AppComponent, {
  providers: [
    { provide: ROOT_REDUCERS, useValue: reducers },
    provideStore(ROOT_REDUCERS, { metaReducers }),
  ]
});

Provide State

Provides feature state configuration for standalone applications and route-based lazy loading.

/**
 * Provides feature state with reducer map
 * @param featureName - Name/key for the feature state slice
 * @param reducers - Feature reducers map
 * @param config - Feature store configuration options
 * @returns EnvironmentProviders for feature state
 */
function provideState<T, V extends Action = Action>(
  featureName: string,
  reducers: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,
  config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>
): EnvironmentProviders;

/**
 * Provides feature state with single reducer
 * @param featureName - Name/key for the feature state slice
 * @param reducer - Single reducer function
 * @param config - Feature store configuration options
 * @returns EnvironmentProviders for feature state
 */
function provideState<T, V extends Action = Action>(
  featureName: string,
  reducer: ActionReducer<T, V> | InjectionToken<ActionReducer<T, V>>,
  config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>
): EnvironmentProviders;

/**
 * Provides feature state using feature slice configuration
 * @param slice - Complete feature slice configuration
 * @returns EnvironmentProviders for feature state
 */
function provideState<T, V extends Action = Action>(
  slice: FeatureSlice<T, V>
): EnvironmentProviders;

Usage Examples:

import { provideState } from "@ngrx/store";
import { userReducer } from "./user.reducer";
import { orderReducer, orderMetaReducer } from "./order.reducer";

// Route-based feature providers
export const userRoutes: Route[] = [
  {
    path: '',
    component: UserListComponent,
    providers: [
      // Single reducer feature
      provideState('users', userReducer, {
        initialState: { entities: [], loading: false, error: null }
      })
    ]
  }
];

export const orderRoutes: Route[] = [
  {
    path: '',
    component: OrderDashboardComponent,
    providers: [
      // Reducer map feature
      provideState('orders', {
        list: orderListReducer,
        details: orderDetailsReducer,
        filters: orderFiltersReducer
      }, {
        metaReducers: [orderMetaReducer],
        initialState: {
          list: { entities: [], pagination: null },
          details: null,
          filters: { status: 'all', dateRange: null }
        }
      })
    ]
  }
];

// Feature slice configuration
export const productRoutes: Route[] = [
  {
    path: '',
    component: ProductCatalogComponent,
    providers: [
      provideState({
        name: 'products',
        reducer: productReducer,
        initialState: initialProductState,
        metaReducers: [productCacheMetaReducer]
      })
    ]
  }
];

Application Configuration Patterns

Main Application Setup

import { ApplicationConfig } from "@angular/core";
import { provideRouter } from "@angular/router";
import { provideStore } from "@ngrx/store";
import { routes } from "./app.routes";
import { reducers, metaReducers } from "./store";

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideStore(reducers, { 
      metaReducers,
      runtimeChecks: {
        strictStateImmutability: true,
        strictActionImmutability: true,
        strictActionWithinNgZone: true
      }
    }),
    // Additional providers
    provideHttpClient(),
    provideAnimations()
  ]
};

// Bootstrap application
import { bootstrapApplication } from "@angular/platform-browser";
import { AppComponent } from "./app.component";

bootstrapApplication(AppComponent, appConfig);

Route-Based Lazy Loading

import { Routes } from "@angular/router";
import { provideState } from "@ngrx/store";

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component').then(c => c.DashboardComponent),
    providers: [
      provideState('dashboard', dashboardReducer, {
        initialState: () => ({
          widgets: getDefaultWidgets(),
          layout: getUserLayout(),
          preferences: getUserDashboardPreferences()
        })
      })
    ]
  },
  {
    path: 'analytics',
    loadChildren: () => import('./analytics/analytics.routes').then(r => r.analyticsRoutes),
    providers: [
      // Shared analytics state
      provideState('analytics', analyticsReducer)
    ]
  },
  {
    path: 'settings',
    loadComponent: () => import('./settings/settings.component').then(c => c.SettingsComponent),
    providers: [
      provideState({
        name: 'settings',
        reducer: settingsReducer,
        initialState: defaultSettings,
        metaReducers: [settingsValidationMetaReducer]
      })
    ]
  }
];

Feature-Specific Routes

// analytics.routes.ts
import { Routes } from "@angular/router";
import { provideState } from "@ngrx/store";

export const analyticsRoutes: Routes = [
  {
    path: '',
    component: AnalyticsOverviewComponent
  },
  {
    path: 'reports',
    component: ReportsComponent,
    providers: [
      // Feature-specific state for reports
      provideState('reports', reportsReducer, {
        initialState: { 
          templates: [],
          generated: [],
          filters: defaultReportFilters
        }
      })
    ]
  },
  {
    path: 'metrics',
    component: MetricsComponent,
    providers: [
      // Metrics-specific state
      provideState('metrics', {
        realtime: realtimeMetricsReducer,
        historical: historicalMetricsReducer,
        alerts: alertsReducer
      })
    ]
  }
];

Advanced Configuration

Injectable Configuration

import { InjectionToken, inject } from "@angular/core";
import { UserService } from "./user.service";

// Injectable configuration tokens
export const USER_INITIAL_STATE = new InjectionToken<UserState>('User Initial State', {
  providedIn: 'root',
  factory: () => {
    const userService = inject(UserService);
    return {
      profile: userService.getCachedProfile(),
      preferences: userService.getStoredPreferences(),
      permissions: userService.getPermissions()
    };
  }
});

export const USER_META_REDUCERS = new InjectionToken<MetaReducer<UserState>[]>('User Meta Reducers', {
  providedIn: 'root',
  factory: () => [
    userAuditMetaReducer,
    userValidationMetaReducer
  ]
});

// Route using injectable configuration
export const userRoutes: Routes = [
  {
    path: '',
    component: UserProfileComponent,
    providers: [
      provideState('user', userReducer, {
        initialState: USER_INITIAL_STATE,
        metaReducers: USER_META_REDUCERS
      })
    ]
  }
];

Dynamic State Registration

import { inject } from "@angular/core";
import { Store } from "@ngrx/store";

// Service for dynamic state management
export class DynamicStateService {
  private store = inject(Store);
  
  registerFeature<T>(name: string, reducer: ActionReducer<T>, initialState?: T) {
    // Add reducer dynamically
    this.store.addReducer(name, reducer);
    
    // Initialize state if provided
    if (initialState) {
      this.store.dispatch({ type: `[${name}] Initialize`, payload: initialState });
    }
  }
  
  unregisterFeature(name: string) {
    this.store.removeReducer(name);
  }
}

// Component using dynamic state
@Component({
  selector: 'app-dynamic-feature',
  providers: [DynamicStateService]
})
export class DynamicFeatureComponent {
  private dynamicState = inject(DynamicStateService);
  
  ngOnInit() {
    // Register state when component initializes
    this.dynamicState.registerFeature('temporaryFeature', temporaryReducer, {
      data: [],
      loading: false
    });
  }
  
  ngOnDestroy() {
    // Clean up when component is destroyed
    this.dynamicState.unregisterFeature('temporaryFeature');
  }
}

Environment-Based Configuration

// environment.ts
export const environment = {
  production: false,
  storeConfig: {
    runtimeChecks: {
      strictStateImmutability: true,
      strictActionImmutability: true,
      strictStateSerializability: true,
      strictActionSerializability: true,
      strictActionWithinNgZone: true,
      strictActionTypeUniqueness: true
    },
    metaReducers: ['logger', 'freeze']
  }
};

// environment.prod.ts
export const environment = {
  production: true,
  storeConfig: {
    runtimeChecks: {
      strictStateImmutability: false,
      strictActionImmutability: false,
      strictStateSerializability: false,
      strictActionSerializability: false,
      strictActionWithinNgZone: false,
      strictActionTypeUniqueness: false
    },
    metaReducers: []
  }
};

// app.config.ts
import { environment } from "../environments/environment";

const getMetaReducers = (): MetaReducer<AppState>[] => {
  const reducers: MetaReducer<AppState>[] = [];
  
  if (environment.storeConfig.metaReducers.includes('logger')) {
    reducers.push(storeLogger);
  }
  
  if (environment.storeConfig.metaReducers.includes('freeze')) {
    reducers.push(storeFreeze);
  }
  
  return reducers;
};

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore(reducers, {
      runtimeChecks: environment.storeConfig.runtimeChecks,
      metaReducers: getMetaReducers()
    })
  ]
};

Integration with Angular Features

HTTP Client Integration

import { provideHttpClient, withInterceptors } from "@angular/common/http";
import { provideStore } from "@ngrx/store";

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
    provideStore(reducers, { metaReducers }),
    // State that depends on HTTP client
    provideState('auth', authReducer, {
      initialState: () => ({
        user: getStoredUser(),
        token: getStoredToken(),
        refreshToken: getStoredRefreshToken()
      })
    })
  ]
};

Router Integration

import { provideRouter, withPreloading, PreloadAllModules } from "@angular/router";
import { provideStore, provideState } from "@ngrx/store";

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withPreloading(PreloadAllModules)),
    provideStore(reducers),
    // Router-dependent state
    provideState('navigation', navigationReducer, {
      initialState: {
        currentRoute: null,
        previousRoute: null,
        breadcrumbs: []
      }
    })
  ]
};

Best Practices

  1. Route-Level Providers: Use provideState in route providers for lazy-loaded features
  2. Application-Level Setup: Configure root store in main application config
  3. Feature Isolation: Keep feature state providers close to their consuming components
  4. Dynamic Registration: Use dynamic state registration for truly dynamic features
  5. Environment Configuration: Adapt store configuration based on environment
  6. Injection Tokens: Use injection tokens for complex, service-dependent initial states

docs

action-creators.md

feature-management.md

index.md

module-configuration.md

reducer-creators.md

selectors.md

standalone-providers.md

store-service.md

testing-utilities.md

tile.json