/
githubmirror
/
components
Обзор
Документация
Войти
/
githubmirror
/
components
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/material/dialog/dialog.ts
239 строк
8 KB
Kristiyan Kostadinov
fix(material/dialog): do not expose CDK dialog ref to users (#33603)
30 июл 2026, 18:45
Не верифицирован
30 июл 2026, 18:45
d61a26d
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import { ComponentType, createBlockScrollStrategy, createGlobalPositionStrategy, ScrollStrategy, } from '@angular/cdk/overlay'; import { ComponentRef, Service, InjectionToken, Injector, OnDestroy, TemplateRef, Type, inject, } from '@angular/core'; import {MatDialogConfig} from './dialog-config'; import {MatDialogContainer} from './dialog-container'; import {MatDialogRef} from './dialog-ref'; import {defer, Observable, Subject} from 'rxjs'; import {Dialog, DialogConfig, DialogRef} from '@angular/cdk/dialog'; import {startWith} from 'rxjs/operators'; import {_IdGenerator} from '@angular/cdk/a11y'; import {_animationsDisabled} from '../core'; /** Injection token that can be used to access the data that was passed in to a dialog. */ export const MAT_DIALOG_DATA = new InjectionToken<any>('MatMdcDialogData'); /** Injection token that can be used to specify default dialog options. */ export const MAT_DIALOG_DEFAULT_OPTIONS = new InjectionToken<MatDialogConfig>( 'mat-mdc-dialog-default-options', ); /** Injection token that determines the scroll handling while the dialog is open. */ export const MAT_DIALOG_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( 'mat-mdc-dialog-scroll-strategy', { providedIn: 'root', factory: () => { const injector = inject(Injector); return () => createBlockScrollStrategy(injector); }, }, ); /** * Service to open Material Design modal dialogs. */ @Service() export class MatDialog implements OnDestroy { private _defaultOptions = inject<MatDialogConfig>(MAT_DIALOG_DEFAULT_OPTIONS, {optional: true}); private _scrollStrategy = inject(MAT_DIALOG_SCROLL_STRATEGY); private _parentDialog = inject(MatDialog, {optional: true, skipSelf: true}); private _idGenerator = inject(_IdGenerator); private _injector = inject(Injector); protected _dialog = inject(Dialog); private _animationsDisabled = _animationsDisabled(); private readonly _openDialogsAtThisLevel: MatDialogRef<any>[] = []; private readonly _afterAllClosedAtThisLevel = new Subject<void>(); private readonly _afterOpenedAtThisLevel = new Subject<MatDialogRef<any>>(); protected dialogConfigClass = MatDialogConfig; private readonly _dialogRefConstructor: Type<MatDialogRef<any>>; private readonly _dialogContainerType: Type<MatDialogContainer>; private readonly _dialogDataToken: InjectionToken<any>; /** Keeps track of the currently-open dialogs. */ get openDialogs(): MatDialogRef<any>[] { return this._parentDialog ? this._parentDialog.openDialogs : this._openDialogsAtThisLevel; } /** Stream that emits when a dialog has been opened. */ get afterOpened(): Subject<MatDialogRef<any>> { return this._parentDialog ? this._parentDialog.afterOpened : this._afterOpenedAtThisLevel; } private _getAfterAllClosed(): Subject<void> { const parent = this._parentDialog; return parent ? parent._getAfterAllClosed() : this._afterAllClosedAtThisLevel; } /** * Stream that emits when all open dialog have finished closing. * Will emit on subscribe if there are no open dialogs to begin with. */ readonly afterAllClosed: Observable<void> = defer(() => this.openDialogs.length ? this._getAfterAllClosed() : this._getAfterAllClosed().pipe(startWith(undefined)), ) as Observable<any>; constructor() { this._dialogRefConstructor = MatDialogRef; this._dialogContainerType = MatDialogContainer; this._dialogDataToken = MAT_DIALOG_DATA; } /** * Opens a modal dialog containing the given component. * @param component Type of the component to load into the dialog. * @param config Extra configuration options. * @returns Reference to the newly-opened dialog. */ open<T, D = any, R = any>( component: ComponentType<T>, config?: MatDialogConfig<D>, ): MatDialogRef<T, R>; /** * Opens a modal dialog containing the given template. * @param template TemplateRef to instantiate as the dialog content. * @param config Extra configuration options. * @returns Reference to the newly-opened dialog. */ open<T, D = any, R = any>( template: TemplateRef<T>, config?: MatDialogConfig<D>, ): MatDialogRef<T, R>; open<T, D = any, R = any>( template: ComponentType<T> | TemplateRef<T>, config?: MatDialogConfig<D>, ): MatDialogRef<T, R>; open<T, D = any, R = any>( componentOrTemplateRef: ComponentType<T> | TemplateRef<T>, config?: MatDialogConfig<D>, ): MatDialogRef<T, R> { let dialogRef: MatDialogRef<T, R>; config = {...(this._defaultOptions || new MatDialogConfig()), ...config}; config.id = config.id || this._idGenerator.getId('mat-mdc-dialog-'); config.scrollStrategy = config.scrollStrategy || this._scrollStrategy(); const cdkRef = this._dialog.open<R, D, T>(componentOrTemplateRef, { ...config, positionStrategy: createGlobalPositionStrategy(this._injector) .centerHorizontally() .centerVertically(), // Disable closing since we need to sync it up to the animation ourselves. disableClose: true, // Closing is tied to our animation so the close predicate has to be implemented separately. closePredicate: undefined, // Disable closing on destroy, because this service cleans up its open dialogs as well. // We want to do the cleanup here, rather than the CDK service, because the CDK destroys // the dialogs immediately whereas we want it to wait for the animations to finish. closeOnDestroy: false, // Disable closing on detachments so that we can sync up the animation. // The Material dialog ref handles this manually. closeOnOverlayDetachments: false, disableAnimations: this._animationsDisabled || config.enterAnimationDuration?.toLocaleString() === '0' || config.exitAnimationDuration?.toString() === '0', container: { type: this._dialogContainerType, providers: () => [ // Provide our config as the CDK config as well since it has the same interface as the // CDK one, but it contains the actual values passed in by the user for things like // `disableClose` which we disable for the CDK dialog since we handle it ourselves. {provide: this.dialogConfigClass, useValue: config}, {provide: DialogConfig, useValue: config}, ], }, templateContext: () => ({dialogRef}), providers: (ref, cdkConfig, dialogContainer) => { dialogRef = new this._dialogRefConstructor(ref, config, dialogContainer); dialogRef.updatePosition(config?.position); return [ {provide: this._dialogContainerType, useValue: dialogContainer}, {provide: this._dialogDataToken, useValue: cdkConfig.data}, {provide: this._dialogRefConstructor, useValue: dialogRef}, {provide: DialogRef, useValue: null}, ]; }, }); // This can't be assigned in the `providers` callback, because // the instance hasn't been assigned to the CDK ref yet. (dialogRef! as {componentRef: ComponentRef<T>}).componentRef = cdkRef.componentRef!; dialogRef!.componentInstance = cdkRef.componentInstance!; this.openDialogs.push(dialogRef!); this.afterOpened.next(dialogRef!); dialogRef!.afterClosed().subscribe(() => { const index = this.openDialogs.indexOf(dialogRef); if (index > -1) { this.openDialogs.splice(index, 1); if (!this.openDialogs.length) { this._getAfterAllClosed().next(); } } }); return dialogRef!; } /** * Closes all of the currently-open dialogs. */ closeAll(): void { this._closeDialogs(this.openDialogs); } /** * Finds an open dialog by its id. * @param id ID to use when looking up the dialog. */ getDialogById(id: string): MatDialogRef<any> | undefined { return this.openDialogs.find(dialog => dialog.id === id); } ngOnDestroy() { // Only close the dialogs at this level on destroy // since the parent service may still be active. this._closeDialogs(this._openDialogsAtThisLevel); this._afterAllClosedAtThisLevel.complete(); this._afterOpenedAtThisLevel.complete(); } private _closeDialogs(dialogs: MatDialogRef<any>[]) { let i = dialogs.length; while (i--) { dialogs[i].close(); } } }