refactor(animations): defer the noop engine's event deregistration
This commit is contained in:

committed by
Igor Minar

parent
4301dce7b0
commit
36b78e9502
@ -162,9 +162,6 @@ function debugSetCurrentNode(view: ViewData, nodeIndex: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string, event: any) {
|
function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string, event: any) {
|
||||||
if (view.state & ViewState.Destroyed) {
|
|
||||||
throw viewDestroyedError(DebugAction[_currentAction]);
|
|
||||||
}
|
|
||||||
debugSetCurrentNode(view, nodeIndex);
|
debugSetCurrentNode(view, nodeIndex);
|
||||||
return callWithDebugContext(
|
return callWithDebugContext(
|
||||||
DebugAction.handleEvent, view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
|
DebugAction.handleEvent, view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
|
||||||
|
@ -14,6 +14,7 @@ interface ListenerTuple {
|
|||||||
eventPhase: string;
|
eventPhase: string;
|
||||||
triggerName: string;
|
triggerName: string;
|
||||||
callback: (event: any) => any;
|
callback: (event: any) => any;
|
||||||
|
doRemove?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChangeTuple {
|
interface ChangeTuple {
|
||||||
@ -82,12 +83,7 @@ export class NoopAnimationEngine extends AnimationEngine {
|
|||||||
const tuple = <ListenerTuple>{triggerName: eventName, eventPhase, callback};
|
const tuple = <ListenerTuple>{triggerName: eventName, eventPhase, callback};
|
||||||
listeners.push(tuple);
|
listeners.push(tuple);
|
||||||
|
|
||||||
return () => {
|
return () => tuple.doRemove = true;
|
||||||
const index = listeners.indexOf(tuple);
|
|
||||||
if (index >= 0) {
|
|
||||||
listeners.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flush(): void {
|
flush(): void {
|
||||||
@ -134,6 +130,16 @@ export class NoopAnimationEngine extends AnimationEngine {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// remove all the listeners after everything is complete
|
||||||
|
Array.from(this._listeners.keys()).forEach(element => {
|
||||||
|
const listenersToKeep = this._listeners.get(element).filter(l => !l.doRemove);
|
||||||
|
if (listenersToKeep.length) {
|
||||||
|
this._listeners.set(element, listenersToKeep);
|
||||||
|
} else {
|
||||||
|
this._listeners.delete(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
onStartCallbacks.forEach(fn => fn());
|
onStartCallbacks.forEach(fn => fn());
|
||||||
onDoneCallbacks.forEach(fn => fn());
|
onDoneCallbacks.forEach(fn => fn());
|
||||||
this._flaggedRemovals.clear();
|
this._flaggedRemovals.clear();
|
||||||
|
@ -121,33 +121,53 @@ export function main() {
|
|||||||
expect(captures).toEqual([]);
|
expect(captures).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should deregister a listener when the return function is called', () => {
|
it('should deregister a listener when the return function is called, but only after flush',
|
||||||
|
() => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
const elm = {};
|
||||||
|
|
||||||
|
const fn1 = engine.listen(elm, 'trig1', 'start', capture('trig1-start'));
|
||||||
|
const fn2 = engine.listen(elm, 'trig2', 'done', capture('trig2-done'));
|
||||||
|
|
||||||
|
engine.setProperty(elm, 'trig1', 'value1');
|
||||||
|
engine.setProperty(elm, 'trig2', 'value2');
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual(['trig1-start', 'trig2-done']);
|
||||||
|
|
||||||
|
captures = [];
|
||||||
|
engine.setProperty(elm, 'trig1', 'value3');
|
||||||
|
engine.setProperty(elm, 'trig2', 'value4');
|
||||||
|
|
||||||
|
fn1();
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual(['trig1-start', 'trig2-done']);
|
||||||
|
|
||||||
|
captures = [];
|
||||||
|
engine.setProperty(elm, 'trig1', 'value5');
|
||||||
|
engine.setProperty(elm, 'trig2', 'value6');
|
||||||
|
|
||||||
|
fn2();
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual(['trig2-done']);
|
||||||
|
|
||||||
|
captures = [];
|
||||||
|
engine.setProperty(elm, 'trig1', 'value7');
|
||||||
|
engine.setProperty(elm, 'trig2', 'value8');
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire a removal listener even if the listener is deregistered prior to flush', () => {
|
||||||
const engine = new NoopAnimationEngine();
|
const engine = new NoopAnimationEngine();
|
||||||
const elm = {};
|
const elm = {};
|
||||||
|
|
||||||
const fn1 = engine.listen(elm, 'trig1', 'start', capture('trig1-start'));
|
const fn = engine.listen(elm, 'trig', 'start', capture('removal listener'));
|
||||||
const fn2 = engine.listen(elm, 'trig2', 'done', capture('trig2-done'));
|
fn();
|
||||||
|
|
||||||
engine.setProperty(elm, 'trig1', 'value1');
|
engine.onRemove(elm, capture('dom removal'));
|
||||||
engine.setProperty(elm, 'trig2', 'value2');
|
|
||||||
engine.flush();
|
engine.flush();
|
||||||
expect(captures).toEqual(['trig1-start', 'trig2-done']);
|
|
||||||
|
|
||||||
captures = [];
|
expect(captures).toEqual(['dom removal', 'removal listener']);
|
||||||
engine.setProperty(elm, 'trig1', 'value3');
|
|
||||||
engine.setProperty(elm, 'trig2', 'value4');
|
|
||||||
|
|
||||||
fn1();
|
|
||||||
engine.flush();
|
|
||||||
expect(captures).toEqual(['trig2-done']);
|
|
||||||
|
|
||||||
captures = [];
|
|
||||||
engine.setProperty(elm, 'trig1', 'value5');
|
|
||||||
engine.setProperty(elm, 'trig2', 'value6');
|
|
||||||
|
|
||||||
fn2();
|
|
||||||
engine.flush();
|
|
||||||
expect(captures).toEqual([]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('styling', () => {
|
describe('styling', () => {
|
||||||
|
@ -63,5 +63,48 @@ export function main() {
|
|||||||
async();
|
async();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle leave animation callbacks even if the element is destroyed in the process',
|
||||||
|
(async) => {
|
||||||
|
@Component({
|
||||||
|
selector: 'my-cmp',
|
||||||
|
template:
|
||||||
|
'<div *ngIf="exp" @myAnimation (@myAnimation.start)="onStart($event)" (@myAnimation.done)="onDone($event)"></div>',
|
||||||
|
animations: [trigger(
|
||||||
|
'myAnimation',
|
||||||
|
[transition(
|
||||||
|
':leave', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
exp: any;
|
||||||
|
startEvent: any;
|
||||||
|
doneEvent: any;
|
||||||
|
onStart(event: any) { this.startEvent = event; }
|
||||||
|
onDone(event: any) { this.doneEvent = event; }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
const engine = TestBed.get(ɵAnimationEngine);
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
cmp.exp = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
cmp.startEvent = null;
|
||||||
|
cmp.doneEvent = null;
|
||||||
|
|
||||||
|
cmp.exp = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(cmp.startEvent.triggerName).toEqual('myAnimation');
|
||||||
|
expect(cmp.startEvent.phaseName).toEqual('start');
|
||||||
|
expect(cmp.startEvent.toState).toEqual('void');
|
||||||
|
expect(cmp.doneEvent.triggerName).toEqual('myAnimation');
|
||||||
|
expect(cmp.doneEvent.phaseName).toEqual('done');
|
||||||
|
expect(cmp.doneEvent.toState).toEqual('void');
|
||||||
|
async();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user