From 8df5efe0824ac8cc5f83be45c0c856b74bbcf862 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 22 Jun 2022 10:07:04 +0200 Subject: [PATCH] fix(material/datepicker): actions not re-rendering if swapped out while calendar is open (#25123) Fixes that we were only refreshing the actions while the calendar is closed. Fixes #25122. (cherry picked from commit 7d87068c724250d17f15d238f5e8f5e19b3adb13) --- .../datepicker/datepicker-actions.spec.ts | 22 +++++++++++++++ src/material/datepicker/datepicker-base.ts | 27 +++++++++++++++---- tools/public_api_guard/material/datepicker.md | 1 + 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/material/datepicker/datepicker-actions.spec.ts b/src/material/datepicker/datepicker-actions.spec.ts index 81b14e21f171..e4fd8fb34104 100644 --- a/src/material/datepicker/datepicker-actions.spec.ts +++ b/src/material/datepicker/datepicker-actions.spec.ts @@ -257,6 +257,28 @@ describe('MatDatepickerActions', () => { expect(control.value).toBeTruthy(); expect(onDateChange).toHaveBeenCalledTimes(1); })); + + it('should be able to toggle the actions while the datepicker is open', fakeAsync(() => { + const fixture = createComponent(DatepickerWithActions); + fixture.componentInstance.renderActions = false; + fixture.detectChanges(); + + fixture.componentInstance.datepicker.open(); + fixture.detectChanges(); + tick(); + flush(); + + const content = document.querySelector('.mat-datepicker-content')!; + expect(content.querySelector('.mat-datepicker-actions')).toBeFalsy(); + + fixture.componentInstance.renderActions = true; + fixture.detectChanges(); + expect(content.querySelector('.mat-datepicker-actions')).toBeTruthy(); + + fixture.componentInstance.renderActions = false; + fixture.detectChanges(); + expect(content.querySelector('.mat-datepicker-actions')).toBeFalsy(); + })); }); @Component({ diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index 04bbfcd7c59b..1f47a696c96f 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -180,10 +180,6 @@ export class MatDatepickerContent> } ngOnInit() { - // If we have actions, clone the model so that we have the ability to cancel the selection, - // otherwise update the global model directly. Note that we want to assign this as soon as - // possible, but `_actionsPortal` isn't available in the constructor so we do it in `ngOnInit`. - this._model = this._actionsPortal ? this._globalModel.clone() : this._globalModel; this._animationState = this.datepicker.touchUi ? 'enter-dialog' : 'enter-dropdown'; } @@ -246,6 +242,25 @@ export class MatDatepickerContent> this._globalModel.updateSelection(this._model.selection, this); } } + + /** + * Assigns a new portal containing the datepicker actions. + * @param portal Portal with the actions to be assigned. + * @param forceRerender Whether a re-render of the portal should be triggered. This isn't + * necessary if the portal is assigned during initialization, but it may be required if it's + * added at a later point. + */ + _assignActions(portal: TemplatePortal | null, forceRerender: boolean) { + // If we have actions, clone the model so that we have the ability to cancel the selection, + // otherwise update the global model directly. Note that we want to assign this as soon as + // possible, but `_actionsPortal` isn't available in the constructor so we do it in `ngOnInit`. + this._model = portal ? this._globalModel.clone() : this._globalModel; + this._actionsPortal = portal; + + if (forceRerender) { + this._changeDetectorRef.detectChanges(); + } + } } /** Form control that can be associated with a datepicker. */ @@ -556,6 +571,7 @@ export abstract class MatDatepickerBase< throw Error('A MatDatepicker can only be associated with a single actions row.'); } this._actionsPortal = portal; + this._componentRef?.instance._assignActions(portal, true); } /** @@ -565,6 +581,7 @@ export abstract class MatDatepickerBase< removeActions(portal: TemplatePortal): void { if (portal === this._actionsPortal) { this._actionsPortal = null; + this._componentRef?.instance._assignActions(null, true); } } @@ -632,8 +649,8 @@ export abstract class MatDatepickerBase< protected _forwardContentValues(instance: MatDatepickerContent) { instance.datepicker = this; instance.color = this.color; - instance._actionsPortal = this._actionsPortal; instance._dialogLabelId = this.datepickerInput.getOverlayLabelId(); + instance._assignActions(this._actionsPortal, false); } /** Opens the overlay with the calendar. */ diff --git a/tools/public_api_guard/material/datepicker.md b/tools/public_api_guard/material/datepicker.md index 8ac9e6557fbd..c2948e8a2d37 100644 --- a/tools/public_api_guard/material/datepicker.md +++ b/tools/public_api_guard/material/datepicker.md @@ -427,6 +427,7 @@ export class MatDatepickerContent> extend readonly _animationDone: Subject; _animationState: 'enter-dropdown' | 'enter-dialog' | 'void'; _applyPendingSelection(): void; + _assignActions(portal: TemplatePortal | null, forceRerender: boolean): void; _calendar: MatCalendar; _closeButtonFocused: boolean; _closeButtonText: string;