diff --git a/libs/angular/src/platform/utils/feature-flagged-route.ts b/libs/angular/src/platform/utils/feature-flagged-route.ts index a08e4d86cf31..6a496f85143f 100644 --- a/libs/angular/src/platform/utils/feature-flagged-route.ts +++ b/libs/angular/src/platform/utils/feature-flagged-route.ts @@ -12,12 +12,18 @@ import { componentRouteSwap } from "../../utils/component-route-swap"; * @param flaggedComponent The component to be used when the feature flag is on. * @param featureFlag The feature flag to evaluate * @param routeOptions The shared route options to apply to both components. + * @param flaggedRouteProviders Optional providers scoped only to the flagged route. Use this to + * register services that should only be instantiated when the feature flag is on. + * @param defaultRouteProviders Optional providers scoped only to the default route. Use this to + * register services that should only be instantiated when the feature flag is off. */ type FeatureFlaggedRouteConfig = { defaultComponent: Type; flaggedComponent: Type; featureFlag: FeatureFlag; routeOptions: Omit; + flaggedRouteProviders?: NonNullable; + defaultRouteProviders?: NonNullable; }; /** @@ -26,6 +32,7 @@ type FeatureFlaggedRouteConfig = { * @param config See {@link FeatureFlaggedRouteConfig} * @returns A tuple containing the conditional configuration for the two routes. This should be unpacked into your existing Routes array. * @example + * // Basic usage — shared route options, no scoped providers: * const routes: Routes = [ * ...featureFlaggedRoute({ * defaultComponent: GroupsComponent, @@ -37,6 +44,28 @@ type FeatureFlaggedRouteConfig = { * }, * }), * ] + * + * @example + * // Scoped providers — each route only instantiates the services it needs: + * const routes: Routes = [ + * ...featureFlaggedRoute({ + * defaultComponent: GroupsComponent, + * flaggedComponent: GroupsNewComponent, + * featureFlag: FeatureFlag.GroupsComponentRefactor, + * routeOptions: { + * path: "groups", + * canActivate: [OrganizationPermissionsGuard], + * }, + * defaultRouteProviders: [ + * // Only instantiated when the feature flag is OFF + * safeProvider({ provide: LegacyGroupsService, useClass: LegacyGroupsService, deps: [...] }), + * ], + * flaggedRouteProviders: [ + * // Only instantiated when the feature flag is ON + * safeProvider({ provide: GroupsService, useClass: GroupsService, deps: [...] }), + * ], + * }), + * ] */ export function featureFlaggedRoute(config: FeatureFlaggedRouteConfig): Routes { const canMatch$ = () => @@ -44,10 +73,25 @@ export function featureFlaggedRoute(config: FeatureFlaggedRouteConfig): Routes { .getFeatureFlag$(config.featureFlag) .pipe(map((flagValue) => flagValue === true)); + const defaultRouteOptions = config.defaultRouteProviders + ? { ...config.routeOptions, providers: config.defaultRouteProviders } + : config.routeOptions; + + // When defaultRouteProviders is set, defaultRouteOptions carries those providers as part of + // `options` passed to componentRouteSwap. Without an explicit flaggedRouteOptions, componentRouteSwap + // would fall back to `options` for the flagged route, unintentionally inheriting the default + // route's providers. Passing config.routeOptions directly avoids that. + const flaggedRouteOptions: Route | undefined = config.flaggedRouteProviders + ? { ...config.routeOptions, providers: config.flaggedRouteProviders } + : config.defaultRouteProviders + ? config.routeOptions + : undefined; + return componentRouteSwap( config.defaultComponent, config.flaggedComponent, canMatch$, - config.routeOptions, + defaultRouteOptions, + flaggedRouteOptions, ); }