diff --git a/packages/animations/browser/src/render/animation_engine_next.ts b/packages/animations/browser/src/render/animation_engine_next.ts index 410d3a77df..3bd0b4b6bd 100644 --- a/packages/animations/browser/src/render/animation_engine_next.ts +++ b/packages/animations/browser/src/render/animation_engine_next.ts @@ -48,8 +48,11 @@ export class AnimationEngine { trigger = buildTrigger(name, ast); this._triggerCache[cacheKey] = trigger; } + this._transitionEngine.registerTrigger(namespaceId, name, trigger); + } - this._transitionEngine.register(namespaceId, hostElement, name, trigger); + register(namespaceId: string, hostElement: any) { + this._transitionEngine.register(namespaceId, hostElement); } destroy(namespaceId: string, context: any) { @@ -86,7 +89,7 @@ export class AnimationEngine { return this._transitionEngine.listen(namespaceId, element, eventName, eventPhase, callback); } - flush(countId: number = -1): void { this._transitionEngine.flush(countId); } + flush(microtaskId: number = -1): void { this._transitionEngine.flush(microtaskId); } get players(): AnimationPlayer[] { return (this._transitionEngine.players as AnimationPlayer[]) diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index f925d4b337..fa3e1419b0 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -13,12 +13,13 @@ import {AnimationTransitionInstruction} from '../dsl/animation_transition_instru import {AnimationTrigger} from '../dsl/animation_trigger'; import {ElementInstructionMap} from '../dsl/element_instruction_map'; import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer'; -import {ENTER_CLASSNAME, LEAVE_CLASSNAME, LEAVE_SELECTOR, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, iteratorToArray, setStyles} from '../util'; +import {ENTER_CLASSNAME, LEAVE_CLASSNAME, LEAVE_SELECTOR, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util'; import {AnimationDriver} from './animation_driver'; import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared'; const EMPTY_PLAYER_ARRAY: AnimationPlayer[] = []; +const ANIMATE_EPOCH_ATTR = 'ng-animate-id'; interface TriggerListener { name: string; @@ -255,7 +256,7 @@ export class AnimationTransitionNamespace { } private _destroyInnerNodes(rootElement: any, context: any, animate: boolean = false) { - listToArray(this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true)).forEach(elm => { + this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true).forEach(elm => { if (animate && containsClass(elm, this._hostClassName)) { const innerNs = this._engine.namespacesByHostElement.get(elm); @@ -273,9 +274,7 @@ export class AnimationTransitionNamespace { removeNode(element: any, context: any, doNotRecurse?: boolean): void { const engine = this._engine; - - addClass(element, LEAVE_CLASSNAME); - engine.afterFlush(() => removeClass(element, LEAVE_CLASSNAME)); + engine.markElementAsRemoved(element); if (!doNotRecurse && element.childElementCount) { this._destroyInnerNodes(element, context, true); @@ -382,7 +381,7 @@ export class AnimationTransitionNamespace { insertNode(element: any, parent: any): void { addClass(element, this._hostClassName); } - drainQueuedTransitions(countId: number): QueueInstruction[] { + drainQueuedTransitions(microtaskId: number): QueueInstruction[] { const instructions: QueueInstruction[] = []; this._queue.forEach(entry => { const player = entry.player; @@ -395,7 +394,7 @@ export class AnimationTransitionNamespace { if (listener.name == entry.triggerName) { const baseEvent = makeAnimationEvent( element, entry.triggerName, entry.fromState.value, entry.toState.value); - (baseEvent as any)['_data'] = countId; + (baseEvent as any)['_data'] = microtaskId; listenOnPlayer(entry.player, listener.phase, baseEvent, listener.callback); } }); @@ -449,13 +448,13 @@ export interface QueuedTransition { export class TransitionAnimationEngine { public players: TransitionAnimationPlayer[] = []; public queuedRemovals = new Map any>(); - public newlyInserted = new Set(); public newHostElements = new Map(); public playersByElement = new Map(); public playersByQueriedElement = new Map(); public statesByElement = new Map(); public totalAnimations = 0; public totalQueuedPlayers = 0; + public currentEpochId = 0; private _namespaceLookup: {[id: string]: AnimationTransitionNamespace} = {}; private _namespaceList: AnimationTransitionNamespace[] = []; @@ -498,7 +497,7 @@ export class TransitionAnimationEngine { // animation renderer type. If this happens then we can still have // access to this item when we query for :enter nodes. If the parent // is a renderer then the set data-structure will normalize the entry - this.newlyInserted.add(hostElement); + this.updateElementEpoch(hostElement); } return this._namespaceLookup[namespaceId] = ns; } @@ -526,17 +525,24 @@ export class TransitionAnimationEngine { return ns; } - register(namespaceId: string, hostElement: any, name: string, trigger: AnimationTrigger) { + register(namespaceId: string, hostElement: any) { let ns = this._namespaceLookup[namespaceId]; if (!ns) { ns = this.createNamespace(namespaceId, hostElement); } - if (ns.register(name, trigger)) { + return ns; + } + + registerTrigger(namespaceId: string, name: string, trigger: AnimationTrigger) { + let ns = this._namespaceLookup[namespaceId]; + if (ns && ns.register(name, trigger)) { this.totalAnimations++; } } destroy(namespaceId: string, context: any) { + if (!namespaceId) return; + const ns = this._fetchNamespace(namespaceId); this.afterFlush(() => { @@ -564,20 +570,51 @@ export class TransitionAnimationEngine { insertNode(namespaceId: string, element: any, parent: any, insertBefore: boolean): void { if (!isElementNode(element)) return; - this._fetchNamespace(namespaceId).insertNode(element, parent); + // special case for when an element is removed and reinserted (move operation) + // when this occurs we do not want to use the element for deletion later + if (this.queuedRemovals.has(element)) { + this.markElementAsRemoved(element, true); + this.queuedRemovals.delete(element); + } + + // in the event that the namespaceId is blank then the caller + // code does not contain any animation code in it, but it is + // just being called so that the node is marked as being inserted + if (namespaceId) { + this._fetchNamespace(namespaceId).insertNode(element, parent); + } // only *directives and host elements are inserted before if (insertBefore) { - this.newlyInserted.add(element); + this.updateElementEpoch(element); + } + } + + updateElementEpoch(element: any, isRemoval?: boolean) { + const epoch = (isRemoval ? -1 : 1) * this.currentEpochId; + setAttribute(element, ANIMATE_EPOCH_ATTR, epoch); + } + + markElementAsRemoved(element: any, unmark?: boolean) { + if (unmark) { + removeClass(element, LEAVE_CLASSNAME); + } else { + addClass(element, LEAVE_CLASSNAME); + this.afterFlush(() => removeClass(element, LEAVE_CLASSNAME)); } } removeNode(namespaceId: string, element: any, context: any, doNotRecurse?: boolean): void { - const ns = this._fetchNamespace(namespaceId); - if (!isElementNode(element) || !ns) { - this._onRemovalComplete(element, context); + if (namespaceId) { + const ns = this._fetchNamespace(namespaceId); + if (!isElementNode(element) || !ns) { + this._onRemovalComplete(element, context); + } else { + ns.removeNode(element, context, doNotRecurse); + } } else { - ns.removeNode(element, context, doNotRecurse); + this.markElementAsRemoved(element); + this.queuedRemovals.set(element, () => this._onRemovalComplete(element, context)); } } @@ -597,7 +634,7 @@ export class TransitionAnimationEngine { } destroyInnerAnimations(containerElement: any) { - listToArray(this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true)).forEach(element => { + this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true).forEach(element => { const players = this.playersByElement.get(element); if (players) { players.forEach(player => { @@ -628,20 +665,21 @@ export class TransitionAnimationEngine { }); } - flush(countId: number = -1) { + flush(microtaskId: number = -1) { let players: AnimationPlayer[] = []; if (this.newHostElements.size) { - this.newHostElements.forEach((ns, element) => { this._balanceNamespaceList(ns, element); }); + this.newHostElements.forEach((ns, element) => this._balanceNamespaceList(ns, element)); this.newHostElements.clear(); } if (this._namespaceList.length && (this.totalQueuedPlayers || this.queuedRemovals.size)) { - players = this._flushAnimations(countId); + players = this._flushAnimations(microtaskId); + } else { + this.queuedRemovals.forEach(fn => fn()); } this.totalQueuedPlayers = 0; this.queuedRemovals.clear(); - this.newlyInserted.clear(); this._flushFns.forEach(fn => fn()); this._flushFns = []; @@ -658,9 +696,11 @@ export class TransitionAnimationEngine { quietFns.forEach(fn => fn()); } } + + this.currentEpochId++; } - private _flushAnimations(countId: number): TransitionAnimationPlayer[] { + private _flushAnimations(microtaskId: number): TransitionAnimationPlayer[] { const subTimelines = new ElementInstructionMap(); const skippedPlayers: TransitionAnimationPlayer[] = []; const skippedPlayersMap = new Map(); @@ -672,13 +712,15 @@ export class TransitionAnimationEngine { // this must occur before the instructions are built below such that // the :enter queries match the elements (since the timeline queries // are fired during instruction building). - const allEnterNodes = iteratorToArray(this.newlyInserted.values()); - const enterNodes: any[] = collectEnterElements(this.driver, allEnterNodes); const bodyNode = getBodyNode(); + const allEnterNodes: any[] = + bodyNode ? this.driver.query(bodyNode, makeEpochSelector(this.currentEpochId), true) : []; + const enterNodes: any[] = + allEnterNodes.length ? collectEnterElements(this.driver, allEnterNodes) : []; for (let i = this._namespaceList.length - 1; i >= 0; i--) { const ns = this._namespaceList[i]; - ns.drainQueuedTransitions(countId).forEach(entry => { + ns.drainQueuedTransitions(microtaskId).forEach(entry => { const player = entry.player; const element = entry.element; @@ -751,7 +793,7 @@ export class TransitionAnimationEngine { allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy())); const leaveNodes: any[] = bodyNode && allPostStyleElements.size ? - listToArray(this.driver.query(bodyNode, LEAVE_SELECTOR, true)) : + this.driver.query(bodyNode, LEAVE_SELECTOR, true) : []; // PRE STAGE: fill the ! styles @@ -866,7 +908,6 @@ export class TransitionAnimationEngine { elementContainsData(namespaceId: string, element: any) { let containsData = false; if (this.queuedRemovals.has(element)) containsData = true; - if (this.newlyInserted.has(element)) containsData = true; if (this.playersByElement.has(element)) containsData = true; if (this.playersByQueriedElement.has(element)) containsData = true; if (this.statesByElement.has(element)) containsData = true; @@ -1217,12 +1258,6 @@ function cloakAndComputeStyles( return valuesMap; } -function listToArray(list: any): any[] { - const arr: any[] = []; - arr.push(...(list as any[])); - return arr; -} - function collectEnterElements(driver: AnimationDriver, allEnterNodes: any[]) { allEnterNodes.forEach(element => addClass(element, POTENTIAL_ENTER_CLASSNAME)); const enterNodes = filterNodeClasses(driver, getBodyNode(), POTENTIAL_ENTER_SELECTOR); @@ -1264,9 +1299,22 @@ function removeClass(element: any, className: string) { } } +function setAttribute(element: any, attr: string, value: any) { + if (element.setAttribute) { + element.setAttribute(attr, value); + } else { + element[attr] = value; + } +} + function getBodyNode(): any|null { if (typeof document != 'undefined') { return document.body; } return null; } + +function makeEpochSelector(epochId: number, isRemoval?: boolean) { + const value = (isRemoval ? -1 : 1) * epochId; + return `[${ANIMATE_EPOCH_ATTR}="${value}"]`; +} diff --git a/packages/animations/browser/test/render/transition_animation_engine_spec.ts b/packages/animations/browser/test/render/transition_animation_engine_spec.ts index 75cd0a1c5a..2ed18f0047 100644 --- a/packages/animations/browser/test/render/transition_animation_engine_spec.ts +++ b/packages/animations/browser/test/render/transition_animation_engine_spec.ts @@ -660,7 +660,8 @@ function registerTrigger( if (errors.length) { } const trigger = buildTrigger(name, ast); - engine.register(id, element, name, trigger) + engine.register(id, element); + engine.registerTrigger(id, name, trigger); } function setProperty( diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index 9f69208e25..b12240b1c6 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -1101,9 +1101,10 @@ export function main() { const cmp = fixture.componentInstance; const someTrigger = trigger('someTrigger', []); + const hostElement = fixture.nativeElement; + engine.register(DEFAULT_NAMESPACE_ID, hostElement); engine.registerTrigger( - DEFAULT_COMPONENT_ID, DEFAULT_NAMESPACE_ID, fixture.nativeElement, someTrigger.name, - someTrigger); + DEFAULT_COMPONENT_ID, DEFAULT_NAMESPACE_ID, hostElement, someTrigger.name, someTrigger); cmp.exp = 'a'; fixture.detectChanges(); diff --git a/packages/core/test/animation/animation_query_integration_spec.ts b/packages/core/test/animation/animation_query_integration_spec.ts index 869f57a940..6582fb55be 100644 --- a/packages/core/test/animation/animation_query_integration_spec.ts +++ b/packages/core/test/animation/animation_query_integration_spec.ts @@ -1084,6 +1084,107 @@ export function main() { ]); }); }); + + it('should query elements in sub components that do not contain animations using the :enter selector', + () => { + @Component({ + selector: 'parent-cmp', + template: ` +
+ +
+ `, + animations: [trigger( + 'myAnimation', + [transition( + '* => on', + [query( + ':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])])])] + }) + class ParentCmp { + public exp: any; + + @ViewChild('child') public child: any; + } + + @Component({ + selector: 'child-cmp', + template: ` +
+ {{ item }} +
+ ` + }) + class ChildCmp { + public items: any[] = []; + } + + TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp]}); + const fixture = TestBed.createComponent(ParentCmp); + const cmp = fixture.componentInstance; + fixture.detectChanges(); + + cmp.exp = 'on'; + cmp.child.items = [1, 2, 3]; + fixture.detectChanges(); + + const players = getLog() as any[]; + expect(players.length).toEqual(3); + + expect(players[0].element.innerText.trim()).toEqual('1'); + expect(players[1].element.innerText.trim()).toEqual('2'); + expect(players[2].element.innerText.trim()).toEqual('3'); + }); + + it('should query elements in sub components that do not contain animations using the :leave selector', + () => { + @Component({ + selector: 'parent-cmp', + template: ` +
+ +
+ `, + animations: [trigger( + 'myAnimation', + [transition( + '* => on', [query(':leave', [animate(1000, style({opacity: 0}))])])])] + }) + class ParentCmp { + public exp: any; + + @ViewChild('child') public child: any; + } + + @Component({ + selector: 'child-cmp', + template: ` +
+ {{ item }} +
+ ` + }) + class ChildCmp { + public items: any[] = []; + } + + TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp]}); + const fixture = TestBed.createComponent(ParentCmp); + const cmp = fixture.componentInstance; + cmp.child.items = [4, 5, 6]; + fixture.detectChanges(); + + cmp.exp = 'on'; + cmp.child.items = []; + fixture.detectChanges(); + + const players = getLog() as any[]; + expect(players.length).toEqual(3); + + expect(players[0].element.innerText.trim()).toEqual('4'); + expect(players[1].element.innerText.trim()).toEqual('5'); + expect(players[2].element.innerText.trim()).toEqual('6'); + }); }); describe('sub triggers', () => { diff --git a/packages/core/test/linker/projection_integration_spec.ts b/packages/core/test/linker/projection_integration_spec.ts index 36e6e213d9..9ba823ed4b 100644 --- a/packages/core/test/linker/projection_integration_spec.ts +++ b/packages/core/test/linker/projection_integration_spec.ts @@ -130,11 +130,13 @@ export function main() { .map(de => de.injector.get(ManualViewportDirective)); expect(main.nativeElement).toHaveText('(, B)'); + viewportDirectives.forEach(d => d.show()); + main.detectChanges(); expect(main.nativeElement).toHaveText('(A1, B)'); viewportDirectives.forEach(d => d.hide()); - + main.detectChanges(); expect(main.nativeElement).toHaveText('(, B)'); }); @@ -200,10 +202,11 @@ export function main() { expect(main.nativeElement).toHaveText('(, BC)'); viewportDirective.show(); + main.detectChanges(); expect(main.nativeElement).toHaveText('(A, BC)'); viewportDirective.hide(); - + main.detectChanges(); expect(main.nativeElement).toHaveText('(, BC)'); }); @@ -492,7 +495,7 @@ export function main() { expect(main.nativeElement).toHaveText('(, D)'); viewViewportDir.show(); - + main.detectChanges(); expect(main.nativeElement).toHaveText('(AC, D)'); const contentViewportDir = @@ -500,12 +503,13 @@ export function main() { ManualViewportDirective); contentViewportDir.show(); - + main.detectChanges(); expect(main.nativeElement).toHaveText('(ABC, D)'); // hide view viewport, and test that it also hides // the content viewport's views viewViewportDir.hide(); + main.detectChanges(); expect(main.nativeElement).toHaveText('(, D)'); }); }); diff --git a/packages/core/test/view/embedded_view_spec.ts b/packages/core/test/view/embedded_view_spec.ts index fee7481e5c..017a7de9c6 100644 --- a/packages/core/test/view/embedded_view_spec.ts +++ b/packages/core/test/view/embedded_view_spec.ts @@ -62,6 +62,7 @@ export function main() { [elementDef(NodeFlags.None, null !, null !, 0, 'span', [['name', 'child1']])])) ])); const viewContainerData = asElementData(parentView, 1); + const rf = parentView.root.rendererFactory; const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]); @@ -75,8 +76,10 @@ export function main() { expect(getDOM().getAttribute(rootChildren[1], 'name')).toBe('child0'); expect(getDOM().getAttribute(rootChildren[2], 'name')).toBe('child1'); + rf.begin !(); detachEmbeddedView(viewContainerData, 1); detachEmbeddedView(viewContainerData, 0); + rf.end !(); expect(getDOM().childNodes(rootNodes[0]).length).toBe(2); }); @@ -187,4 +190,4 @@ export function main() { expect(log).toEqual(['ngOnDestroy']); }); }); -} \ No newline at end of file +} diff --git a/packages/core/test/view/ng_content_spec.ts b/packages/core/test/view/ng_content_spec.ts index 1c176b0dad..b65b24668f 100644 --- a/packages/core/test/view/ng_content_spec.ts +++ b/packages/core/test/view/ng_content_spec.ts @@ -121,6 +121,7 @@ export function main() { ]))); const componentView = asElementData(view, 0).componentView; + const rf = componentView.root.rendererFactory; const view0 = createEmbeddedView(componentView, componentView.def.nodes[1]); attachEmbeddedView(view, asElementData(componentView, 1), 0, view0); @@ -128,7 +129,9 @@ export function main() { expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0]))[1]) .toBe(asTextData(view, 2).renderText); + rf.begin !(); detachEmbeddedView(asElementData(componentView, 1), 0); + rf.end !(); expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(1); }); diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index de510acc08..69caf1b144 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -12,12 +12,13 @@ import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, Re @Injectable() export class AnimationRendererFactory implements RendererFactory2 { private _currentId: number = 0; - private _currentFlushId: number = 1; + private _microtaskId: number = 1; private _animationCallbacksBuffer: [(e: any) => any, any][] = []; + private _rendererCache = new Map(); constructor( - private delegate: RendererFactory2, private _engine: AnimationEngine, private _zone: NgZone) { - _engine.onRemovalComplete = (element: any, delegate: any) => { + private delegate: RendererFactory2, private engine: AnimationEngine, private _zone: NgZone) { + engine.onRemovalComplete = (element: any, delegate: Renderer2) => { // Note: if an component element has a leave animation, and the component // a host leave animation, the view engine will call `removeChild` for the parent // component renderer as well as for the child component renderer. @@ -29,18 +30,31 @@ export class AnimationRendererFactory implements RendererFactory2 { } createRenderer(hostElement: any, type: RendererType2): Renderer2 { - let delegate = this.delegate.createRenderer(hostElement, type); - if (!hostElement || !type || !type.data || !type.data['animation']) return delegate; + const EMPTY_NAMESPACE_ID = ''; + + // cache the delegates to find out which cached delegate can + // be used by which cached renderer + const delegate = this.delegate.createRenderer(hostElement, type); + if (!hostElement || !type || !type.data || !type.data['animation']) { + let renderer: BaseAnimationRenderer|undefined = this._rendererCache.get(delegate); + if (!renderer) { + renderer = new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine); + // only cache this result when the base renderer is used + this._rendererCache.set(delegate, renderer); + } + return renderer; + } const componentId = type.id; const namespaceId = type.id + '-' + this._currentId; this._currentId++; + this.engine.register(namespaceId, hostElement); const animationTriggers = type.data['animation'] as AnimationTriggerMetadata[]; animationTriggers.forEach( - trigger => this._engine.registerTrigger( + trigger => this.engine.registerTrigger( componentId, namespaceId, hostElement, trigger.name, trigger)); - return new AnimationRenderer(this, delegate, this._engine, this._zone, namespaceId); + return new AnimationRenderer(this, namespaceId, delegate, this.engine); } begin() { @@ -50,13 +64,12 @@ export class AnimationRendererFactory implements RendererFactory2 { } private _scheduleCountTask() { - Zone.current.scheduleMicroTask( - 'incremenet the animation microtask', () => { this._currentFlushId++; }); + Zone.current.scheduleMicroTask('incremenet the animation microtask', () => this._microtaskId++); } /* @internal */ scheduleListenerCallback(count: number, fn: (e: any) => any, data: any) { - if (count >= 0 && count < this._currentFlushId) { + if (count >= 0 && count < this._microtaskId) { this._zone.run(() => fn(data)); return; } @@ -79,54 +92,64 @@ export class AnimationRendererFactory implements RendererFactory2 { end() { this._zone.runOutsideAngular(() => { this._scheduleCountTask(); - this._engine.flush(this._currentFlushId); + this.engine.flush(this._microtaskId); }); if (this.delegate.end) { this.delegate.end(); } } - whenRenderingDone(): Promise { return this._engine.whenRenderingDone(); } + whenRenderingDone(): Promise { return this.engine.whenRenderingDone(); } } -export class AnimationRenderer implements Renderer2 { - public destroyNode: ((node: any) => any)|null = null; - public microtaskCount: number = 0; - +export class BaseAnimationRenderer implements Renderer2 { constructor( - private _factory: AnimationRendererFactory, public delegate: Renderer2, - private _engine: AnimationEngine, private _zone: NgZone, private _namespaceId: string) { + protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine) { this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode !(n) : null; } get data() { return this.delegate.data; } + destroyNode: ((n: any) => void)|null; + destroy(): void { - this._engine.destroy(this._namespaceId, this.delegate); + this.engine.destroy(this.namespaceId, this.delegate); this.delegate.destroy(); } - createElement(name: string, namespace?: string): any { + createElement(name: string, namespace?: string|null|undefined) { return this.delegate.createElement(name, namespace); } - createComment(value: string): any { return this.delegate.createComment(value); } + createComment(value: string) { return this.delegate.createComment(value); } - createText(value: string): any { return this.delegate.createText(value); } + createText(value: string) { return this.delegate.createText(value); } - selectRootElement(selectorOrNode: string|any): any { - return this.delegate.selectRootElement(selectorOrNode); + appendChild(parent: any, newChild: any): void { + this.delegate.appendChild(parent, newChild); + this.engine.onInsert(this.namespaceId, newChild, parent, false); } - parentNode(node: any): any { return this.delegate.parentNode(node); } + insertBefore(parent: any, newChild: any, refChild: any): void { + this.delegate.insertBefore(parent, newChild, refChild); + this.engine.onInsert(this.namespaceId, newChild, parent, true); + } - nextSibling(node: any): any { return this.delegate.nextSibling(node); } + removeChild(parent: any, oldChild: any): void { + this.engine.onRemove(this.namespaceId, oldChild, this.delegate); + } - setAttribute(el: any, name: string, value: string, namespace?: string): void { + selectRootElement(selectorOrNode: any) { return this.delegate.selectRootElement(selectorOrNode); } + + parentNode(node: any) { return this.delegate.parentNode(node); } + + nextSibling(node: any) { return this.delegate.nextSibling(node); } + + setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void { this.delegate.setAttribute(el, name, value, namespace); } - removeAttribute(el: any, name: string, namespace?: string): void { + removeAttribute(el: any, name: string, namespace?: string|null|undefined): void { this.delegate.removeAttribute(el, name, namespace); } @@ -134,34 +157,37 @@ export class AnimationRenderer implements Renderer2 { removeClass(el: any, name: string): void { this.delegate.removeClass(el, name); } - setStyle(el: any, style: string, value: any, flags: RendererStyleFlags2): void { + setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void { this.delegate.setStyle(el, style, value, flags); } - removeStyle(el: any, style: string, flags: RendererStyleFlags2): void { + removeStyle(el: any, style: string, flags?: RendererStyleFlags2|undefined): void { this.delegate.removeStyle(el, style, flags); } + setProperty(el: any, name: string, value: any): void { + this.delegate.setProperty(el, name, value); + } + setValue(node: any, value: string): void { this.delegate.setValue(node, value); } - appendChild(parent: any, newChild: any): void { - this.delegate.appendChild(parent, newChild); - this._engine.onInsert(this._namespaceId, newChild, parent, false); + listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { + return this.delegate.listen(target, eventName, callback); } +} - insertBefore(parent: any, newChild: any, refChild: any): void { - this.delegate.insertBefore(parent, newChild, refChild); - this._engine.onInsert(this._namespaceId, newChild, parent, true); - } - - removeChild(parent: any, oldChild: any): void { - this._engine.onRemove(this._namespaceId, oldChild, this.delegate); +export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 { + constructor( + public factory: AnimationRendererFactory, namespaceId: string, delegate: Renderer2, + engine: AnimationEngine) { + super(namespaceId, delegate, engine); + this.namespaceId = namespaceId; } setProperty(el: any, name: string, value: any): void { if (name.charAt(0) == '@') { name = name.substr(1); - this._engine.setProperty(this._namespaceId, el, name, value); + this.engine.setProperty(this.namespaceId, el, name, value); } else { this.delegate.setProperty(el, name, value); } @@ -176,9 +202,9 @@ export class AnimationRenderer implements Renderer2 { if (name.charAt(0) != '@') { // transition-specific [name, phase] = parseTriggerCallbackName(name); } - return this._engine.listen(this._namespaceId, element, name, phase, event => { + return this.engine.listen(this.namespaceId, element, name, phase, event => { const countId = (event as any)['_data'] || -1; - this._factory.scheduleListenerCallback(countId, callback, event); + this.factory.scheduleListenerCallback(countId, callback, event); }); } return this.delegate.listen(target, eventName, callback);