Skip to content

Commit

Permalink
feat(animations): allow animation integration support into host params
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko committed Jul 9, 2016
1 parent 3fffaea commit 84cdaef
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class AnimationCompiler {
groupedErrors.push(errorMessage);
}

var factoryName = `${component.type.name}_${entry.name}_${index}`;
var factoryName = `${component.type.name}_${entry.name}`;
index++;

var visitor = new _AnimationBuilder(entry.name, factoryName);
Expand Down
8 changes: 6 additions & 2 deletions modules/@angular/compiler/src/compile_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {getUrlScheme} from './url_resolver';
import {sanitizeIdentifier, splitAtColon} from './util';



// group 0: "[prop] or (event) or @trigger"
// group 1: "prop" from "[prop]"
// group 2: "event" from "(event)"
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
// group 3: "@trigger" from "@trigger"
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/g;

export abstract class CompileMetadataWithIdentifier {
abstract toJson(): {[key: string]: any};
Expand Down Expand Up @@ -741,6 +743,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
hostProperties[matches[1]] = value;
} else if (isPresent(matches[2])) {
hostListeners[matches[2]] = value;
} else if (isPresent(matches[3])) {
hostProperties[matches[3]] = value;
}
});
}
Expand Down
17 changes: 12 additions & 5 deletions modules/@angular/compiler/src/template_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,13 +774,20 @@ class TemplateParseVisitor implements HtmlAstVisitor {
const parts = name.split(PROPERTY_PARTS_SEPARATOR);
let securityContext: SecurityContext;
if (parts.length === 1) {
boundPropertyName = this._schemaRegistry.getMappedPropName(parts[0]);
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
bindingType = PropertyBindingType.Property;
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
this._reportError(
var partValue = parts[0];
if (partValue[0] == '@') {
boundPropertyName = partValue.substr(1);
bindingType = PropertyBindingType.Animation;
securityContext = SecurityContext.NONE;
} else {
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
bindingType = PropertyBindingType.Property;
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
this._reportError(
`Can't bind to '${boundPropertyName}' since it isn't a known native property`,
sourceSpan);
}
}
} else {
if (parts[0] == ATTRIBUTE_PREFIX) {
Expand Down
4 changes: 1 addition & 3 deletions modules/@angular/compiler/src/view_compiler/compile_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,14 @@ export class CompileView implements NameResolver {
public literalArrayCount = 0;
public literalMapCount = 0;
public pipeCount = 0;
public animations = new Map<string, CompiledAnimation>();

public componentContext: o.Expression;

constructor(
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
animations: CompiledAnimation[], public viewIndex: number,
public animations: CompiledAnimation[], public viewIndex: number,
public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
animations.forEach(entry => this.animations.set(entry.name, entry));
this.createMethod = new CompileMethod(this);
this.injectorGetMethod = new CompileMethod(this);
this.updateContentQueriesMethod = new CompileMethod(this);
Expand Down
19 changes: 12 additions & 7 deletions modules/@angular/compiler/src/view_compiler/property_binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {CompileView} from './compile_view';
import {DetectChangesVars, ViewProperties} from './constants';
import {convertCdExpressionToIr} from './expression_converter';


function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
return o.THIS_EXPR.prop(`_expr_${exprIndex}`);
}
Expand Down Expand Up @@ -85,7 +84,7 @@ export function bindRenderText(
}

function bindAndWriteToRenderer(
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement) {
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement, isHostProp: boolean) {
var view = compileElement.view;
var renderNode = compileElement.renderNode;
boundProps.forEach((boundProp) => {
Expand Down Expand Up @@ -129,6 +128,7 @@ function bindAndWriteToRenderer(
if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit));
}

renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
updateStmts.push(
o.THIS_EXPR.prop('renderer')
Expand All @@ -137,7 +137,12 @@ function bindAndWriteToRenderer(
break;
case PropertyBindingType.Animation:
var animationName = boundProp.name;
var animation = view.componentView.animations.get(animationName);
var targetViewExpr: o.Expression = o.THIS_EXPR;
if (isHostProp) {
targetViewExpr = compileElement.appElement.prop('componentView');
}

var animationFnExpr = targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName));

// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
Expand All @@ -158,11 +163,11 @@ function bindAndWriteToRenderer(
[newRenderVar.set(emptyStateValue).toStmt()]));

updateStmts.push(
animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar])
animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar])
.toStmt());

view.detachMethod.addStmt(
animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])
animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])
.toStmt());

if (!_animationViewCheckedFlagMap.get(view)) {
Expand Down Expand Up @@ -212,13 +217,13 @@ function sanitizedValue(

export function bindRenderInputs(
boundProps: BoundElementPropertyAst[], compileElement: CompileElement): void {
bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement);
bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement, false);
}

export function bindDirectiveHostProps(
directiveAst: DirectiveAst, directiveInstance: o.Expression,
compileElement: CompileElement): void {
bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement);
bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement, true);
}

export function bindDirectiveInputs(
Expand Down
4 changes: 3 additions & 1 deletion modules/@angular/compiler/src/view_compiler/view_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,14 +500,16 @@ function createViewFactory(
templateUrlInfo = view.component.template.templateUrl;
}
if (view.viewIndex === 0) {
var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnVariable]));
initRenderCompTypeStmts = [new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
renderCompTypeVar
.set(ViewConstructorVars.viewUtils.callMethod(
'createRenderComponentType',
[
o.literal(templateUrlInfo),
o.literal(view.component.template.ngContentSelectors.length),
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), view.styles
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), view.styles,
animationsExpr
]))
.toStmt()
])];
Expand Down
5 changes: 3 additions & 2 deletions modules/@angular/core/src/linker/view_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ export class ViewUtils {
/**
* Used by the generated code
*/
// TODO (matsko): add typing for the animation function
createRenderComponentType(
templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation,
styles: Array<string|any[]>): RenderComponentType {
styles: Array<string|any[]>, animations: {[key: string]: Function}): RenderComponentType {
return new RenderComponentType(
`${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles);
`${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles, animations);
}

/** @internal */
Expand Down
5 changes: 3 additions & 2 deletions modules/@angular/core/src/render/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import {Injector} from '../di/injector';
import {unimplemented} from '../facade/exceptions';
import {ViewEncapsulation} from '../metadata/view';


/**
* @experimental
*/
// TODO (matsko): add typing for the animation function
export class RenderComponentType {
constructor(
public id: string, public templateUrl: string, public slotCount: number,
public encapsulation: ViewEncapsulation, public styles: Array<string|any[]>) {}
public encapsulation: ViewEncapsulation, public styles: Array<string|any[]>,
public animations: {[key: string]: Function}) {}
}

export abstract class RenderDebugInfo {
Expand Down
80 changes: 62 additions & 18 deletions modules/@angular/core/test/animation/animation_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,29 @@ function declareTests({useJit}: {useJit: boolean}) {
});
})));

it('should be permitted to be registered on the host element',
inject(
[TestComponentBuilder, AnimationDriver],
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
tcb = tcb.overrideAnimations(DummyLoadingCmp, [
trigger('loading', [
state('final', style({'background': 'grey'})),
transition('* => final', [animate(1000)])
])
]);
tcb.createAsync(DummyLoadingCmp).then(fixture => {
var cmp = fixture.debugElement.componentInstance;
cmp.exp = 'final';
fixture.detectChanges();
flushMicrotasks();

var animation = driver.log.pop();
var keyframes = animation['keyframeLookup'];
expect(keyframes[1]).toEqual([1, {'background': 'grey'}]);
});
tick();
})));

it('should retain the destination animation state styles once the animation is complete',
inject(
[TestComponentBuilder, AnimationDriver],
Expand Down Expand Up @@ -1183,22 +1206,10 @@ function declareTests({useJit}: {useJit: boolean}) {
});
}

@Component({
selector: 'if-cmp',
directives: [NgIf],
template: `
<div *ngIf="exp" [@myAnimation]="exp"></div>
`
})
class DummyIfCmp {
exp = false;
exp2 = false;
}

class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
super.animate(element, startingStyles, keyframes, duration, delay, easing);
var player = new InnerContentTrackingAnimationPlayer(element);
this.log[this.log.length - 1]['player'] = player;
Expand All @@ -1207,13 +1218,46 @@ class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
}

class InnerContentTrackingAnimationPlayer extends MockAnimationPlayer {
constructor(public element: any) { super(); }
public computedHeight: number;
public capturedInnerText: string;
constructor(public element:any) {
super();
}

public computedHeight:number;
public capturedInnerText:string;
public playAttempts = 0;
init() { this.computedHeight = getDOM().getComputedStyle(this.element)['height']; }

init() {
this.computedHeight = getDOM().getComputedStyle(this.element)['height'];
}

play() {
this.playAttempts++;
this.capturedInnerText = this.element.querySelector('.inner').innerText;
}
}

@Component({
selector: 'if-cmp',
directives: [NgIf],
template: `
<div *ngIf="exp" [@myAnimation]="exp"></div>
`
})
class DummyIfCmp {
exp = false;
exp2 = false;
}

@Component({
selector: 'if-cmp',
host: {
'@loading': 'exp'
},
directives: [NgIf],
template: `
<div>loading...</div>
`
})
class DummyLoadingCmp {
exp = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ export class Serializer {
return new RenderComponentType(
map['id'], map['templateUrl'], map['slotCount'],
this.deserialize(map['encapsulation'], ViewEncapsulation),
this.deserialize(map['styles'], PRIMITIVE));
this.deserialize(map['styles'], PRIMITIVE),
{});
}
}

Expand Down
27 changes: 14 additions & 13 deletions modules/playground/src/animate/app/animate-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ import {
} from '@angular/core';

@Component({
host: {
'[@backgroundAnimation]': "bgStatus"
},
selector: 'animate-app',
styleUrls: ['css/animate-app.css'],
template: `
<div [@backgroundAnimation]="bgStatus">
<button (click)="state='start'">Start State</button>
<button (click)="state='active'">Active State</button>
|
<button (click)="state='void'">Void State</button>
<button (click)="state='default'">Unhandled (default) State</button>
<button style="float:right" (click)="bgStatus='blur'">Blur Page</button>
<hr />
<div *ngFor="let item of items" class="box" [@boxAnimation]="state">
{{ item }}
<div *ngIf="true">
something inside
</div>
<button (click)="state='start'">Start State</button>
<button (click)="state='active'">Active State</button>
|
<button (click)="state='void'">Void State</button>
<button (click)="state='default'">Unhandled (default) State</button>
<button style="float:right" (click)="bgStatus='blur'">Blur Page (Host)</button>
<hr />
<div *ngFor="let item of items" class="box" [@boxAnimation]="state">
{{ item }}
<div *ngIf="true">
something inside
</div>
</div>
`,
Expand Down
11 changes: 11 additions & 0 deletions modules/playground/src/animate/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
<html>
<title>Animation Example</title>
<link rel="stylesheet" type="text/css" href="./css/app.css" />
<style>
animate-app {
display:block;
position:fixed;
top:0;
left:0;
right:0;
bottom:0;
padding:50px;
}
</style>
<body>
<animate-app>Loading...</animate-app>
<script src="../bootstrap.js"></script>
Expand Down

0 comments on commit 84cdaef

Please sign in to comment.