parent
d6cd041cbd
commit
d2dfd48be0
|
@ -164,6 +164,14 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
|||
component.get('encapsulation') !, this.reflector, this.checker) as string);
|
||||
}
|
||||
|
||||
let animations: any[]|null = null;
|
||||
if (component.has('animations')) {
|
||||
animations =
|
||||
(staticallyResolve(component.get('animations') !, this.reflector, this.checker) as any |
|
||||
null[]);
|
||||
animations = animations ? animations.map(entry => convertMapToStringMap(entry)) : null;
|
||||
}
|
||||
|
||||
return {
|
||||
analysis: {
|
||||
...metadata,
|
||||
|
@ -176,7 +184,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
|||
// analyzed and the full compilation scope for the component can be realized.
|
||||
pipes: EMPTY_MAP,
|
||||
directives: EMPTY_MAP,
|
||||
wrapDirectivesInClosure: false,
|
||||
wrapDirectivesInClosure: false, animations,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -224,3 +232,9 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
|||
return meta;
|
||||
}
|
||||
}
|
||||
|
||||
function convertMapToStringMap<T>(map: Map<string, T>): {[key: string]: T} {
|
||||
const stringMap: {[key: string]: T} = {};
|
||||
map.forEach((value: T, key: string) => { stringMap[key] = value; });
|
||||
return stringMap;
|
||||
}
|
||||
|
|
|
@ -100,6 +100,88 @@ describe('compiler compliance: styling', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('@Component.animations', () => {
|
||||
it('should pass in the component metadata animations into the component definition', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: "my-component",
|
||||
animations: [{name: 'foo123'}, {name: 'trigger123'}],
|
||||
template: ""
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors:[["my-component"]],
|
||||
factory:function MyComponent_Factory(t){
|
||||
return new (t || MyComponent)();
|
||||
},
|
||||
features: [$r3$.ɵPublicFeature],
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
},
|
||||
animations: [{name: "foo123"}, {name: "trigger123"}]
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should include animations even if the provided array is empty', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: "my-component",
|
||||
animations: [],
|
||||
template: ""
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
MyComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors:[["my-component"]],
|
||||
factory:function MyComponent_Factory(t){
|
||||
return new (t || MyComponent)();
|
||||
},
|
||||
features: [$r3$.ɵPublicFeature],
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
},
|
||||
animations: []
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[style] and [style.prop]', () => {
|
||||
it('should create style instructions on the element', () => {
|
||||
const files = {
|
||||
|
|
|
@ -189,6 +189,7 @@ export interface CompileTemplateSummary {
|
|||
ngContentSelectors: string[];
|
||||
encapsulation: ViewEncapsulation|null;
|
||||
styles: string[];
|
||||
animations: any[]|null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,7 +245,8 @@ export class CompileTemplateMetadata {
|
|||
return {
|
||||
ngContentSelectors: this.ngContentSelectors,
|
||||
encapsulation: this.encapsulation,
|
||||
styles: this.styles
|
||||
styles: this.styles,
|
||||
animations: this.animations
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,6 +175,11 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
|
|||
* into a shadow root.
|
||||
*/
|
||||
encapsulation: ViewEncapsulation;
|
||||
|
||||
/**
|
||||
* A collection of animation triggers that will be used in the component template.
|
||||
*/
|
||||
animations: {[key: string]: any}[]|null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,7 +27,7 @@ import {typeWithParameters} from '../util';
|
|||
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
||||
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
|
||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, mapToExpression, temporaryAllocator} from './util';
|
||||
|
||||
const EMPTY_ARRAY: any[] = [];
|
||||
|
||||
|
@ -246,6 +246,12 @@ export function compileComponentFromMetadata(
|
|||
definitionMap.set('styles', o.literalArr(strings));
|
||||
}
|
||||
|
||||
// e.g. `animations: [trigger('123', [])]`
|
||||
if (meta.animations) {
|
||||
const animationValues = meta.animations.map(entry => mapToExpression(entry));
|
||||
definitionMap.set('animations', o.literalArr(animationValues));
|
||||
}
|
||||
|
||||
// On the type side, remove newlines from the selector as it will need to fit into a TypeScript
|
||||
// string literal, which must be on one line.
|
||||
const selectorForType = (meta.selector || '').replace(/\n/g, '');
|
||||
|
@ -301,6 +307,7 @@ export function compileComponentFromRender2(
|
|||
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component);
|
||||
|
||||
const summary = component.toSummary();
|
||||
const animations = summary.template && summary.template.animations || null;
|
||||
|
||||
// Compute the R3ComponentMetadata from the CompileDirectiveMetadata
|
||||
const meta: R3ComponentMetadata = {
|
||||
|
@ -318,7 +325,8 @@ export function compileComponentFromRender2(
|
|||
wrapDirectivesInClosure: false,
|
||||
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
|
||||
encapsulation:
|
||||
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated
|
||||
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated,
|
||||
animations
|
||||
};
|
||||
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
||||
|
||||
|
|
|
@ -262,6 +262,11 @@ export function defineComponent<T>(componentDefinition: {
|
|||
* `PipeDefs`s. The function is necessary to be able to support forward declarations.
|
||||
*/
|
||||
pipes?: PipeTypesOrFactory | null;
|
||||
|
||||
/**
|
||||
* Registry of the animation triggers present on the component that will be used by the view.
|
||||
*/
|
||||
animations?: any[] | null;
|
||||
}): never {
|
||||
const type = componentDefinition.type;
|
||||
const pipeTypes = componentDefinition.pipes !;
|
||||
|
@ -269,6 +274,11 @@ export function defineComponent<T>(componentDefinition: {
|
|||
const declaredInputs: {[key: string]: string} = {} as any;
|
||||
const encapsulation = componentDefinition.encapsulation || ViewEncapsulation.Emulated;
|
||||
const styles: string[] = componentDefinition.styles || EMPTY_ARRAY;
|
||||
const animations: any[]|null = componentDefinition.animations || null;
|
||||
let data = componentDefinition.data || {};
|
||||
if (animations) {
|
||||
data.animations = animations;
|
||||
}
|
||||
const def: ComponentDefInternal<any> = {
|
||||
type: type,
|
||||
diPublic: null,
|
||||
|
@ -303,7 +313,7 @@ export function defineComponent<T>(componentDefinition: {
|
|||
selectors: componentDefinition.selectors,
|
||||
viewQuery: componentDefinition.viewQuery || null,
|
||||
features: componentDefinition.features || null,
|
||||
data: componentDefinition.data || EMPTY,
|
||||
data,
|
||||
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in the
|
||||
// next line. Also `None` should be 0 not 2.
|
||||
encapsulation,
|
||||
|
|
|
@ -76,7 +76,8 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
|||
viewQueries: [],
|
||||
wrapDirectivesInClosure: false,
|
||||
styles: metadata.styles || [],
|
||||
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated
|
||||
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated,
|
||||
animations: metadata.animations || null
|
||||
},
|
||||
constantPool, makeBindingParser());
|
||||
const preStatements = [...constantPool.statements, ...res.statements];
|
||||
|
|
|
@ -1394,6 +1394,54 @@ describe('render3 integration test', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('component animations', () => {
|
||||
it('should pass in the component styles directly into the underlying renderer', () => {
|
||||
const animA = {name: 'a'};
|
||||
const animB = {name: 'b'};
|
||||
|
||||
class AnimComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: AnimComp,
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
animations: [
|
||||
animA,
|
||||
animB,
|
||||
],
|
||||
selectors: [['foo']],
|
||||
factory: () => new AnimComp(),
|
||||
template: (rf: RenderFlags, ctx: AnimComp) => {}
|
||||
});
|
||||
}
|
||||
const rendererFactory = new MockRendererFactory();
|
||||
new ComponentFixture(AnimComp, {rendererFactory});
|
||||
|
||||
const capturedAnimations = rendererFactory.lastCapturedType !.data !['animations'];
|
||||
expect(Array.isArray(capturedAnimations)).toBeTruthy();
|
||||
expect(capturedAnimations.length).toEqual(2);
|
||||
expect(capturedAnimations).toContain(animA);
|
||||
expect(capturedAnimations).toContain(animB);
|
||||
});
|
||||
|
||||
it('should include animations in the renderType data array even if the array is empty', () => {
|
||||
class AnimComp {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: AnimComp,
|
||||
consts: 0,
|
||||
vars: 0,
|
||||
animations: [],
|
||||
selectors: [['foo']],
|
||||
factory: () => new AnimComp(),
|
||||
template: (rf: RenderFlags, ctx: AnimComp) => {}
|
||||
});
|
||||
}
|
||||
const rendererFactory = new MockRendererFactory();
|
||||
new ComponentFixture(AnimComp, {rendererFactory});
|
||||
const data = rendererFactory.lastCapturedType !.data;
|
||||
expect(data.animations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('element discovery', () => {
|
||||
it('should only monkey-patch immediate child nodes in a component', () => {
|
||||
class StructuredComp {
|
||||
|
|
Loading…
Reference in New Issue