feat(ivy): bridge component styles into the component renderer (#25255)
PR Close #25255
This commit is contained in:
@ -15,7 +15,7 @@ import {filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} f
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {ResourceLoader} from './api';
|
||||
import {extractDirectiveMetadata, extractQueriesFromDecorator, queriesFromFields} from './directive';
|
||||
import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive';
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {isAngularCore, unwrapExpression} from './util';
|
||||
|
||||
@ -135,11 +135,24 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||
viewQueries.push(...queriesFromDecorator.view);
|
||||
}
|
||||
|
||||
let styles: string[]|null = null;
|
||||
if (component.has('styles')) {
|
||||
styles = parseFieldArrayValue(component, 'styles', this.reflector, this.checker);
|
||||
}
|
||||
|
||||
let encapsulation: number = 0;
|
||||
if (component.has('encapsulation')) {
|
||||
encapsulation = parseInt(staticallyResolve(
|
||||
component.get('encapsulation') !, this.reflector, this.checker) as string);
|
||||
}
|
||||
|
||||
return {
|
||||
analysis: {
|
||||
...metadata,
|
||||
template,
|
||||
viewQueries,
|
||||
encapsulation,
|
||||
styles: styles || [],
|
||||
|
||||
// These will be replaced during the compilation step, after all `NgModule`s have been
|
||||
// analyzed and the full compilation scope for the component can be realized.
|
||||
|
@ -261,6 +261,22 @@ function isStringArrayOrDie(value: any, name: string): value is string[] {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function parseFieldArrayValue(
|
||||
directive: Map<string, ts.Expression>, field: string, reflector: ReflectionHost,
|
||||
checker: ts.TypeChecker): null|string[] {
|
||||
if (!directive.has(field)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Resolve the field of interest from the directive metadata to a string[].
|
||||
const value = staticallyResolve(directive.get(field) !, reflector, checker);
|
||||
if (!isStringArrayOrDie(value, field)) {
|
||||
throw new Error(`Failed to resolve @Directive.${field}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret property mapping fields on the decorator (e.g. inputs or outputs) and return the
|
||||
* correctly shaped metadata object.
|
||||
@ -268,16 +284,11 @@ function isStringArrayOrDie(value: any, name: string): value is string[] {
|
||||
function parseFieldToPropertyMapping(
|
||||
directive: Map<string, ts.Expression>, field: string, reflector: ReflectionHost,
|
||||
checker: ts.TypeChecker): {[field: string]: string} {
|
||||
if (!directive.has(field)) {
|
||||
const metaValues = parseFieldArrayValue(directive, field, reflector, checker);
|
||||
if (!metaValues) {
|
||||
return EMPTY_OBJECT;
|
||||
}
|
||||
|
||||
// Resolve the field of interest from the directive metadata to a string[].
|
||||
const metaValues = staticallyResolve(directive.get(field) !, reflector, checker);
|
||||
if (!isStringArrayOrDie(metaValues, field)) {
|
||||
throw new Error(`Failed to resolve @Directive.${field}`);
|
||||
}
|
||||
|
||||
return metaValues.reduce(
|
||||
(results, value) => {
|
||||
// Either the value is 'field' or 'field: property'. In the first case, `property` will
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InitialStylingFlags} from '@angular/compiler/src/core';
|
||||
import {InitialStylingFlags, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||
import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
|
||||
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
@ -18,6 +18,89 @@ describe('compiler compliance: styling', () => {
|
||||
compileAnimations: false,
|
||||
});
|
||||
|
||||
describe('@Component.styles', () => {
|
||||
it('should pass in the component metadata styles into the component definition and shim them using style encapsulation',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: "my-component",
|
||||
styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"],
|
||||
template: "..."
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template =
|
||||
'styles: ["div.foo[_ngcontent-%COMP%] { color: red; }", "[_nghost-%COMP%] p[_ngcontent-%COMP%]:nth-child(even) { --webkit-transition: 1s linear all; }"]';
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should pass in styles, but skip shimming the styles if the view encapsulation signals not to',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: "my-component",
|
||||
encapsulation: ${ViewEncapsulation.None},
|
||||
styles: ["div.tall { height: 123px; }", ":host.small p { height:5px; }"],
|
||||
template: "..."
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = 'div.tall { height: 123px; }", ":host.small p { height:5px; }';
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should pass in the component metadata styles into the component definition but skip shimming when style encapsulation is set to native',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
encapsulation: ${ViewEncapsulation.Native},
|
||||
selector: "my-component",
|
||||
styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"],
|
||||
template: "..."
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = 'div.cool { color: blue; }", ":host.nice p { color: gold; }';
|
||||
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 = {
|
||||
|
Reference in New Issue
Block a user