Skip to content

Commit a643a2e

Browse files
authored
fix(material/snack-bar): Ensure snackbar open animation works with OnPush ancestor (#28331)
The enter animation of the snackbar is not guaranteed to work when there is an `OnPush` parent because it never calls `markForCheck` when updating the animation state. Even though it calls `detectChanges`, this will not refresh the host bindings, which includes the animation. This change updates the `enter` behavior to also call `markForCheck` to ensure the host view is eventually refreshed when change detection runs.
1 parent 083472d commit a643a2e

File tree

2 files changed

+13
-3
lines changed

2 files changed

+13
-3
lines changed

src/material/snack-bar/snack-bar-container.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
191191
enter(): void {
192192
if (!this._destroyed) {
193193
this._animationState = 'visible';
194+
// _animationState lives in host bindings and `detectChanges` does not refresh host bindings
195+
// so we have to call `markForCheck` to ensure the host view is refreshed eventually.
196+
this._changeDetectorRef.markForCheck();
194197
this._changeDetectorRef.detectChanges();
195198
this._screenReaderAnnounce();
196199
}
@@ -205,6 +208,7 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
205208
// where multiple snack bars are opened in quick succession (e.g. two consecutive calls to
206209
// `MatSnackBar.open`).
207210
this._animationState = 'hidden';
211+
this._changeDetectorRef.markForCheck();
208212

209213
// Mark this element with an 'exit' attribute to indicate that the snackbar has
210214
// been dismissed and will soon be removed from the DOM. This is used by the snackbar

src/material/snack-bar/snack-bar.spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {LiveAnnouncer} from '@angular/cdk/a11y';
22
import {OverlayContainer} from '@angular/cdk/overlay';
33
import {
4+
ChangeDetectionStrategy,
45
Component,
56
Directive,
67
Inject,
78
TemplateRef,
89
ViewChild,
910
ViewContainerRef,
11+
signal,
1012
} from '@angular/core';
1113
import {ComponentFixture, fakeAsync, flush, inject, TestBed, tick} from '@angular/core/testing';
1214
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -358,7 +360,7 @@ describe('MatSnackBar', () => {
358360
viewContainerFixture.detectChanges();
359361
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
360362

361-
viewContainerFixture.componentInstance.childComponentExists = false;
363+
viewContainerFixture.componentInstance.childComponentExists.set(false);
362364
viewContainerFixture.detectChanges();
363365
flush();
364366

@@ -403,6 +405,9 @@ describe('MatSnackBar', () => {
403405
const dismissCompleteSpy = jasmine.createSpy('dismiss complete spy');
404406

405407
viewContainerFixture.detectChanges();
408+
409+
const containerElement = document.querySelector('mat-snack-bar-container')!;
410+
expect(containerElement.classList).toContain('ng-animating');
406411
const container1 = snackBarRef.containerInstance as MatSnackBarContainer;
407412
expect(container1._animationState)
408413
.withContext(`Expected the animation state would be 'visible'.`)
@@ -1102,14 +1107,15 @@ class DirectiveWithViewContainer {
11021107

11031108
@Component({
11041109
selector: 'arbitrary-component',
1105-
template: `@if (childComponentExists) {<dir-with-view-container></dir-with-view-container>}`,
1110+
template: `@if (childComponentExists()) {<dir-with-view-container></dir-with-view-container>}`,
11061111
standalone: true,
11071112
imports: [DirectiveWithViewContainer],
1113+
changeDetection: ChangeDetectionStrategy.OnPush,
11081114
})
11091115
class ComponentWithChildViewContainer {
11101116
@ViewChild(DirectiveWithViewContainer) childWithViewContainer: DirectiveWithViewContainer;
11111117

1112-
childComponentExists: boolean = true;
1118+
childComponentExists = signal(true);
11131119

11141120
get childViewContainer() {
11151121
return this.childWithViewContainer.viewContainerRef;

0 commit comments

Comments
 (0)