diff --git a/libs/angular/src/platform/utils/feature-flagged-route.ts b/libs/angular/src/platform/utils/feature-flagged-route.ts new file mode 100644 index 0000000000..a08e4d86cf --- /dev/null +++ b/libs/angular/src/platform/utils/feature-flagged-route.ts @@ -0,0 +1,53 @@ +import { Type, inject } from "@angular/core"; +import { Route, Routes } from "@angular/router"; +import { map } from "rxjs"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { componentRouteSwap } from "../../utils/component-route-swap"; + +/** + * @param defaultComponent The component to be used when the feature flag is off. + * @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. + */ +type FeatureFlaggedRouteConfig = { + defaultComponent: Type; + flaggedComponent: Type; + featureFlag: FeatureFlag; + routeOptions: Omit; +}; + +/** + * Swap between two routes at runtime based on the value of a feature flag. + * The routes share a common path and configuration but load different components. + * @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 + * const routes: Routes = [ + * ...featureFlaggedRoute({ + * defaultComponent: GroupsComponent, + * flaggedComponent: GroupsNewComponent, + * featureFlag: FeatureFlag.GroupsComponentRefactor, + * routeOptions: { + * path: "groups", + * canActivate: [OrganizationPermissionsGuard], + * }, + * }), + * ] + */ +export function featureFlaggedRoute(config: FeatureFlaggedRouteConfig): Routes { + const canMatch$ = () => + inject(ConfigService) + .getFeatureFlag$(config.featureFlag) + .pipe(map((flagValue) => flagValue === true)); + + return componentRouteSwap( + config.defaultComponent, + config.flaggedComponent, + canMatch$, + config.routeOptions, + ); +} diff --git a/libs/angular/src/utils/component-route-swap.ts b/libs/angular/src/utils/component-route-swap.ts index 1a45671d2b..ab44f49b65 100644 --- a/libs/angular/src/utils/component-route-swap.ts +++ b/libs/angular/src/utils/component-route-swap.ts @@ -1,5 +1,5 @@ import { Type } from "@angular/core"; -import { Route, Routes } from "@angular/router"; +import { CanMatchFn, Route, Routes } from "@angular/router"; /** * Helper function to swap between two components based on an async condition. The async condition is evaluated @@ -32,7 +32,7 @@ import { Route, Routes } from "@angular/router"; export function componentRouteSwap( defaultComponent: Type, altComponent: Type, - shouldSwapFn: () => Promise, + shouldSwapFn: CanMatchFn, options: Route, altOptions?: Route, ): Routes { @@ -46,12 +46,7 @@ export function componentRouteSwap( const altRoute: Route = { ...selectedAltOptions, component: altComponent, - canMatch: [ - async () => { - return await shouldSwapFn(); - }, - ...(selectedAltOptions.canMatch ?? []), - ], + canMatch: [shouldSwapFn, ...(selectedAltOptions.canMatch ?? [])], }; // Return the alternate route first, so it is evaluated first.