Browse Source

fix(admin-ui): Fix support for canDeactivate guard on angular routes

Michael Bromley 1 year ago
parent
commit
6d9af1d0fb

+ 49 - 3
packages/admin-ui/src/lib/core/src/extension/components/angular-route.component.ts

@@ -1,13 +1,59 @@
-import { Component, inject } from '@angular/core';
+import {
+    Component,
+    ComponentRef,
+    EventEmitter,
+    inject,
+    Input,
+    OnInit,
+    Output,
+    ViewContainerRef,
+} from '@angular/core';
 import { SharedModule } from '../../shared/shared.module';
 import { ROUTE_COMPONENT_OPTIONS, RouteComponent } from './route.component';
 
+/**
+ * @description
+ * This component is used internally to allow us to dynamically load a component
+ * like with `*ngComponentOutlet`, but with the ability to get a reference to the
+ * created ComponentRef. This can then be used to delegate lifecycle events like
+ * `canDeactivate` to the loaded component.
+ */
+@Component({
+    selector: 'vdr-dynamic-component-loader',
+    template: ``,
+    standalone: true,
+    imports: [SharedModule],
+})
+export class DynamicComponentLoaderComponent implements OnInit {
+    @Input() componentType: any;
+    @Output() loaded = new EventEmitter<ComponentRef<any>>();
+    constructor(private viewContainer: ViewContainerRef) {}
+
+    ngOnInit() {
+        const componentRef = this.viewContainer.createComponent(this.componentType);
+        this.loaded.emit(componentRef);
+    }
+}
+
 @Component({
     selector: 'vdr-angular-route-component',
-    template: ` <vdr-route-component><ng-container *ngComponentOutlet="component" /></vdr-route-component> `,
+    template: `
+        <vdr-route-component>
+            <vdr-dynamic-component-loader [componentType]="component" (loaded)="componentLoaded($event)" />
+        </vdr-route-component>
+    `,
     standalone: true,
-    imports: [SharedModule, RouteComponent],
+    imports: [SharedModule, RouteComponent, DynamicComponentLoaderComponent],
 })
 export class AngularRouteComponent {
     protected component = inject(ROUTE_COMPONENT_OPTIONS).component;
+    protected componentRef: ComponentRef<any>;
+
+    componentLoaded(componentRef: ComponentRef<any>) {
+        this.componentRef = componentRef;
+    }
+
+    canDeactivate() {
+        return this.componentRef?.instance?.canDeactivate?.();
+    }
 }

+ 4 - 10
packages/admin-ui/src/lib/core/src/shared/providers/routing/can-deactivate-detail-guard.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@angular/core';
-import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
@@ -8,16 +7,11 @@ import { DeactivateAware } from '../../../common/deactivate-aware';
 import { ModalService } from '../../../providers/modal/modal.service';
 
 @Injectable()
-export class CanDeactivateDetailGuard  {
-    constructor(private modalService: ModalService, private router: Router) {}
+export class CanDeactivateDetailGuard {
+    constructor(private modalService: ModalService) {}
 
-    canDeactivate(
-        component: DeactivateAware,
-        currentRoute: ActivatedRouteSnapshot,
-        currentState: RouterStateSnapshot,
-        nextState?: RouterStateSnapshot,
-    ): boolean | Observable<boolean> {
-        if (!component.canDeactivate()) {
+    canDeactivate(component: DeactivateAware): boolean | Observable<boolean> {
+        if (typeof component.canDeactivate === 'function' && !component.canDeactivate()) {
             return this.modalService
                 .dialog({
                     title: _('common.confirm-navigation'),

+ 4 - 0
packages/dev-server/example-plugins/ui-extensions-library/ui/angular-components/angular-ui/angular-ui.component.ts

@@ -32,6 +32,10 @@ export class AngularUiComponent {
         private pageMetadataService: PageMetadataService,
     ) {}
 
+    canDeactivate() {
+        return this.pageTitleControl.pristine;
+    }
+
     updateTitle() {
         const title = this.pageTitleControl.value;
         if (title) {

+ 4 - 1
packages/dev-server/example-plugins/ui-extensions-library/ui/routes.ts

@@ -1,4 +1,4 @@
-import { registerRouteComponent } from '@vendure/admin-ui/core';
+import { CanDeactivateDetailGuard, registerRouteComponent } from '@vendure/admin-ui/core';
 import { registerReactRouteComponent } from '@vendure/admin-ui/react';
 
 import { AngularUiComponent } from './angular-components/angular-ui/angular-ui.component';
@@ -14,5 +14,8 @@ export default [
         path: 'angular-ui',
         component: AngularUiComponent,
         title: 'Angular UI',
+        routeConfig: {
+            canDeactivate: [CanDeactivateDetailGuard],
+        },
     }),
 ];