parent
d6cd041cbd
commit
d2dfd48be0
|
@ -164,6 +164,14 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||||
component.get('encapsulation') !, this.reflector, this.checker) as string);
|
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 {
|
return {
|
||||||
analysis: {
|
analysis: {
|
||||||
...metadata,
|
...metadata,
|
||||||
|
@ -176,7 +184,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||||
// analyzed and the full compilation scope for the component can be realized.
|
// analyzed and the full compilation scope for the component can be realized.
|
||||||
pipes: EMPTY_MAP,
|
pipes: EMPTY_MAP,
|
||||||
directives: EMPTY_MAP,
|
directives: EMPTY_MAP,
|
||||||
wrapDirectivesInClosure: false,
|
wrapDirectivesInClosure: false, animations,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -224,3 +232,9 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||||
return meta;
|
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]', () => {
|
describe('[style] and [style.prop]', () => {
|
||||||
it('should create style instructions on the element', () => {
|
it('should create style instructions on the element', () => {
|
||||||
const files = {
|
const files = {
|
||||||
|
|
|
@ -189,6 +189,7 @@ export interface CompileTemplateSummary {
|
||||||
ngContentSelectors: string[];
|
ngContentSelectors: string[];
|
||||||
encapsulation: ViewEncapsulation|null;
|
encapsulation: ViewEncapsulation|null;
|
||||||
styles: string[];
|
styles: string[];
|
||||||
|
animations: any[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,7 +245,8 @@ export class CompileTemplateMetadata {
|
||||||
return {
|
return {
|
||||||
ngContentSelectors: this.ngContentSelectors,
|
ngContentSelectors: this.ngContentSelectors,
|
||||||
encapsulation: this.encapsulation,
|
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.
|
* into a shadow root.
|
||||||
*/
|
*/
|
||||||
encapsulation: ViewEncapsulation;
|
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 {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
||||||
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
|
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[] = [];
|
const EMPTY_ARRAY: any[] = [];
|
||||||
|
|
||||||
|
@ -246,6 +246,12 @@ export function compileComponentFromMetadata(
|
||||||
definitionMap.set('styles', o.literalArr(strings));
|
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
|
// 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.
|
// string literal, which must be on one line.
|
||||||
const selectorForType = (meta.selector || '').replace(/\n/g, '');
|
const selectorForType = (meta.selector || '').replace(/\n/g, '');
|
||||||
|
@ -301,6 +307,7 @@ export function compileComponentFromRender2(
|
||||||
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component);
|
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component);
|
||||||
|
|
||||||
const summary = component.toSummary();
|
const summary = component.toSummary();
|
||||||
|
const animations = summary.template && summary.template.animations || null;
|
||||||
|
|
||||||
// Compute the R3ComponentMetadata from the CompileDirectiveMetadata
|
// Compute the R3ComponentMetadata from the CompileDirectiveMetadata
|
||||||
const meta: R3ComponentMetadata = {
|
const meta: R3ComponentMetadata = {
|
||||||
|
@ -318,7 +325,8 @@ export function compileComponentFromRender2(
|
||||||
wrapDirectivesInClosure: false,
|
wrapDirectivesInClosure: false,
|
||||||
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
|
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
|
||||||
encapsulation:
|
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);
|
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.
|
* `PipeDefs`s. The function is necessary to be able to support forward declarations.
|
||||||
*/
|
*/
|
||||||
pipes?: PipeTypesOrFactory | null;
|
pipes?: PipeTypesOrFactory | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of the animation triggers present on the component that will be used by the view.
|
||||||
|
*/
|
||||||
|
animations?: any[] | null;
|
||||||
}): never {
|
}): never {
|
||||||
const type = componentDefinition.type;
|
const type = componentDefinition.type;
|
||||||
const pipeTypes = componentDefinition.pipes !;
|
const pipeTypes = componentDefinition.pipes !;
|
||||||
|
@ -269,6 +274,11 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
const declaredInputs: {[key: string]: string} = {} as any;
|
const declaredInputs: {[key: string]: string} = {} as any;
|
||||||
const encapsulation = componentDefinition.encapsulation || ViewEncapsulation.Emulated;
|
const encapsulation = componentDefinition.encapsulation || ViewEncapsulation.Emulated;
|
||||||
const styles: string[] = componentDefinition.styles || EMPTY_ARRAY;
|
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> = {
|
const def: ComponentDefInternal<any> = {
|
||||||
type: type,
|
type: type,
|
||||||
diPublic: null,
|
diPublic: null,
|
||||||
|
@ -303,7 +313,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
selectors: componentDefinition.selectors,
|
selectors: componentDefinition.selectors,
|
||||||
viewQuery: componentDefinition.viewQuery || null,
|
viewQuery: componentDefinition.viewQuery || null,
|
||||||
features: componentDefinition.features || 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
|
// 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.
|
// next line. Also `None` should be 0 not 2.
|
||||||
encapsulation,
|
encapsulation,
|
||||||
|
|
|
@ -76,7 +76,8 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
||||||
viewQueries: [],
|
viewQueries: [],
|
||||||
wrapDirectivesInClosure: false,
|
wrapDirectivesInClosure: false,
|
||||||
styles: metadata.styles || [],
|
styles: metadata.styles || [],
|
||||||
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated
|
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated,
|
||||||
|
animations: metadata.animations || null
|
||||||
},
|
},
|
||||||
constantPool, makeBindingParser());
|
constantPool, makeBindingParser());
|
||||||
const preStatements = [...constantPool.statements, ...res.statements];
|
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', () => {
|
describe('element discovery', () => {
|
||||||
it('should only monkey-patch immediate child nodes in a component', () => {
|
it('should only monkey-patch immediate child nodes in a component', () => {
|
||||||
class StructuredComp {
|
class StructuredComp {
|
||||||
|
|
Loading…
Reference in New Issue