Skip to content

Commit

Permalink
fix(animations): make sure 'ngbRunTransition' runs inside the zone (#…
Browse files Browse the repository at this point in the history
…3957)

Fixes #3950

(cherry picked from commit a29f570)
  • Loading branch information
maxokorokov committed Jan 21, 2021
1 parent dce3886 commit a006a62
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 88 deletions.
2 changes: 1 addition & 1 deletion src/accordion/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ export class NgbAccordion implements AfterContentChecked {
// The direction (show or hide) is choosen by each panel.isOpen state
if (panel.transitionRunning) {
const panelElement = this._getPanelElement(panel.id);
ngbRunTransition(panelElement !, ngbCollapsingTransition, {
ngbRunTransition(this._ngZone, panelElement !, ngbCollapsingTransition, {
animation,
runningTransition: 'stop',
context: {direction: panel.isOpen ? 'show' : 'hide'}
Expand Down
8 changes: 5 additions & 3 deletions src/alert/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
OnChanges,
OnInit,
SimpleChanges,
ViewEncapsulation
ViewEncapsulation,
NgZone
} from '@angular/core';

import {Observable} from 'rxjs';
Expand Down Expand Up @@ -75,7 +76,8 @@ export class NgbAlert implements OnInit,
@Output() closed = new EventEmitter<void>();


constructor(config: NgbAlertConfig, private _renderer: Renderer2, private _element: ElementRef) {
constructor(
config: NgbAlertConfig, private _renderer: Renderer2, private _element: ElementRef, private _zone: NgZone) {
this.dismissible = config.dismissible;
this.type = config.type;
this.animation = config.animation;
Expand All @@ -93,7 +95,7 @@ export class NgbAlert implements OnInit,
*/
close(): Observable<void> {
const transition = ngbRunTransition(
this._element.nativeElement, ngbAlertFadingTransition,
this._zone, this._element.nativeElement, ngbAlertFadingTransition,
{animation: this.animation, runningTransition: 'continue'});
transition.subscribe(() => this.closed.emit());
return transition;
Expand Down
5 changes: 3 additions & 2 deletions src/carousel/carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,15 +377,16 @@ export class NgbCarousel implements AfterContentChecked,
const activeSlide = this._getSlideById(this.activeId);
if (activeSlide) {
const activeSlideTransition =
ngbRunTransition(this._getSlideElement(activeSlide.id), ngbCarouselTransitionOut, options);
ngbRunTransition(this._ngZone, this._getSlideElement(activeSlide.id), ngbCarouselTransitionOut, options);
activeSlideTransition.subscribe(() => { activeSlide.slid.emit({isShown: false, direction, source}); });
transitions.push(activeSlideTransition);
}

const previousId = this.activeId;
this.activeId = selectedSlide.id;
const nextSlide = this._getSlideById(this.activeId);
const transition = ngbRunTransition(this._getSlideElement(selectedSlide.id), ngbCarouselTransitionIn, options);
const transition =
ngbRunTransition(this._ngZone, this._getSlideElement(selectedSlide.id), ngbCarouselTransitionIn, options);
transition.subscribe(() => { nextSlide ?.slid.emit({isShown: true, direction, source}); });
transitions.push(transition);

Expand Down
7 changes: 5 additions & 2 deletions src/collapse/collapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ElementRef,
EventEmitter,
Input,
NgZone,
OnChanges,
OnInit,
Output,
Expand Down Expand Up @@ -49,7 +50,9 @@ export class NgbCollapse implements OnInit, OnChanges {
@Output() hidden = new EventEmitter<void>();


constructor(private _element: ElementRef, config: NgbCollapseConfig) { this.animation = config.animation; }
constructor(private _element: ElementRef, config: NgbCollapseConfig, private _zone: NgZone) {
this.animation = config.animation;
}

ngOnInit() {
this._element.nativeElement.classList.add('collapse');
Expand Down Expand Up @@ -77,7 +80,7 @@ export class NgbCollapse implements OnInit, OnChanges {
}

private _runTransition(collapsed: boolean, animation: boolean, emitEvent = true) {
ngbRunTransition(this._element.nativeElement, ngbCollapsingTransition, {
ngbRunTransition(this._zone, this._element.nativeElement, ngbCollapsingTransition, {
animation,
runningTransition: 'stop',
context: {direction: collapsed ? 'hide' : 'show'}
Expand Down
4 changes: 2 additions & 2 deletions src/modal/modal-backdrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ export class NgbModalBackdrop implements OnInit {
ngOnInit() {
this._zone.onStable.asObservable().pipe(take(1)).subscribe(() => {
ngbRunTransition(
this._el.nativeElement, ({classList}) => classList.add('show'),
this._zone, this._el.nativeElement, ({classList}) => classList.add('show'),
{animation: this.animation, runningTransition: 'continue'});
});
}

hide(): Observable<void> {
return ngbRunTransition(
this._el.nativeElement, ({classList}) => classList.remove('show'),
this._zone, this._el.nativeElement, ({classList}) => classList.remove('show'),
{animation: this.animation, runningTransition: 'stop'});
}
}
12 changes: 7 additions & 5 deletions src/modal/modal-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ export class NgbModalWindow implements OnInit,
const {nativeElement} = this._elRef;
const context: NgbTransitionOptions<any> = {animation: this.animation, runningTransition: 'stop'};

const windowTransition$ = ngbRunTransition(nativeElement, () => nativeElement.classList.remove('show'), context);
const dialogTransition$ = ngbRunTransition(this._dialogEl.nativeElement, () => {}, context);
const windowTransition$ =
ngbRunTransition(this._zone, nativeElement, () => nativeElement.classList.remove('show'), context);
const dialogTransition$ = ngbRunTransition(this._zone, this._dialogEl.nativeElement, () => {}, context);

const transitions$ = zip(windowTransition$, dialogTransition$);
transitions$.subscribe(() => {
Expand All @@ -98,8 +99,9 @@ export class NgbModalWindow implements OnInit,
const {nativeElement} = this._elRef;
const context: NgbTransitionOptions<any> = {animation: this.animation, runningTransition: 'continue'};

const windowTransition$ = ngbRunTransition(nativeElement, () => nativeElement.classList.add('show'), context);
const dialogTransition$ = ngbRunTransition(this._dialogEl.nativeElement, () => {}, context);
const windowTransition$ =
ngbRunTransition(this._zone, nativeElement, () => nativeElement.classList.add('show'), context);
const dialogTransition$ = ngbRunTransition(this._zone, this._dialogEl.nativeElement, () => {}, context);

zip(windowTransition$, dialogTransition$).subscribe(() => {
this.shown.next();
Expand Down Expand Up @@ -189,7 +191,7 @@ export class NgbModalWindow implements OnInit,

private _bumpBackdrop() {
if (this.backdrop === 'static') {
ngbRunTransition(this._elRef.nativeElement, ({classList}) => {
ngbRunTransition(this._zone, this._elRef.nativeElement, ({classList}) => {
classList.add('modal-static');
return () => classList.remove('modal-static');
}, {animation: this.animation, runningTransition: 'continue'});
Expand Down
53 changes: 28 additions & 25 deletions src/nav/nav-outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Directive,
ElementRef,
Input,
NgZone,
QueryList,
ViewChildren,
ViewEncapsulation
Expand Down Expand Up @@ -68,7 +69,7 @@ export class NgbNavOutlet implements AfterViewInit {
*/
@Input('ngbNavOutlet') nav: NgbNav;

constructor(private _cd: ChangeDetectorRef) {}
constructor(private _cd: ChangeDetectorRef, private _ngZone: NgZone) {}

isPanelTransitioning(item: NgbNavItem) { return this._activePane ?.item === item; }

Expand All @@ -88,32 +89,34 @@ export class NgbNavOutlet implements AfterViewInit {

// fading out
if (this._activePane) {
ngbRunTransition(this._activePane.elRef.nativeElement, ngbNavFadeOutTransition, options).subscribe(() => {
const activeItem = this._activePane ?.item;
this._activePane = this._getPaneForItem(nextItem);

// mark for check when transition finishes as outlet or parent containers might be OnPush
// without this the panes that have "faded out" will stay in DOM
this._cd.markForCheck();

// fading in
if (this._activePane) {
// we have to add the '.active' class before running the transition,
// because it should be in place before `ngbRunTransition` does `reflow()`
this._activePane.elRef.nativeElement.classList.add('active');
ngbRunTransition(this._activePane.elRef.nativeElement, ngbNavFadeInTransition, options).subscribe(() => {
if (nextItem) {
nextItem.shown.emit();
this.nav.shown.emit(nextItem.id);
ngbRunTransition(this._ngZone, this._activePane.elRef.nativeElement, ngbNavFadeOutTransition, options)
.subscribe(() => {
const activeItem = this._activePane ?.item;
this._activePane = this._getPaneForItem(nextItem);

// mark for check when transition finishes as outlet or parent containers might be OnPush
// without this the panes that have "faded out" will stay in DOM
this._cd.markForCheck();

// fading in
if (this._activePane) {
// we have to add the '.active' class before running the transition,
// because it should be in place before `ngbRunTransition` does `reflow()`
this._activePane.elRef.nativeElement.classList.add('active');
ngbRunTransition(this._ngZone, this._activePane.elRef.nativeElement, ngbNavFadeInTransition, options)
.subscribe(() => {
if (nextItem) {
nextItem.shown.emit();
this.nav.shown.emit(nextItem.id);
}
});
}
});
}

if (activeItem) {
activeItem.hidden.emit();
this.nav.hidden.emit(activeItem.id);
}
});
if (activeItem) {
activeItem.hidden.emit();
this.nav.hidden.emit(activeItem.id);
}
});
} else {
this._updateActivePane();
}
Expand Down
11 changes: 6 additions & 5 deletions src/popover/popover.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
OnDestroy,
TemplateRef,
ViewContainerRef,
AfterViewInit
AfterViewInit,
NgZone
} from '@angular/core';

import {NgbPopoverModule} from './popover.module';
Expand Down Expand Up @@ -511,8 +512,8 @@ describe('ngb-popover', () => {
`<div ngbPopover="Great tip!" triggers="click" (shown)="shown()" (hidden)="hidden()"></div>`);
const directive = fixture.debugElement.query(By.directive(NgbPopover));

let shownSpy = spyOn(fixture.componentInstance, 'shown');
let hiddenSpy = spyOn(fixture.componentInstance, 'hidden');
let shownSpy = spyOn(fixture.componentInstance, 'shown').and.callThrough();
let hiddenSpy = spyOn(fixture.componentInstance, 'hidden').and.callThrough();

triggerEvent(directive, 'click');
fixture.detectChanges();
Expand Down Expand Up @@ -907,8 +908,8 @@ export class TestComponent {
this._vcRef.remove(0);
}

shown() {}
hidden() {}
shown() { expect(NgZone.isInAngularZone()).toBe(true, `'shown' should run inside the Angular zone`); }
hidden() { expect(NgZone.isInAngularZone()).toBe(true, `'hidden' should run inside the Angular zone`); }
}

@Component({selector: 'test-onpush-cmpt', changeDetection: ChangeDetectionStrategy.OnPush, template: ``})
Expand Down
5 changes: 3 additions & 2 deletions src/toast/toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ export class NgbToast implements AfterContentInit,
hide(): Observable<void> {
this._clearTimeout();
const transition = ngbRunTransition(
this._element.nativeElement, ngbToastFadeOutTransition, {animation: this.animation, runningTransition: 'stop'});
this._zone, this._element.nativeElement, ngbToastFadeOutTransition,
{animation: this.animation, runningTransition: 'stop'});
transition.subscribe(() => { this.hidden.emit(); });
return transition;
}
Expand All @@ -180,7 +181,7 @@ export class NgbToast implements AfterContentInit,
* @since 8.0.0
*/
show(): Observable<void> {
const transition = ngbRunTransition(this._element.nativeElement, ngbToastFadeInTransition, {
const transition = ngbRunTransition(this._zone, this._element.nativeElement, ngbToastFadeInTransition, {
animation: this.animation,
runningTransition: 'continue',
});
Expand Down
9 changes: 5 additions & 4 deletions src/tooltip/tooltip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
NgZone,
TemplateRef,
ViewChild,
ViewContainerRef
Expand Down Expand Up @@ -531,8 +532,8 @@ describe('ngb-tooltip', () => {
`<div ngbTooltip="Great tip!" triggers="click" (shown)="shown()" (hidden)="hidden()"></div>`);
const directive = fixture.debugElement.query(By.directive(NgbTooltip));

let shownSpy = spyOn(fixture.componentInstance, 'shown');
let hiddenSpy = spyOn(fixture.componentInstance, 'hidden');
const shownSpy = spyOn(fixture.componentInstance, 'shown').and.callThrough();
const hiddenSpy = spyOn(fixture.componentInstance, 'hidden').and.callThrough();

triggerEvent(directive, 'click');
fixture.detectChanges();
Expand Down Expand Up @@ -789,8 +790,8 @@ export class TestComponent {

@ViewChild(NgbTooltip, {static: true}) tooltip: NgbTooltip;

shown() {}
hidden() {}
shown() { expect(NgZone.isInAngularZone()).toBe(true, `'shown' should run inside the Angular zone`); }
hidden() { expect(NgZone.isInAngularZone()).toBe(true, `'hidden' should run inside the Angular zone`); }

constructor(private _vcRef: ViewContainerRef) {}

Expand Down
12 changes: 6 additions & 6 deletions src/util/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ export class PopupService<T> {
}

const {nativeElement} = this._windowRef.location;
const onStable$ = this._ngZone.onStable.asObservable().pipe(take(1));
const transition$ = onStable$.pipe(mergeMap(() => this._ngZone.run(() => {
return ngbRunTransition(
nativeElement, ({classList}) => classList.add('show'), {animation, runningTransition: 'continue'});
})));
const transition$ = this._ngZone.onStable.pipe(
take(1), mergeMap(
() => ngbRunTransition(
this._ngZone, nativeElement, ({classList}) => classList.add('show'),
{animation, runningTransition: 'continue'})));

return {windowRef: this._windowRef, transition$};
}
Expand All @@ -53,7 +53,7 @@ export class PopupService<T> {
}

return ngbRunTransition(
this._windowRef.location.nativeElement, ({classList}) => classList.remove('show'),
this._ngZone, this._windowRef.location.nativeElement, ({classList}) => classList.remove('show'),
{animation, runningTransition: 'stop'})
.pipe(tap(() => {
if (this._windowRef) {
Expand Down
Loading

0 comments on commit a006a62

Please sign in to comment.