Create Dialog in CDK. (#8844)

This commit is contained in:
Joey Perrott
2017-12-14 15:33:44 -08:00
committed by Andrew Seguin
parent dc045967d8
commit 4d675d6aac
15 changed files with 1984 additions and 1 deletions
+1
View File
@@ -74,6 +74,7 @@
# Material experimental package
/src/material-experimental/** @jelbourn
/src/material-experimental/dialog/** @jelbourn @josephperrott @crisbeto
# Docs examples & guides
/guides/** @amcdnl @jelbourn
@@ -0,0 +1,92 @@
/**
* @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.io/license
*/
import {ViewContainerRef} from '@angular/core';
import {Direction} from '@angular/cdk/bidi';
import {ComponentType} from '@angular/cdk/overlay';
import {CdkDialogContainer} from './dialog-container';
/** Valid ARIA roles for a dialog element. */
export type DialogRole = 'dialog' | 'alertdialog';
/** Possible overrides for a dialog's position. */
export interface DialogPosition {
top?: string;
bottom?: string;
left?: string;
right?: string;
}
export class DialogConfig<D = any> {
/** Component to use as the container for the dialog. */
containerComponent?: ComponentType<CdkDialogContainer>;
/**
* Where the attached component should live in Angular's *logical* component tree.
* This affects what is available for injection and the change detection order for the
* component instantiated inside of the dialog. This does not affect where the dialog
* content will be rendered.
*/
viewContainerRef?: ViewContainerRef;
/** The id of the dialog. */
id?: string;
/** The ARIA role of the dialog. */
role?: DialogRole = 'dialog';
/** Custom class(es) for the overlay panel. */
panelClass?: string | string[] = '';
/** Custom class(es) for the dialog container. */
containerClass?: string | string[] = '';
/** Whether the dialog has a background. */
hasBackdrop?: boolean = true;
/** Custom class(es) for the backdrop. */
backdropClass?: string | undefined = '';
/** Whether the dialog can be closed by user interaction. */
disableClose?: boolean = false;
/** The width of the dialog. */
width?: string = '';
/** The height of the dialog. */
height?: string = '';
/** The minimum width of the dialog. */
minWidth?: string | number = '';
/** The minimum height of the dialog. */
minHeight?: string | number = '';
/** The maximum width of the dialog. */
maxWidth?: string | number = '80vw';
/** The maximum height of the dialog. */
maxHeight?: string | number = '';
/** The position of the dialog. */
position?: DialogPosition;
/** Data to be injected into the dialog content. */
data?: D | null = null;
/** The layout direction for the dialog content. */
direction?: Direction = 'ltr';
/** ID of the element that describes the dialog. */
ariaDescribedBy?: string | null = null;
/** Aria label to assign to the dialog element */
ariaLabel?: string | null = null;
/** Whether the dialog should focus the first focusable element on open. */
autoFocus?: boolean = true;
}
@@ -0,0 +1 @@
<ng-template cdkPortalOutlet></ng-template>
@@ -0,0 +1,6 @@
cdk-dialog-container {
background: white;
border-radius: 5px;
display: block;
padding: 10px;
}
@@ -0,0 +1,218 @@
/**
* @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.io/license
*/
import {
ElementRef,
HostBinding,
ViewChild,
ComponentRef,
EmbeddedViewRef,
ChangeDetectorRef,
Component,
Optional,
Inject,
ViewEncapsulation,
ChangeDetectionStrategy,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {trigger, state, style, transition, animate, AnimationEvent} from '@angular/animations';
import {
BasePortalOutlet,
PortalHostDirective,
ComponentPortal,
TemplatePortal
} from '@angular/cdk/portal';
import {FocusTrapFactory} from '@angular/cdk/a11y';
import {DialogConfig} from './dialog-config';
import {Subject} from 'rxjs/Subject';
export function throwDialogContentAlreadyAttachedError() {
throw Error('Attempting to attach dialog content after content is already attached');
}
/**
* Internal component that wraps user-provided dialog content.
* @docs-private
*/
@Component({
moduleId: module.id,
selector: 'cdk-dialog-container',
templateUrl: './dialog-container.html',
styleUrls: ['dialog-container.css'],
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
// Using OnPush for dialogs caused some G3 sync issues. Disabled until we can track them down.
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
animations: [
trigger('dialog', [
state('enter', style({ opacity: 1 })),
state('exit, void', style({ opacity: 0 })),
transition('* => *', animate(225)),
])
],
host: {
'[@dialog]': '_state',
'(@dialog.start)': '_onAnimationStart($event)',
'(@dialog.done)': '_onAnimationDone($event)',
},
})
export class CdkDialogContainer extends BasePortalOutlet {
/** State of the dialog animation. */
_state: 'void' | 'enter' | 'exit' = 'enter';
/** Element that was focused before the dialog was opened. Save this to restore upon close. */
private _elementFocusedBeforeDialogWasOpened: HTMLElement | null = null;
/** The class that traps and manages focus within the dialog. */
private _focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, false);
// @HostBinding is used in the class as it is expected to be extended. Since @Component decorator
// metadata is not inherited by child classes, instead the host binding data is defined in a way
// that can be inherited.
// tslint:disable:no-host-decorator-in-concrete
@HostBinding('attr.aria-label') get _ariaLabel() { return this._config.ariaLabel || null; }
@HostBinding('attr.aria-describedby')
get _ariaDescribedBy() { return this._config ? this._config.ariaDescribedBy : null; }
@HostBinding('attr.role') get _role() { return this._config ? this._config.role : null; }
@HostBinding('attr.tabindex') get _tabindex() { return -1; }
// tslint:disable:no-host-decorator-in-concrete
/** The portal host inside of this container into which the dialog content will be loaded. */
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
/** A subject emitting before the dialog enters the view. */
_beforeEnter: Subject<void> = new Subject();
/** A subject emitting after the dialog enters the view. */
_afterEnter: Subject<void> = new Subject();
/** A subject emitting before the dialog exits the view. */
_beforeExit: Subject<void> = new Subject();
/** A subject emitting after the dialog exits the view. */
_afterExit: Subject<void> = new Subject();
/** The dialog configuration. */
_config: DialogConfig;
constructor(
private _elementRef: ElementRef,
private _focusTrapFactory: FocusTrapFactory,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() @Inject(DOCUMENT) private _document: any) {
super();
}
/** Destroy focus trap to place focus back to the element focused before the dialog opened. */
ngOnDestroy() {
this._focusTrap.destroy();
}
/**
* Attach a ComponentPortal as content to this dialog container.
* @param portal Portal to be attached as the dialog content.
*/
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
if (this._portalHost.hasAttached()) {
throwDialogContentAlreadyAttachedError();
}
this._savePreviouslyFocusedElement();
return this._portalHost.attachComponentPortal(portal);
}
/**
* Attach a TemplatePortal as content to this dialog container.
* @param portal Portal to be attached as the dialog content.
*/
attachTemplatePortal<C>(portal: TemplatePortal<C>): EmbeddedViewRef<C> {
if (this._portalHost.hasAttached()) {
throwDialogContentAlreadyAttachedError();
}
this._savePreviouslyFocusedElement();
return this._portalHost.attachTemplatePortal(portal);
}
/** Emit lifecycle events based on animation `start` callback. */
_onAnimationStart(event: AnimationEvent) {
if (event.toState === 'enter') {
this._beforeEnter.next();
}
if (event.toState === 'void' || event.toState === 'exit') {
this._beforeExit.next();
}
}
/** Emit lifecycle events based on animation `done` callback. */
_onAnimationDone(event: AnimationEvent) {
if (event.toState === 'enter') {
this._autoFocusFirstTabbableElement();
this._afterEnter.next();
}
if (event.toState === 'void' || event.toState === 'exit') {
this._returnFocusAfterDialog();
this._afterExit.next();
}
}
/** Starts the dialog exit animation. */
_startExiting(): void {
this._state = 'exit';
// Mark the container for check so it can react if the
// view container is using OnPush change detection.
this._changeDetectorRef.markForCheck();
}
/** Saves a reference to the element that was focused before the dialog was opened. */
private _savePreviouslyFocusedElement() {
if (this._document) {
this._elementFocusedBeforeDialogWasOpened = this._document.activeElement as HTMLElement;
// Move focus onto the dialog immediately in order to prevent the user from accidentally
// opening multiple dialogs at the same time. Needs to be async, because the element
// may not be focusable immediately.
Promise.resolve().then(() => this._elementRef.nativeElement.focus());
}
}
/**
* Autofocus the first tabbable element inside of the dialog, if there is not a tabbable element,
* focus the dialog instead.
*/
private _autoFocusFirstTabbableElement() {
// If were to attempt to focus immediately, then the content of the dialog would not yet be
// ready in instances where change detection has to run first. To deal with this, we simply
// wait for the microtask queue to be empty.
if (this._config.autoFocus) {
this._focusTrap.focusInitialElementWhenReady().then(hasMovedFocus => {
// If we didn't find any focusable elements inside the dialog, focus the
// container so the user can't tab into other elements behind it.
if (!hasMovedFocus) {
this._elementRef.nativeElement.focus();
}
});
}
}
/** Returns the focus to the element focused before the dialog was open. */
private _returnFocusAfterDialog() {
const toFocus = this._elementFocusedBeforeDialogWasOpened;
// We need the extra check, because IE can set the `activeElement` to null in some cases.
if (toFocus && typeof toFocus.focus === 'function') {
toFocus.focus();
}
}
}
@@ -0,0 +1,43 @@
/**
* @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.io/license
*/
import {InjectionToken} from '@angular/core';
import {ComponentType, Overlay, ScrollStrategy, BlockScrollStrategy} from '@angular/cdk/overlay';
import {DialogRef} from './dialog-ref';
import {CdkDialogContainer} from './dialog-container';
import {DialogConfig} from './dialog-config';
/** Injection token for the Dialog's ScrollStrategy. */
export const DIALOG_SCROLL_STRATEGY =
new InjectionToken<() => ScrollStrategy>('DialogScrollStrategy');
/** Injection token for the Dialog's Data. */
export const DIALOG_DATA = new InjectionToken<any>('DialogData');
/** Injection token for the DialogRef constructor. */
export const DIALOG_REF = new InjectionToken<DialogRef<any>>('DialogRef');
/** Injection token for the DialogConfig. */
export const DIALOG_CONFIG = new InjectionToken<DialogConfig>('DialogConfig');
/** Injection token for the Dialog's DialogContainer component. */
export const DIALOG_CONTAINER =
new InjectionToken<ComponentType<CdkDialogContainer>>('DialogContainer');
/** @docs-private */
export function MAT_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay):
() => BlockScrollStrategy {
return () => overlay.scrollStrategies.block();
}
/** @docs-private */
export const MAT_DIALOG_SCROLL_STRATEGY_PROVIDER = {
provide: DIALOG_SCROLL_STRATEGY,
deps: [Overlay],
useFactory: MAT_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY,
};
@@ -0,0 +1,48 @@
/**
* @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.io/license
*/
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {OverlayModule} from '@angular/cdk/overlay';
import {PortalModule} from '@angular/cdk/portal';
import {A11yModule} from '@angular/cdk/a11y';
import {Dialog} from './dialog';
import {CdkDialogContainer} from './dialog-container';
import {DialogConfig} from './dialog-config';
import {DialogRef} from './dialog-ref';
import {
DIALOG_CONFIG,
DIALOG_CONTAINER,
DIALOG_REF,
MAT_DIALOG_SCROLL_STRATEGY_PROVIDER
} from './dialog-injectors';
@NgModule({
imports: [
CommonModule,
OverlayModule,
PortalModule,
A11yModule,
],
exports: [
CdkDialogContainer,
],
declarations: [
CdkDialogContainer,
],
providers: [
Dialog,
MAT_DIALOG_SCROLL_STRATEGY_PROVIDER,
{provide: DIALOG_REF, useValue: DialogRef},
{provide: DIALOG_CONTAINER, useValue: CdkDialogContainer},
{provide: DIALOG_CONFIG, useValue: DialogConfig},
],
entryComponents: [CdkDialogContainer],
})
export class DialogModule {}
@@ -0,0 +1,142 @@
/**
* @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.io/license
*/
import {OverlayRef, GlobalPositionStrategy, OverlaySizeConfig} from '@angular/cdk/overlay';
import {Observable} from 'rxjs/Observable';
import {map} from 'rxjs/operators/map';
import {DialogPosition} from './dialog-config';
import {CdkDialogContainer} from './dialog-container';
/** Unique id for the created dialog. */
let uniqueId = 0;
/**
* Reference to a dialog opened via the Dialog service.
*/
export class DialogRef<T, R = any> {
/** The instance of the component in the dialog. */
componentInstance: T;
/** Whether the user is allowed to close the dialog. */
disableClose: boolean | undefined;
/** Result to be passed to afterClosed. */
private _result: R | undefined;
constructor(
public _overlayRef: OverlayRef,
protected _containerInstance: CdkDialogContainer,
readonly id: string = `dialog-${uniqueId++}`) {
// If the dialog has a backdrop, handle clicks from the backdrop.
if (_containerInstance._config.hasBackdrop) {
_overlayRef.backdropClick().subscribe(() => {
if (!this.disableClose) {
this.close();
}
});
}
this.beforeClose().subscribe(() => {
this._overlayRef.detachBackdrop();
});
this.afterClosed().subscribe(() => {
this._overlayRef.detach();
this._overlayRef.dispose();
this.componentInstance = null!;
});
}
/** Gets an observable that emits when the overlay's backdrop has been clicked. */
backdropClick(): Observable<void> {
return this._overlayRef.backdropClick();
}
/**
* Close the dialog.
* @param dialogResult Optional result to return to the dialog opener.
*/
close(dialogResult?: R): void {
this._result = dialogResult;
this._containerInstance._startExiting();
}
/**
* Updates the dialog's position.
* @param position New dialog position.
*/
updatePosition(position?: DialogPosition): this {
let strategy = this._getPositionStrategy();
if (position && (position.left || position.right)) {
position.left ? strategy.left(position.left) : strategy.right(position.right);
} else {
strategy.centerHorizontally();
}
if (position && (position.top || position.bottom)) {
position.top ? strategy.top(position.top) : strategy.bottom(position.bottom);
} else {
strategy.centerVertically();
}
this._overlayRef.updatePosition();
return this;
}
/**
* Gets an observable that emits when keydown events are targeted on the overlay.
*/
keydownEvents(): Observable<KeyboardEvent> {
return this._overlayRef.keydownEvents();
}
/**
* Updates the dialog's width and height, defined, min and max.
* @param size New size for the overlay.
*/
updateSize(size: OverlaySizeConfig): this {
if (size.width) {
this._getPositionStrategy().width(size.width.toString());
}
if (size.height) {
this._getPositionStrategy().height(size.height.toString());
}
this._overlayRef.updateSize(size);
this._overlayRef.updatePosition();
return this;
}
/** Fetches the position strategy object from the overlay ref. */
private _getPositionStrategy(): GlobalPositionStrategy {
return this._overlayRef.getConfig().positionStrategy as GlobalPositionStrategy;
}
/** Gets an observable that emits when dialog begins opening. */
beforeOpen(): Observable<void> {
return this._containerInstance._beforeEnter.asObservable();
}
/** Gets an observable that emits when dialog is finished opening. */
afterOpen(): Observable<void> {
return this._containerInstance._afterEnter.asObservable();
}
/** Gets an observable that emits when dialog begins closing. */
beforeClose(): Observable<R | undefined> {
return this._containerInstance._beforeExit.pipe(map(() => this._result));
}
/** Gets an observable that emits when dialog is finished closing. */
afterClosed(): Observable<R | undefined> {
return this._containerInstance._afterExit.pipe(map(() => this._result));
}
}
File diff suppressed because it is too large Load Diff
+309
View File
@@ -0,0 +1,309 @@
/**
* @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.io/license
*/
import {
TemplateRef,
SkipSelf,
Optional,
Injector,
Inject,
ComponentRef
} from '@angular/core';
import {ComponentPortal, PortalInjector, TemplatePortal} from '@angular/cdk/portal';
import {ESCAPE} from '@angular/cdk/keycodes';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {DialogRef} from './dialog-ref';
import {Location} from '@angular/common';
import {DialogConfig} from './dialog-config';
import {Directionality} from '@angular/cdk/bidi';
import {of as observableOf} from 'rxjs/observable/of';
import {CdkDialogContainer} from './dialog-container';
import {
ComponentType,
Overlay,
OverlayRef,
OverlayConfig,
} from '@angular/cdk/overlay';
import {startWith} from 'rxjs/operators/startWith';
import {defer} from 'rxjs/observable/defer';
import {
DIALOG_SCROLL_STRATEGY,
DIALOG_DATA,
DIALOG_REF,
DIALOG_CONTAINER,
DIALOG_CONFIG,
} from './dialog-injectors';
/**
* Service to open modal dialogs.
*/
export class Dialog {
/** A the handleKeydown method bound to the class instance. */
private _boundKeydown = this._handleKeydown.bind(this);
/** Stream that emits when all dialogs are closed. */
get _afterAllClosed(): Observable<void> {
return this._parentDialog ? this._parentDialog.afterAllClosed : this._afterAllClosedBase;
}
_afterAllClosedBase = new Subject<void>();
afterAllClosed: Observable<void> = defer<void>(() => this.openDialogs.length ?
this._afterAllClosed : this._afterAllClosed.pipe(startWith(undefined)));
/** Stream that emits when a dialog is opened. */
get afterOpen(): Subject<DialogRef<any>> {
return this._parentDialog ? this._parentDialog.afterOpen : this._afterOpen;
}
_afterOpen: Subject<DialogRef<any>> = new Subject();
/** Stream that emits when a dialog is opened. */
get openDialogs(): DialogRef<any>[] {
return this._parentDialog ? this._parentDialog.openDialogs : this._openDialogs;
}
_openDialogs: DialogRef<any>[] = [];
constructor(
private overlay: Overlay,
private injector: Injector,
@Inject(DIALOG_REF) private dialogRefConstructor,
@Inject(DIALOG_SCROLL_STRATEGY) private _scrollStrategy,
@Optional() @SkipSelf() private _parentDialog: Dialog,
@Optional() location: Location) {
// Close all of the dialogs when the user goes forwards/backwards in history or when the
// location hash changes. Note that this usually doesn't include clicking on links (unless
// the user is using the `HashLocationStrategy`).
if (!_parentDialog && location) {
location.subscribe(() => this.closeAll());
}
this.afterAllClosed.subscribe(() => {
document.removeEventListener('keydown', this._boundKeydown);
});
}
/** Gets an open dialog by id. */
getById(id: string): DialogRef<any> | undefined {
return this._openDialogs.find(ref => ref.id === id);
}
/** Closes all open dialogs. */
closeAll(): void {
this.openDialogs.forEach(ref => ref.close());
}
/** Opens a dialog from a component. */
openFromComponent<T>(component: ComponentType<T>, config?: DialogConfig): DialogRef<any> {
config = this._applyConfigDefaults(config);
if (config.id && this.getById(config.id)) {
throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
}
const overlayRef = this._createOverlay(config);
const dialogContainer = this._attachDialogContainer(overlayRef, config);
const dialogRef = this._attachDialogContentForComponent(component, dialogContainer,
overlayRef, config);
this.registerDialogRef(dialogRef);
return dialogRef;
}
/** Opens a dialog from a template. */
openFromTemplate<T>(template: TemplateRef<T>, config?: DialogConfig): DialogRef<any> {
config = this._applyConfigDefaults(config);
if (config.id && this.getById(config.id)) {
throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
}
const overlayRef = this._createOverlay(config);
const dialogContainer = this._attachDialogContainer(overlayRef, config);
const dialogRef = this._attachDialogContentForTemplate(template, dialogContainer,
overlayRef, config);
this.registerDialogRef(dialogRef);
return dialogRef;
}
/**
* Registers a keydown listener, and forwards emitting events for when dialogs are opened and all
* dialogs are closed.
*/
private registerDialogRef(dialogRef: DialogRef<any>): void {
if (!this.openDialogs.length) {
document.addEventListener('keydown', this._boundKeydown);
}
this.openDialogs.push(dialogRef);
let dialogOpenSub = dialogRef.afterOpen().subscribe(() => {
this.afterOpen.next(dialogRef);
dialogOpenSub.unsubscribe();
});
let dialogCloseSub = dialogRef.afterClosed().subscribe(() => {
let dialogIdx = this._openDialogs.indexOf(dialogRef);
if (dialogIdx !== -1) { this._openDialogs.splice(dialogIdx, 1); }
if (!this._openDialogs.length) {
this._afterAllClosedBase.next();
dialogCloseSub.unsubscribe();
}
});
}
/**
* Handles global key presses while there are open dialogs. Closes the
* top dialog when the user presses escape.
*/
private _handleKeydown(event: KeyboardEvent): void {
const topDialog = this.openDialogs[this.openDialogs.length - 1];
const canClose = topDialog ? !topDialog.disableClose : false;
if (event.keyCode === ESCAPE && canClose) {
topDialog.close();
}
}
/**
* Creates an overlay config from a dialog config.
* @param config The dialog configuration.
* @returns The overlay configuration.
*/
protected _createOverlay(config: DialogConfig): OverlayRef {
const overlayConfig = new OverlayConfig({
positionStrategy: this.overlay.position().global(),
scrollStrategy: this._scrollStrategy(),
panelClass: config.panelClass,
hasBackdrop: config.hasBackdrop,
direction: config.direction,
minWidth: config.minWidth,
minHeight: config.minHeight,
maxWidth: config.maxWidth,
maxHeight: config.maxHeight
});
if (config.backdropClass) {
overlayConfig.backdropClass = config.backdropClass;
}
return this.overlay.create(overlayConfig);
}
/**
* Attaches an MatDialogContainer to a dialog's already-created overlay.
* @param overlay Reference to the dialog's underlying overlay.
* @param config The dialog configuration.
* @returns A promise resolving to a ComponentRef for the attached container.
*/
protected _attachDialogContainer(overlay: OverlayRef, config: DialogConfig): CdkDialogContainer {
const container = config.containerComponent || this.injector.get(DIALOG_CONTAINER);
let containerPortal = new ComponentPortal(container, config.viewContainerRef);
let containerRef: ComponentRef<CdkDialogContainer> = overlay.attach(containerPortal);
containerRef.instance._config = config;
return containerRef.instance;
}
/**
* Attaches the user-provided component to the already-created MatDialogContainer.
* @param componentOrTemplateRef The type of component being loaded into the dialog,
* or a TemplateRef to instantiate as the content.
* @param dialogContainer Reference to the wrapping MatDialogContainer.
* @param overlayRef Reference to the overlay in which the dialog resides.
* @param config The dialog configuration.
* @returns A promise resolving to the MatDialogRef that should be returned to the user.
*/
protected _attachDialogContentForComponent<T>(
componentOrTemplateRef: ComponentType<T>,
dialogContainer: CdkDialogContainer,
overlayRef: OverlayRef,
config: DialogConfig): DialogRef<any> {
// Create a reference to the dialog we're creating in order to give the user a handle
// to modify and close it.
const dialogRef = new this.dialogRefConstructor(overlayRef, dialogContainer, config.id);
const injector = this._createInjector<T>(config, dialogRef, dialogContainer);
const contentRef = dialogContainer.attachComponentPortal(
new ComponentPortal(componentOrTemplateRef, undefined, injector));
dialogRef.componentInstance = contentRef.instance;
dialogRef.updateSize({width: config.width, height: config.height})
.updatePosition(config.position);
return dialogRef;
}
/**
* Attaches the user-provided component to the already-created MatDialogContainer.
* @param componentOrTemplateRef The type of component being loaded into the dialog,
* or a TemplateRef to instantiate as the content.
* @param dialogContainer Reference to the wrapping MatDialogContainer.
* @param overlayRef Reference to the overlay in which the dialog resides.
* @param config The dialog configuration.
* @returns A promise resolving to the MatDialogRef that should be returned to the user.
*/
protected _attachDialogContentForTemplate<T>(
componentOrTemplateRef: TemplateRef<T>,
dialogContainer: CdkDialogContainer,
overlayRef: OverlayRef,
config: DialogConfig): DialogRef<any> {
// Create a reference to the dialog we're creating in order to give the user a handle
// to modify and close it.
const dialogRef = new this.dialogRefConstructor(overlayRef, dialogContainer, config.id);
dialogContainer.attachTemplatePortal(
new TemplatePortal<T>(componentOrTemplateRef, null!,
<any>{$implicit: config.data, dialogRef}));
dialogRef.updateSize({width: config.width, height: config.height})
.updatePosition(config.position);
return dialogRef;
}
/**
* Creates a custom injector to be used inside the dialog. This allows a component loaded inside
* of a dialog to close itself and, optionally, to return a value.
* @param config Config object that is used to construct the dialog.
* @param dialogRef Reference to the dialog.
* @param container Dialog container element that wraps all of the contents.
* @returns The custom injector that can be used inside the dialog.
*/
private _createInjector<T>(
config: DialogConfig,
dialogRef: DialogRef<T>,
dialogContainer: CdkDialogContainer): PortalInjector {
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
const injectionTokens = new WeakMap();
injectionTokens.set(this.injector.get(DIALOG_REF), dialogRef);
injectionTokens.set(this.injector.get(DIALOG_CONTAINER), dialogContainer);
injectionTokens.set(DIALOG_DATA, config.data);
injectionTokens.set(Directionality, {
value: config.direction,
change: observableOf()
});
return new PortalInjector(userInjector || this.injector, injectionTokens);
}
/**
* Expands the provided configuration object to include the default values for properties which
* are undefined.
*/
private _applyConfigDefaults(config?: DialogConfig): DialogConfig {
const dialogConfig = this.injector.get(DIALOG_CONFIG) as typeof DialogConfig;
return {...new dialogConfig(), ...config};
}
}
@@ -0,0 +1,9 @@
/**
* @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.io/license
*/
export * from './public-api';
@@ -0,0 +1,14 @@
/**
* @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.io/license
*/
export * from './dialog-container';
export * from './dialog-config';
export * from './dialog-ref';
export * from './dialog';
export * from './dialog-module';
export * from './dialog-injectors';
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig-build",
"files": [
"public-api.ts",
"../typings.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"flatModuleOutFile": "index.js",
"flatModuleId": "@angular/material-experimental/dialog",
"skipTemplateCodegen": true
}
}
@@ -27,7 +27,8 @@
}
},
"files": [
"public-api.ts"
"public-api.ts",
"typings.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
+1
View File
@@ -0,0 +1 @@
declare var module: {id: string};