Skip to content

Commit

Permalink
refactor(Angular 15): replace class-based route guards by functional …
Browse files Browse the repository at this point in the history
…guards

BREAKING CHANGE: Replaced class-based route guards by functional guards (see [Migrations / 3.3 to 4.0](https://github.com/intershop/intershop-pwa/blob/develop/docs/guides/migrations.md#33-to-40) for more details).
  • Loading branch information
SGrueber authored and shauke committed Mar 29, 2023
1 parent 292d559 commit 289e161
Show file tree
Hide file tree
Showing 56 changed files with 504 additions and 520 deletions.
4 changes: 2 additions & 2 deletions docs/concepts/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,13 @@ const routes: Routes = [
{
path: 'quote',
loadChildren: ...,
canActivate: [FeatureToggleGuard],
canActivate: [featureToggleGuard],
data: { feature: 'quoting' },
},
...
```
Add the Guard as `CanActivate` to the routing definition.
Add the Guard as `canActivate` to the routing definition.
Additionally, you have to supply a `data` field called `feature`, containing a string that determines for which feature the route should be active.
If the feature is deactivated, the user is sent to the error page on accessing.
Expand Down
26 changes: 26 additions & 0 deletions docs/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@ A factory function is provided in the [`internationalization.module.ts`](https:/

Please adapt the `useFactory()` function to return all imported local translation files depending on the `lang` parameter.

With Angular 15 class-based route guards are deprecated in favor of functional guards.
That's why we removed the guard classes and replace them by functions.
For the `canActivate/canChildActivate` methods only change to class name into the function name by lowercasing the first letter, e.g.

```typescript
{
path: 'organization-management',
...
canActivate: [AuthGuard],
canActivateChild: [AuthGuard],
}
```

will become

```typescript
{
path: 'organization-management',
...
canActivate: [authGuard],
canActivateChild: [authGuard],
},
```

Find more information about functional guards in this [blog article](https://blog.angular.io/advancements-in-the-angular-router-5d69ec4c032).

The account navigation was reworked to support navigation grouping (used in `b2b` theme, see [`account-navigation.items.ts`](https://github.com/intershop/intershop-pwa/blob/4.0.0/src/app/pages/account/account-navigation/account-navigation.items.ts)).
For better maintainability and brand specific overriding the account navigation items were externalized in an extra file `account-navigation.items.ts` used by the `account-navigation.component.ts`.
Also with this rework the navigation items data structure was changed from a key value object to a simpler `NavigationItem` Array.
Expand Down
10 changes: 5 additions & 5 deletions projects/organization-management/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';

import { CoreModule } from 'ish-core/core.module';
import { AuthGuard } from 'ish-core/guards/auth.guard';
import { IdentityProviderLogoutGuard } from 'ish-core/guards/identity-provider-logout.guard';
import { authGuard } from 'ish-core/guards/auth.guard';
import { identityProviderLogoutGuard } from 'ish-core/guards/identity-provider-logout.guard';
import { SharedModule } from 'ish-shared/shared.module';

import { AppComponent } from './app.component';
Expand All @@ -27,15 +27,15 @@ import { LoginComponent } from './login.component';
},
{
path: 'logout',
canActivate: [IdentityProviderLogoutGuard],
canActivate: [identityProviderLogoutGuard],
component: LoginComponent,
},
{
path: 'organization-management',
loadChildren: () =>
import('./app/pages/organization-management-routing.module').then(m => m.OrganizationManagementRoutingModule),
canActivate: [AuthGuard],
canActivateChild: [AuthGuard],
canActivate: [authGuard],
canActivateChild: [authGuard],
},
{
path: '**',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
import { inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable, of } from 'rxjs';

import { loadCostCenters } from '../store/cost-centers';

@Injectable({ providedIn: 'root' })
export class FetchCostCentersGuard implements CanActivate {
constructor(private store: Store) {}
/**
* Fetch cost centers for cost center management page
*/
export function fetchCostCentersGuard(): boolean | Observable<boolean> {
const store = inject(Store);

canActivate(_: ActivatedRouteSnapshot): boolean | Observable<boolean> {
this.store.dispatch(loadCostCenters());
return of(true);
}
store.dispatch(loadCostCenters());
return of(true);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';

import { getUserCount, loadUsers } from '../store/users';

@Injectable({ providedIn: 'root' })
export class FetchUsersGuard implements CanActivate {
constructor(private store: Store) {}
/**
* Fetch users for user management page
*/
export function fetchUsersGuard(route: ActivatedRouteSnapshot): boolean | Observable<boolean> {
const store = inject(Store);

canActivate(route: ActivatedRouteSnapshot): boolean | Observable<boolean> {
return this.store.pipe(
select(getUserCount),
tap(count => {
if (count <= 1 || !route.data.onlyInitialUsers) {
this.store.dispatch(loadUsers());
}
}),
map(() => true)
);
}
return store.pipe(
select(getUserCount),
tap(count => {
if (count <= 1 || !route.data.onlyInitialUsers) {
store.dispatch(loadUsers());
}
}),
map(() => true)
);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class RedirectFirstToParentGuard implements CanActivate {
constructor(private router: Router) {}
/**
* Redirects the user to the parent page if the requested page is the starting page (first page the user requested)
*/
export function redirectFirstToParentGuard(_: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
const router = inject(Router);

canActivate(_: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
if (!this.router.navigated) {
return this.router.parseUrl(state.url.replace(/\/\w+$/, ''));
}
return true;
if (!router.navigated) {
return router.parseUrl(state.url.replace(/\/\w+$/, ''));
}
return true;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AuthorizationToggleGuard } from 'ish-core/authorization-toggle.module';
import { FeatureToggleGuard } from 'ish-core/feature-toggle.module';
import { authorizationToggleGuard } from 'ish-core/authorization-toggle.module';
import { featureToggleGuard } from 'ish-core/feature-toggle.module';

import { FetchCostCentersGuard } from '../guards/fetch-cost-centers.guard';
import { FetchUsersGuard } from '../guards/fetch-users.guard';
import { RedirectFirstToParentGuard } from '../guards/redirect-first-to-parent.guard';
import { fetchCostCentersGuard } from '../guards/fetch-cost-centers.guard';
import { fetchUsersGuard } from '../guards/fetch-users.guard';
import { redirectFirstToParentGuard } from '../guards/redirect-first-to-parent.guard';

/**
* routes for the organization management
Expand All @@ -19,18 +19,18 @@ export const routes: Routes = [
path: 'users',
loadChildren: () => import('./users/users-page.module').then(m => m.UsersPageModule),
data: { permission: 'APP_B2B_MANAGE_USERS' },
canActivate: [FetchUsersGuard, AuthorizationToggleGuard],
canActivate: [fetchUsersGuard, authorizationToggleGuard],
},
{
path: 'users/create',
loadChildren: () => import('./user-create/user-create-page.module').then(m => m.UserCreatePageModule),
data: { permission: 'APP_B2B_MANAGE_USERS' },
canActivate: [RedirectFirstToParentGuard, AuthorizationToggleGuard],
canActivate: [redirectFirstToParentGuard, authorizationToggleGuard],
},
{
path: 'users/:B2BCustomerLogin',
loadChildren: () => import('./user-detail/user-detail-page.module').then(m => m.UserDetailPageModule),
canActivate: [FetchUsersGuard, AuthorizationToggleGuard],
canActivate: [fetchUsersGuard, authorizationToggleGuard],
data: {
onlyInitialUsers: true,
permission: 'APP_B2B_MANAGE_USERS',
Expand All @@ -40,32 +40,32 @@ export const routes: Routes = [
path: 'users/:B2BCustomerLogin/profile',
loadChildren: () =>
import('./user-edit-profile/user-edit-profile-page.module').then(m => m.UserEditProfilePageModule),
canActivate: [RedirectFirstToParentGuard, AuthorizationToggleGuard],
canActivate: [redirectFirstToParentGuard, authorizationToggleGuard],
data: { permission: 'APP_B2B_MANAGE_USERS' },
},
{
path: 'users/:B2BCustomerLogin/roles',
loadChildren: () => import('./user-edit-roles/user-edit-roles-page.module').then(m => m.UserEditRolesPageModule),
canActivate: [RedirectFirstToParentGuard, AuthorizationToggleGuard],
canActivate: [redirectFirstToParentGuard, authorizationToggleGuard],
data: { permission: 'APP_B2B_MANAGE_USERS' },
},
{
path: 'users/:B2BCustomerLogin/budget',
loadChildren: () => import('./user-edit-budget/user-edit-budget-page.module').then(m => m.UserEditBudgetPageModule),
canActivate: [RedirectFirstToParentGuard, AuthorizationToggleGuard],
canActivate: [redirectFirstToParentGuard, authorizationToggleGuard],
data: { permission: 'APP_B2B_MANAGE_USERS' },
},
{
path: 'cost-centers',
canActivate: [FeatureToggleGuard, FetchCostCentersGuard],
canActivate: [featureToggleGuard, fetchCostCentersGuard],
data: { feature: 'costCenters' },
loadChildren: () => import('./cost-centers/cost-centers-page.module').then(m => m.CostCentersPageModule),
},
{
path: 'cost-centers/create',
loadChildren: () =>
import('./cost-center-create/cost-center-create-page.module').then(m => m.CostCenterCreatePageModule),
canActivate: [RedirectFirstToParentGuard],
canActivate: [redirectFirstToParentGuard],
},
{
path: 'cost-centers/:CostCenterId',
Expand All @@ -75,13 +75,13 @@ export const routes: Routes = [
{
path: 'cost-centers/:CostCenterId/edit',
loadChildren: () => import('./cost-center-edit/cost-center-edit-page.module').then(m => m.CostCenterEditPageModule),
canActivate: [RedirectFirstToParentGuard],
canActivate: [redirectFirstToParentGuard],
},
{
path: 'cost-centers/:CostCenterId/buyers',
loadChildren: () =>
import('./cost-center-buyers/cost-center-buyers-page.module').then(m => m.CostCenterBuyersPageModule),
canActivate: [RedirectFirstToParentGuard, FetchUsersGuard],
canActivate: [redirectFirstToParentGuard, fetchUsersGuard],
},
];

Expand Down
10 changes: 5 additions & 5 deletions projects/requisition-management/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';

import { CoreModule } from 'ish-core/core.module';
import { AuthGuard } from 'ish-core/guards/auth.guard';
import { IdentityProviderLogoutGuard } from 'ish-core/guards/identity-provider-logout.guard';
import { authGuard } from 'ish-core/guards/auth.guard';
import { identityProviderLogoutGuard } from 'ish-core/guards/identity-provider-logout.guard';
import { SharedModule } from 'ish-shared/shared.module';

import { AppComponent } from './app.component';
Expand All @@ -27,15 +27,15 @@ import { LoginComponent } from './login.component';
},
{
path: 'logout',
canActivate: [IdentityProviderLogoutGuard],
canActivate: [identityProviderLogoutGuard],
component: LoginComponent,
},
{
path: 'requisition-management',
loadChildren: () =>
import('./app/pages/requisition-management-routing.module').then(m => m.RequisitionManagementRoutingModule),
canActivate: [AuthGuard],
canActivateChild: [AuthGuard],
canActivate: [authGuard],
canActivateChild: [authGuard],
},
{
path: '**',
Expand Down
4 changes: 2 additions & 2 deletions schematics/src/page/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function addRouteToArray(
const path = options.child ? options.child : options.lazy ? dasherizedName : dasherizedName.replace(/-/g, '/');

const guard = options.extension
? `, canActivate: [FeatureToggleGuard], data: { feature: '${strings.camelize(options.extension)}' }`
? `, canActivate: [featureToggleGuard], data: { feature: '${strings.camelize(options.extension)}' }`
: '';

if (options.lazy) {
Expand Down Expand Up @@ -172,7 +172,7 @@ export function createPage(options: Options): Rule {
operations.push(
addImportToFile({
module: options.routingModule,
artifactName: 'FeatureToggleGuard',
artifactName: 'featureToggleGuard',
moduleImportPath: '/src/app/core/feature-toggle.module',
})
);
Expand Down
4 changes: 2 additions & 2 deletions schematics/src/page/factory_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ describe('Page Schematic', () => {
const tree = await schematicRunner.runSchematic('page', options, appTree);
const appRoutingModule = tree.readContent('/src/app/extensions/feature/pages/feature-routing.module.ts');
expect(appRoutingModule).toContain(
`{ path: 'foo', component: FooPageComponent, canActivate: [FeatureToggleGuard], data: { feature: 'feature' } }`
`{ path: 'foo', component: FooPageComponent, canActivate: [featureToggleGuard], data: { feature: 'feature' } }`
);
expect(appRoutingModule).toContain("import { FooPageComponent } from './foo/foo-page.component'");
});
Expand All @@ -266,7 +266,7 @@ describe('Page Schematic', () => {
const tree = await schematicRunner.runSchematic('page', options, appTree);
const appRoutingModule = tree.readContent('/src/app/extensions/feature2/pages/feature2-routing.module.ts');
expect(appRoutingModule).toContain(
`{ path: 'foo', component: FooPageComponent, canActivate: [FeatureToggleGuard], data: { feature: 'feature2' } }`
`{ path: 'foo', component: FooPageComponent, canActivate: [featureToggleGuard], data: { feature: 'feature2' } }`
);
expect(appRoutingModule).toContain("import { FooPageComponent } from './foo/foo-page.component'");
});
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/authorization-toggle.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ export class AuthorizationToggleModule {
}

export { AuthorizationToggleService } from './utils/authorization-toggle/authorization-toggle.service';
export { AuthorizationToggleGuard } from './guards/authorization-toggle.guard';
export { authorizationToggleGuard } from './guards/authorization-toggle.guard';
2 changes: 1 addition & 1 deletion src/app/core/feature-toggle.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ export class FeatureToggleModule {
}

export { FeatureToggleService } from './utils/feature-toggle/feature-toggle.service';
export { FeatureToggleGuard } from './guards/feature-toggle.guard';
export { featureToggleGuard } from './guards/feature-toggle.guard';
Loading

0 comments on commit 289e161

Please sign in to comment.