I am using Angular 15 to build an enterprise web application as I have updated the app from Angular 6 to Angular 15 in summer this year. A few days ago a key user reported the following error when opening a specific Angular Material dialog that prevents the dialog from showing:

TypeError: Cannot read properties of undefined (reading 'focusInitialElementWhenReady')
at bt._trapFocus (vendor.68845b091518ea76.js:1:1028636)
at bt._openAnimationDone (vendor.68845b091518ea76.js:1:1487405)
at bt._onAnimationDone (vendor.68845b091518ea76.js:1:1493261)
at vendor.68845b091518ea76.js:1:1494469
at Se (vendor.68845b091518ea76.js:1:1265844)
at b (vendor.68845b091518ea76.js:1:1266012)
at vendor.68845b091518ea76.js:1:1828893
at z.invoke (polyfills.729dcc77ee327c3e.js:1:6654)
at Object.onInvoke (vendor.68845b091518ea76.js:1:1296471)
at z.invoke (polyfills.729dcc77ee327c3e.js:1:6594)

The problem only occurs on a Samsung Galaxy Fold 5 smartphone independent of the used browser and it can only be reproduced on this device.

Below you can see the dialog component. It is opened via the static show(...) method:

export class DocumentPropertiesDialogComponent {
    smShowCheckBoxList: boolean = false;
    viewModelSubject$: ViewModelSubject<DocumentMetaDataViewModel> = new ViewModelSubject<DocumentMetaDataViewModel>();
    readonly documentRights$: Observable<DocumentRightsDTO>;

    static show(dh: DialogHelper, document: DocumentDTO): Observable<MetaDataUpdateDialogResult> {
        return dh.showDialog<DocumentPropertiesDialogComponent, DocumentMetaDataDialogData, MetaDataUpdateDialogResult>
            (DocumentPropertiesDialogComponent, new DocumentMetaDataDialogData(document), {
                maxHeight: '95vh',
                maxWidth: '100vw',
                minHeight: '80vh',
                xsFullscreen: true,
            });
    }

    constructor(public dialogRef: MatDialogRef<DocumentPropertiesDialogComponent>,
        @Inject(MAT_DIALOG_DATA) data: DocumentMetaDataDialogData,
        public modelCache: ModelCache,
        public urlFactory: UrlFactory,
        private _documentMetaDataHelper: DocumentMetaDataHelper,
        public windowService: WindowService,
        private _dirtyMetaDataInputHelper: DirtyMetaDataInputHelper,
        private _documentFunctionService: DocumentFunctionService,
        private readonly documentService: DocumentService
    ) {
        const document = data.document;
        this.smShowCheckBoxList = !document.metaData || (!document.metaData.assignedDocumentClasses || document.metaData.assignedDocumentClasses.length === 0);
        document.initializeDocumentMetaDataHelper(this._documentMetaDataHelper);
        if (data.isUploadDialog && data.document) {
            data.document.hasChanges.next(true);
        }
        this.viewModelSubject$.next(DocumentMetaDataViewModel.createNew(this.modelCache, data.document, data.isUploadDialog, data.documentDepotTypeArchiveAssignments, data.depotTypeID));
        this.documentRights$ = this.documentService.getDocumentRights(document.documentID).pipe(
            shareReplay(1),
        );
    }

    ...
}

The DialogHelper mentioned in the static show(...) method looks like this:

@Injectable()
export class DialogHelper {
    private _dialogNumber: number = 0;

    constructor(public dialogFactory: MatDialog, private ngZone: NgZone, private windowService: WindowService) { }

    get dialogNumber(): number {
        return this._dialogNumber;
    }

    showDialog<T, D = any, R = any>(componentOrTemplateRef: ComponentType<T> | TemplateRef<T>, data?: D, props?: MatDialogConfig<D> | ShowDialogConfig<D>): Observable<R>;
    showDialog<T, D = any, R = any>(componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
        data: D,
        hasBackdrop?: boolean | null,
        panelClass?: string | null,
        disableClose?: boolean | null,
        xsFullScreen?: boolean | null,
        autofocus?: boolean | null,
        matDialogConfig?: MatDialogConfig<D> | undefined | null,
    ): Observable<R>;
    showDialog<T, D = any, R = any>(componentOrTemplateRef: ComponentType<T> | TemplateRef<T>, data: D,
        hasBackdropOrConfig: boolean | MatDialogConfig<D> | ShowDialogConfig<D> | null,
        panelClass: string | null,
        disableClose: boolean | null,
        xsFullScreen: boolean | null,
        autofocus: boolean | null,
        matDialogConfig: MatDialogConfig<D> | null): Observable<R>;
    showDialog<T, D = any, R = any>(componentOrTemplateRef: ComponentType<T> | TemplateRef<T>, data: D = null,
        hasBackdropOrConfig: boolean | MatDialogConfig<D> | ShowDialogConfig<D> | null = true,
        panelClass: string | null = null,
        disableClose: boolean | null = false,
        xsFullScreen: boolean | null = false,
        autofocus: boolean | null = true,
        matDialogConfig: MatDialogConfig<D> | null = null): Observable<R> {
        if (hasBackdropOrConfig instanceof MatDialogConfig) {
            const config: MatDialogConfig<D> = hasBackdropOrConfig;
            config.data = data;
            config.closeOnNavigation = true;
            this._dialogNumber++;
            return this.ngZone.run(() => this.openDialog(componentOrTemplateRef, config));
        }
        else {
            const config = matDialogConfig ? matDialogConfig : new MatDialogConfig<D>();

            if (typeof hasBackdropOrConfig === 'boolean') {
                config.hasBackdrop = hasBackdropOrConfig;

                return this.showDialog<T, D, R>(componentOrTemplateRef, data, config,
                    panelClass, disableClose, xsFullScreen, autofocus, matDialogConfig);
            }
            else {
                Object.assign(config, hasBackdropOrConfig);
            }

            if (!this.windowService.size$.value.xsOrLess || xsFullScreen) {
                config.maxHeight = '100vh';
                config.maxWidth = '100vw';
            }

            return this.showDialog<T, D, R>(componentOrTemplateRef, data, config,
                panelClass, disableClose, xsFullScreen, autofocus, undefined);
        }
    }

    public isDialogOpen(): boolean {
        return this._dialogNumber > 0;
    }

    private openDialog<T, D = any, R = any>(componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
        config: MatDialogConfig<D>): Observable<R> {
        const self = this;
        return this.dialogFactory.open<T, D, R>(componentOrTemplateRef, config)
            .afterClosed()
            .pipe(finalize(() => {
                self._dialogNumber--;
            }));
    }
}

Here are the interesting parts of my dependencies from package.json:

  "dependencies": {
    "@angular/animations": "15.2.9",
    "@angular/cdk": "15.2.9",
    "@angular/common": "15.2.9",
    "@angular/compiler": "15.2.9",
    "@angular/core": "15.2.9",
    "@angular/elements": "15.2.9",
    "@angular/forms": "15.2.9",
    "@angular/material": "15.2.9",
    "@angular/platform-browser": "15.2.9",
    "@angular/platform-browser-dynamic": "15.2.9",
    "@angular/router": "15.2.9",
    "@material-design-icons/font": "0.14.2",
    "@ngx-translate/core": "14.0.0",
    "hammerjs": "2.0.8",
    "ng-animate": "2.0.1",
    "ng-circle-progress": "1.5.1",
    "rxjs": "6.5.4",
    "tslib": "2.3.0",
    "zone.js": "0.11.4",
    ...
  },

I was not able to reproduce the problem in any other way (device emulation in browser dev tools, Android emulator, other smartphones, ...) - the dialog always opens as expected without an error.

I have already checked all dependencies of the dialog component as an online research showed that a dependency injection problem when instantiating the dialog component might cause this error. All dependencies are provided in modules that should have been loaded at this point.

Which reason may cause this error and how can I prevent it?

Thanks for your support!

0

There are 0 best solutions below