refactor(ivy): Switch styling to new reconcile algorithm (#34616)

NOTE: This change must be reverted with previous deletes so that it code remains in build-able state.

This change deletes old styling code and replaces it with a simplified styling algorithm.

The mental model for the new algorithm is:
- Create a linked list of styling bindings in the order of priority. All styling bindings ere executed in compiled order and than a linked list of bindings is created in priority order.
- Flush the style bindings at the end of `advance()` instruction. This implies that there are two flush events. One at the end of template `advance` instruction in the template. Second one at the end of `hostBindings` `advance` instruction when processing host bindings (if any).
- Each binding instructions effectively updates the string to represent the string at that location. Because most of the bindings are additive, this is a cheap strategy in most cases. In rare cases the strategy requires removing tokens from the styling up to this point. (We expect that to be rare case)S Because, the bindings are presorted in the order of priority, it is safe to resume the processing of the concatenated string from the last change binding.

PR Close #34616
This commit is contained in:
Miško Hevery
2020-01-24 12:23:00 -08:00
parent b4a711ea9f
commit 5aabe93abe
60 changed files with 2439 additions and 1413 deletions
+2 -2
View File
@@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2987,
"main-es2015": 462235,
"main-es2015": 456890,
"polyfills-es2015": 52503
}
}
@@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3097,
"main-es2015": 438671,
"main-es2015": 425216,
"polyfills-es2015": 52503
}
}
+5 -5
View File
@@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 18214,
"main-es2015": 16787,
"polyfills-es2015": 36808
}
}
@@ -30,7 +30,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 139487,
"main-es2015": 137226,
"polyfills-es2015": 37494
}
}
@@ -39,7 +39,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 268796,
"main-es2015": 254857,
"polyfills-es2015": 36808,
"5-es2015": 751
}
@@ -49,7 +49,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 228770,
"main-es2015": 226519,
"polyfills-es2015": 36808,
"5-es2015": 779
}
@@ -60,7 +60,7 @@
"uncompressed": {
"bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated",
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
"bundle": 176433
"bundle": 175498
}
}
}
@@ -18,7 +18,7 @@ import {BrowserModule} from '@angular/platform-browser';
<ng-template #t3><button [class.bar]="exp ==='bar'"></button></ng-template>
<ng-template #t4><button class="foo" [class.bar]="exp ==='bar'"></button></ng-template>
<ng-template #t5><button class="foo" [ngClass]="{bar: exp ==='bar'}"></button></ng-template>
<ng-template #t6><button class="foo" [ngStyle]="staticStyle" [style.background-color]="exp"></button></ng-template>
<ng-template #t6><button class="foo" [ngStyle]="staticStyle" [style.background-color]="exp == 'bar' ? 'yellow': 'red'"></button></ng-template>
<ng-template #t7><button style="color: red"></button></ng-template>
<ng-template #t8><button [style.width.px]="exp ==='bar' ? 10 : 20" [style.color]="exp"></button></ng-template>
<ng-template #t9><button style="width: 10px" [style.color]="exp"></button></ng-template>
@@ -25,7 +25,7 @@ export class TreeFunction {
type: TreeFunction,
selectors: [['tree']],
decls: 5,
vars: 1,
vars: 2,
template: function(rf: ɵRenderFlags, ctx: TreeFunction) {
// bit of a hack
TreeTpl(rf, ctx.data);
@@ -34,6 +34,7 @@ export class TreeFunction {
});
}
const TreeFunctionCmpDef = TreeFunction.ɵcmp as{decls: number, vars: number};
export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
if (rf & ɵRenderFlags.Create) {
ɵɵelementStart(0, 'tree');
@@ -54,7 +55,7 @@ export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
ɵɵcontainerRefreshStart(3);
{
if (ctx.left != null) {
let rf0 = ɵɵembeddedViewStart(0, 5, 1);
let rf0 = ɵɵembeddedViewStart(0, 5, 2);
{ TreeTpl(rf0, ctx.left); }
ɵɵembeddedViewEnd();
}
@@ -63,7 +64,7 @@ export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
ɵɵcontainerRefreshStart(4);
{
if (ctx.right != null) {
let rf0 = ɵɵembeddedViewStart(0, 5, 1);
let rf0 = ɵɵembeddedViewStart(0, TreeFunctionCmpDef.decls, TreeFunctionCmpDef.vars);
{ TreeTpl(rf0, ctx.right); }
ɵɵembeddedViewEnd();
}
@@ -157,4 +157,11 @@ export class NgClass implements DoCheck {
});
}
}
// TODO(misko): Delete this code after angula/flex-layout stops depending on private APIs
// We need to export this to make angular/flex-layout happy
// https://github.com/angular/flex-layout/blob/ec7b57eb6adf59ecfdfff1de5ccf1ab2f6652ed3/src/lib/extended/class/class.ts#L9
setClass(value: string) { this.klass = value; }
setNgClass(value: any) { this.ngClass = value; }
applyChanges() { this.ngDoCheck(); }
}
@@ -85,4 +85,10 @@ export class NgStyle implements DoCheck {
changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
}
// TODO(misko): Delete this code after angula/flex-layout stops depending on private APIs
// We need to export this to make angular/flex-layout happy
// https://github.com/angular/flex-layout/blob/ec7b57eb6adf59ecfdfff1de5ccf1ab2f6652ed3/src/lib/extended/class/class.ts#L9
setNgStyle(value: any) { this.ngStyle = value; }
applyChanges() { this.ngDoCheck(); }
}
+6
View File
@@ -6,5 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
// TODO(misko): Delete this code after angula/flex-layout stops depending on private APIs
// We need to export this to make angular/flex-layout happy
// https://github.com/angular/flex-layout/blob/ec7b57eb6adf59ecfdfff1de5ccf1ab2f6652ed3/src/lib/extended/class/class.ts#L9
export {NgClass as ɵNgClassImpl, NgClass as ɵNgClassR2Impl} from './directives/ng_class';
export {NgStyle as ɵNgStyleR2Impl} from './directives/ng_style';
export {DomAdapter as ɵDomAdapter, getDOM as ɵgetDOM, setRootDomAdapter as ɵsetRootDomAdapter} from './dom_adapter';
export {BrowserPlatformLocation as ɵBrowserPlatformLocation} from './location/platform_location';
@@ -196,7 +196,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
fixture = createTestComponent(`<div [ngClass]="['foo', {}]"></div>`);
expect(() => fixture !.detectChanges())
.toThrowError(
/NgClass can only toggle CSS classes expressed as strings, got: \[object Object\]/);
/NgClass can only toggle CSS classes expressed as strings, got \[object Object\]/);
});
});
@@ -372,6 +372,27 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
detectChangesAndExpectClassName('color-red');
});
it('should allow classes with trailing and leading spaces in [ngClass]', () => {
@Component({
template: `
<div leading-space [ngClass]="{' foo': applyClasses}"></div>
<div trailing-space [ngClass]="{'foo ': applyClasses}"></div>
`
})
class Cmp {
applyClasses = true;
}
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
const leading = fixture.nativeElement.querySelector('[leading-space]');
const trailing = fixture.nativeElement.querySelector('[trailing-space]');
expect(leading.className).toBe('foo');
expect(trailing.className).toBe('foo');
});
});
});
}
@@ -379,8 +400,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
condition: boolean = true;
// TODO(issue/24571): remove '!'.
items !: any[];
items: any[]|undefined;
arrExpr: string[] = ['foo'];
setExpr: Set<string> = new Set<string>();
objExpr: {[klass: string]: any}|null = {'foo': true, 'bar': false};
@@ -856,7 +856,7 @@ describe('compiler compliance: bindings', () => {
type: HostAttributeDir,
selectors: [["", "hostAttributeDir", ""]],
hostAttrs: ["title", "hello there from directive", ${AttributeMarker.Classes}, "one", "two", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"],
hostVars: 2,
hostVars: 4,
hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) {
…
}
@@ -1006,15 +1006,8 @@ describe('compiler compliance: styling', () => {
const template = `
hostAttrs: [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"],
hostVars: 6,
hostVars: 8,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
if (rf & 1) {
$r3$.ɵɵallocHostVars(8);
$r3$.ɵɵelementHostAttrs($e0_attrs$);
}
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClass);
@@ -1068,14 +1061,8 @@ describe('compiler compliance: styling', () => {
};
const template = `
hostVars: 8,
hostVars: 12,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
if (rf & 1) {
$r3$.ɵɵallocHostVars(12);
}
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClasses);
@@ -1143,14 +1130,8 @@ describe('compiler compliance: styling', () => {
`;
const hostBindings = `
hostVars: 6,
hostVars: 8,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
if (rf & 1) {
$r3$.ɵɵallocHostVars(8);
}
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵstyleMap(ctx.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClassExp);
@@ -1212,6 +1193,7 @@ describe('compiler compliance: styling', () => {
// NOTE: IF YOU ARE CHANGING THIS COMPILER SPEC, YOU MAY NEED TO CHANGE THE DIRECTIVE
// DEF THAT'S HARD-CODED IN `ng_class.ts`.
const template = `
…
hostVars: 2,
hostBindings: function ClassDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 2) {
@@ -1219,7 +1201,7 @@ describe('compiler compliance: styling', () => {
}
}
…
hostVars: 2,
hostVars: 4,
hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 2) {
$r3$.ɵɵstyleProp("width", ctx.myWidth);
@@ -1227,7 +1209,7 @@ describe('compiler compliance: styling', () => {
}
}
…
hostVars: 2,
hostVars: 4,
hostBindings: function HeightDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 2) {
$r3$.ɵɵstyleProp("height", ctx.myHeight);
@@ -1917,7 +1899,7 @@ describe('compiler compliance: styling', () => {
};
const template = `
hostVars: 4,
hostVars: 6,
hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
@@ -2079,14 +2061,8 @@ describe('compiler compliance: styling', () => {
};
const template = `
hostVars: 9,
hostVars: 10,
hostBindings: function MyDir_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
…
$r3$.ɵɵallocHostVars(10);
…
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵhostProperty("title", ctx.title);
$r3$.ɵɵupdateSyntheticHostBinding("@anim",
@@ -2340,7 +2340,7 @@ runInEachFileSystem(os => {
env.driveMain();
const jsContents = env.getContents('test.js');
const hostBindingsFn = `
hostVars: 3,
hostVars: 4,
hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); })("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody)("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
@@ -524,9 +524,10 @@ function isStyleSanitizable(prop: string): boolean {
// Note that browsers support both the dash case and
// camel case property names when setting through JS.
return prop === 'background-image' || prop === 'backgroundImage' || prop === 'background' ||
prop === 'border-image' || prop === 'borderImage' || prop === 'filter' ||
prop === 'list-style' || prop === 'listStyle' || prop === 'list-style-image' ||
prop === 'listStyleImage' || prop === 'clip-path' || prop === 'clipPath';
prop === 'border-image' || prop === 'borderImage' || prop === 'border-image-source' ||
prop === 'borderImageSource' || prop === 'filter' || prop === 'list-style' ||
prop === 'listStyle' || prop === 'list-style-image' || prop === 'listStyleImage' ||
prop === 'clip-path' || prop === 'clipPath';
}
/**
@@ -21,6 +21,11 @@ describe('style parsing', () => {
expect(result).toEqual(['width', '100px', 'height', '200px', 'opacity', '0']);
});
it('should allow empty values', () => {
const result = parseStyle('width:;height: ;');
expect(result).toEqual(['width', '', 'height', '']);
});
it('should trim values and properties', () => {
const result = parseStyle('width :333px ; height:666px ; opacity: 0.5;');
expect(result).toEqual(['width', '333px', 'height', '666px', 'opacity', '0.5']);
+5
View File
@@ -73,6 +73,11 @@ export function assertFirstCreatePass(tView: TView, errMessage?: string) {
tView.firstCreatePass, true, errMessage || 'Should only be called in first create pass.');
}
export function assertFirstUpdatePass(tView: TView, errMessage?: string) {
assertEqual(
tView.firstUpdatePass, true, errMessage || 'Should only be called in first update pass.');
}
/**
* This is a basic sanity check that an object is probably a directive def. DirectiveDef is
* an interface, so we can't do a direct instanceof check.
+18 -1
View File
@@ -30,7 +30,19 @@ export function getBinding(lView: LView, bindingIndex: number): any {
return lView[bindingIndex];
}
/** Updates binding if changed, then returns whether it was updated. */
/**
* Updates binding if changed, then returns whether it was updated.
*
* This function also checks the `CheckNoChangesMode` and throws if changes are made.
* Some changes (Objects/iterables) during `CheckNoChangesMode` are exempt to comply with VE
* behavior.
*
* @param lView current `LView`
* @param bindingIndex The binding in the `LView` to check
* @param value New value to check against `lView[bindingIndex]`
* @returns `true` if the bindings has changed. (Throws if binding has changed during
* `CheckNoChangesMode`)
*/
export function bindingUpdated(lView: LView, bindingIndex: number, value: any): boolean {
ngDevMode && assertNotSame(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
ngDevMode &&
@@ -50,6 +62,11 @@ export function bindingUpdated(lView: LView, bindingIndex: number, value: any):
throwErrorIfNoChangesMode(
oldValue === NO_CHANGE, details.oldValue, details.newValue, details.propName);
}
// There was a change, but the `devModeEqual` decided that the change is exempt from an error.
// For this reason we exit as if no change. The early exit is needed to prevent the changed
// value to be written into `LView` (If we would write the new value that we would not see it
// as change on next CD.)
return false;
}
lView[bindingIndex] = value;
return true;
+25 -17
View File
@@ -20,9 +20,10 @@ import {CLEAN_PROMISE, addHostBindingsToExpandoInstructions, addToViewTree, crea
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, incrementActiveDirectiveId, leaveView, setActiveHostElement} from './state';
import {enterView, getPreviousOrParentTNode, leaveView, setActiveHostElement} from './state';
import {writeDirectClass, writeDirectStyle} from './styling/reconcile';
import {computeStaticStyling} from './styling/static_styling';
import {setUpAttributes} from './util/attrs_utils';
import {publishDefaultGlobalUtils} from './util/global_utils';
@@ -119,8 +120,9 @@ export function renderComponent<T>(
// The first index of the first selector is the tag name.
const componentTag = componentDef.selectors ![0] ![0] as string;
const hostRenderer = rendererFactory.createRenderer(null, null);
const hostRNode =
locateHostElement(rendererFactory, opts.host || componentTag, componentDef.encapsulation);
locateHostElement(hostRenderer, opts.host || componentTag, componentDef.encapsulation);
const rootFlags = componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
LViewFlags.CheckAlways | LViewFlags.IsRoot;
const rootContext = createRootContext(opts.scheduler, opts.playerHandler);
@@ -137,7 +139,7 @@ export function renderComponent<T>(
try {
if (rendererFactory.begin) rendererFactory.begin();
const componentView = createRootComponentView(
hostRNode, componentDef, rootView, rendererFactory, renderer, sanitizer);
hostRNode, componentDef, rootView, rendererFactory, renderer, null, sanitizer);
component = createRootComponent(
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
@@ -160,14 +162,15 @@ export function renderComponent<T>(
* @param rNode Render host element.
* @param def ComponentDef
* @param rootView The parent view where the host node is stored
* @param renderer The current renderer
* @param hostRenderer The current renderer
* @param sanitizer The sanitizer, if provided
*
* @returns Component view created
*/
export function createRootComponentView(
rNode: RElement | null, def: ComponentDef<any>, rootView: LView,
rendererFactory: RendererFactory3, renderer: Renderer3, sanitizer?: Sanitizer | null): LView {
rendererFactory: RendererFactory3, hostRenderer: Renderer3, addVersion: string | null,
sanitizer: Sanitizer | null): LView {
const tView = rootView[TVIEW];
ngDevMode && assertDataInRange(rootView, 0 + HEADER_OFFSET);
rootView[0 + HEADER_OFFSET] = rNode;
@@ -176,13 +179,27 @@ export function createRootComponentView(
if (mergedAttrs !== null) {
computeStaticStyling(tNode, mergedAttrs);
if (rNode !== null) {
setUpAttributes(renderer, rNode, mergedAttrs);
setUpAttributes(hostRenderer, rNode, mergedAttrs);
if (tNode.classes !== null) {
writeDirectClass(hostRenderer, rNode, tNode.classes);
}
if (tNode.styles !== null) {
writeDirectStyle(hostRenderer, rNode, tNode.styles);
}
}
}
const viewRenderer = rendererFactory.createRenderer(rNode, def);
if (rNode !== null && addVersion) {
ngDevMode && ngDevMode.rendererSetAttribute++;
isProceduralRenderer(hostRenderer) ?
hostRenderer.setAttribute(rNode, 'ng-version', addVersion) :
rNode.setAttribute('ng-version', addVersion);
}
const componentView = createLView(
rootView, getOrCreateTComponentView(def), null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
rendererFactory, renderer, sanitizer);
rendererFactory, viewRenderer, sanitizer);
if (tView.firstCreatePass) {
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);
@@ -219,26 +236,17 @@ export function createRootComponent<T>(
}
const rootTNode = getPreviousOrParentTNode();
// TODO(misko-next): This is a temporary work around for the fact that we moved the information
// from instruction to declaration. The workaround is to just call the instruction as if it was
// part of the `hostAttrs`.
// The check for componentDef.hostBindings is wrong since now some directives may not
// have componentDef.hostBindings but they still need to process hostVars and hostAttrs
if (tView.firstCreatePass &&
(componentDef.hostBindings !== null || componentDef.hostAttrs !== null)) {
const elementIndex = rootTNode.index - HEADER_OFFSET;
setActiveHostElement(elementIndex);
incrementActiveDirectiveId();
const rootTView = rootLView[TVIEW];
addHostBindingsToExpandoInstructions(rootTView, componentDef);
growHostVarsSpace(rootTView, rootLView, componentDef.hostVars);
invokeHostBindingsInCreationMode(componentDef, component, rootTNode);
setActiveHostElement(null);
}
return component;
}
+7 -14
View File
@@ -19,7 +19,6 @@ import {RendererFactory2} from '../render/api';
import {Sanitizer} from '../sanitization/sanitizer';
import {VERSION} from '../version';
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider';
import {assertComponentType} from './assert';
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
import {getComponentDef} from './definition';
@@ -27,7 +26,7 @@ import {NodeInjector} from './di';
import {assignTViewNodeToLView, createLView, createTView, elementCreate, locateHostElement, renderView} from './instructions/shared';
import {ComponentDef} from './interfaces/definition';
import {TContainerNode, TElementContainerNode, TElementNode} from './interfaces/node';
import {RNode, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {RNode, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {LView, LViewFlags, TVIEW, TViewType} from './interfaces/view';
import {stringifyCSSSelectorList} from './node_selector_matcher';
import {enterView, leaveView} from './state';
@@ -132,8 +131,9 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
rootViewInjector.get(RendererFactory2, domRendererFactory3) as RendererFactory3;
const sanitizer = rootViewInjector.get(Sanitizer, null);
const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
const hostRNode = rootSelectorOrNode ?
locateHostElement(rendererFactory, rootSelectorOrNode, this.componentDef.encapsulation) :
locateHostElement(hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation) :
// Determine a tag name used for creating host elements when this component is created
// dynamically. Default to 'div' if this component did not specify any tag name in its
// selector.
@@ -152,20 +152,13 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
/^#root-ng-internal-isolated-\d+/.test(rootSelectorOrNode);
const rootContext = createRootContext();
const renderer = rendererFactory.createRenderer(hostRNode, this.componentDef);
if (rootSelectorOrNode && hostRNode) {
ngDevMode && ngDevMode.rendererSetAttribute++;
isProceduralRenderer(renderer) ?
renderer.setAttribute(hostRNode, 'ng-version', VERSION.full) :
hostRNode.setAttribute('ng-version', VERSION.full);
}
// Create the root view. Uses empty TView and ContentTemplate.
const rootTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, null);
const rootLView = createLView(
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, sanitizer,
rootViewInjector);
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, hostRenderer,
sanitizer, rootViewInjector);
const addVersion = rootSelectorOrNode && hostRNode ? VERSION.full : null;
// rootView is the parent when bootstrapping
// TODO(misko): it looks like we are entering view here but we don't really need to as
@@ -179,7 +172,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
try {
const componentView = createRootComponentView(
hostRNode, this.componentDef, rootLView, rendererFactory, renderer);
hostRNode, this.componentDef, rootLView, rendererFactory, hostRenderer, addVersion, null);
tElementNode = getTNode(0, rootLView) as TElementNode;
+2 -3
View File
@@ -29,7 +29,6 @@ import {enterDI, leaveDI} from './state';
import {isNameOnlyAttributeMarker} from './util/attrs_utils';
import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils';
import {stringifyForError} from './util/misc_utils';
import {getInitialStylingValue} from './util/styling_utils';
@@ -269,10 +268,10 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str
tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer);
ngDevMode && assertDefined(tNode, 'expecting tNode');
if (attrNameToInject === 'class') {
return getInitialStylingValue(tNode.classes);
return tNode.classes;
}
if (attrNameToInject === 'style') {
return getInitialStylingValue(tNode.styles);
return tNode.styles;
}
const attrs = tNode.attrs;
@@ -8,7 +8,7 @@
import {assertDataInRange, assertGreaterThan} from '../../util/assert';
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TVIEW} from '../interfaces/view';
import {ActiveElementFlags, executeElementExitFn, getCheckNoChangesMode, getLView, getSelectedIndex, hasActiveElementFlag, setSelectedIndex} from '../state';
import {executeElementExitFn, getCheckNoChangesMode, getLView, getSelectedIndex, setSelectedIndex} from '../state';
@@ -46,6 +46,7 @@ export function ɵɵadvance(delta: number): void {
* @codeGenApi
*/
export function ɵɵselect(index: number): void {
// TODO(misko): Remove this function as it is no longer being used.
selectIndexInternal(getLView(), index, getCheckNoChangesMode());
}
@@ -53,9 +54,7 @@ export function selectIndexInternal(lView: LView, index: number, checkNoChangesM
ngDevMode && assertGreaterThan(index, -1, 'Invalid index');
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
executeElementExitFn();
}
executeElementExitFn();
// Flush the initial hooks for elements in the view that have been added up to this point.
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
@@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {getLView, getSelectedIndex} from '../state';
import {getLView} from '../state';
import {CLASS_MAP_STYLING_KEY} from '../styling/style_binding_list';
import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation';
import {classMapInternal} from './styling';
import {checkStylingMap} from './styling';
@@ -37,7 +37,7 @@ import {classMapInternal} from './styling';
export function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string): void {
const lView = getLView();
const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -67,7 +67,7 @@ export function ɵɵclassMapInterpolate2(
prefix: string, v0: any, i0: string, v1: any, suffix: string): void {
const lView = getLView();
const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -100,7 +100,7 @@ export function ɵɵclassMapInterpolate3(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void {
const lView = getLView();
const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -136,7 +136,7 @@ export function ɵɵclassMapInterpolate4(
suffix: string): void {
const lView = getLView();
const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -175,7 +175,7 @@ export function ɵɵclassMapInterpolate5(
const lView = getLView();
const interpolatedValue =
interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -216,7 +216,7 @@ export function ɵɵclassMapInterpolate6(
const lView = getLView();
const interpolatedValue =
interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -259,7 +259,7 @@ export function ɵɵclassMapInterpolate7(
const lView = getLView();
const interpolatedValue =
interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -305,7 +305,7 @@ export function ɵɵclassMapInterpolate8(
const lView = getLView();
const interpolatedValue = interpolation8(
lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
/**
@@ -334,5 +334,5 @@ export function ɵɵclassMapInterpolate8(
export function ɵɵclassMapInterpolateV(values: any[]): void {
const lView = getLView();
const interpolatedValue = interpolationV(lView, values);
classMapInternal(getSelectedIndex(), interpolatedValue);
checkStylingMap(CLASS_MAP_STYLING_KEY, interpolatedValue, true);
}
@@ -10,20 +10,20 @@ import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert';
import {assertFirstCreatePass, assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
import {TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
import {TAttributes, TElementNode, TNode, TNodeType, hasClassInput, hasStyleInput} from '../interfaces/node';
import {RElement} from '../interfaces/renderer';
import {StylingMapArray, TStylingContext} from '../interfaces/styling';
import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks';
import {HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {decreaseElementDepthCount, getBindingIndex, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {decreaseElementDepthCount, getBindingIndex, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {writeDirectClass, writeDirectStyle} from '../styling/reconcile';
import {computeStaticStyling} from '../styling/static_styling';
import {setUpAttributes} from '../util/attrs_utils';
import {getInitialStylingValue, hasClassInput, hasStyleInput, selectClassBasedInputName} from '../util/styling_utils';
import {getConstant, getNativeByTNode, getTNode} from '../util/view_utils';
import {getConstant} from '../util/view_utils';
import {setDirectiveInputsWhichShadowsStyling} from './property';
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, matchingSchemas, resolveDirectives, saveResolvedLocalsInData} from './shared';
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, matchingSchemas, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
import {registerInitialStylingOnTNode} from './styling';
function elementStartFirstCreatePass(
index: number, tView: TView, lView: LView, native: RElement, name: string,
@@ -40,7 +40,7 @@ function elementStartFirstCreatePass(
ngDevMode && warnAboutUnknownElement(lView, native, tNode, hasDirectives);
if (tNode.mergedAttrs !== null) {
registerInitialStylingOnTNode(tNode, tNode.mergedAttrs, 0);
computeStaticStyling(tNode, tNode.mergedAttrs);
}
if (tView.queries !== null) {
@@ -88,8 +88,13 @@ export function ɵɵelementStart(
if (mergedAttrs !== null) {
setUpAttributes(renderer, native, mergedAttrs);
}
if ((tNode.flags & TNodeFlags.hasInitialStyling) === TNodeFlags.hasInitialStyling) {
renderInitialStyling(renderer, native, tNode, false);
const classes = tNode.classes;
if (classes !== null) {
writeDirectClass(renderer, native, classes);
}
const styles = tNode.styles;
if (styles !== null) {
writeDirectStyle(renderer, native, styles);
}
appendChild(native, tNode, lView);
@@ -143,17 +148,15 @@ export function ɵɵelementEnd(): void {
}
}
if (hasClassInput(tNode)) {
const inputName: string = selectClassBasedInputName(tNode.inputs !);
setDirectiveStylingInput(tNode.classes, lView, tNode.inputs ![inputName], inputName);
if (tNode.classes !== null && hasClassInput(tNode)) {
setDirectiveInputsWhichShadowsStyling(tNode, lView, tNode.classes, true);
}
if (hasStyleInput(tNode)) {
setDirectiveStylingInput(tNode.styles, lView, tNode.inputs !['style'], 'style');
if (tNode.styles !== null && hasStyleInput(tNode)) {
setDirectiveInputsWhichShadowsStyling(tNode, lView, tNode.styles, false);
}
}
/**
* Creates an empty element using {@link elementStart} and {@link elementEnd}
*
@@ -170,20 +173,6 @@ export function ɵɵelement(
ɵɵelementEnd();
}
function setDirectiveStylingInput(
context: TStylingContext | StylingMapArray | string | null, lView: LView,
stylingInputs: (string | number)[], propName: string) {
// older versions of Angular treat the input as `null` in the
// event that the value does not exist at all. For this reason
// we can't have a styling value be an empty string.
const value = (context && getInitialStylingValue(context)) || null;
// Ivy does an extra `[class]` write with a falsy value since the value
// is applied during creation mode. This is a deviation from VE and should
// be (Jira Issue = FW-1467).
setInputsForProperty(lView, stylingInputs, propName, value);
}
function warnAboutUnknownElement(
hostView: LView, element: RElement, tNode: TNode, hasDirectives: boolean): void {
const schemas = hostView[TVIEW].schemas;
@@ -15,10 +15,10 @@ import {HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from '../interface
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {computeStaticStyling} from '../styling/static_styling';
import {getConstant} from '../util/view_utils';
import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared';
import {registerInitialStylingOnTNode} from './styling';
function elementContainerStartFirstCreatePass(
index: number, tView: TView, lView: LView, attrsIndex?: number | null,
@@ -33,7 +33,7 @@ function elementContainerStartFirstCreatePass(
// While ng-container doesn't necessarily support styling, we use the style context to identify
// and execute directives on the ng-container.
if (attrs !== null) {
registerInitialStylingOnTNode(tNode, attrs, 0);
computeStaticStyling(tNode, attrs);
}
const localRefs = getConstant<string[]>(tViewConsts, localRefsIndex);
@@ -14,15 +14,13 @@ import {initNgDevMode} from '../../util/ng_dev_mode';
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection';
import {TQueries} from '../interfaces/query';
import {RComment, RElement, RNode} from '../interfaces/renderer';
import {TStylingContext, TStylingRange} from '../interfaces/styling';
import {TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate} from '../interfaces/styling';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, TViewType, T_HOST} from '../interfaces/view';
import {DebugNodeStyling, NodeStylingDebug} from '../styling/styling_debug';
import {attachDebugObject} from '../util/debug_utils';
import {isStylingContext} from '../util/styling_utils';
import {getLContainerActiveIndex, getTNode, unwrapRNode} from '../util/view_utils';
const NG_DEV_MODE = ((typeof ngDevMode === 'undefined' || !!ngDevMode) && initNgDevMode());
@@ -140,7 +138,7 @@ export const TViewConstructor = class TView implements ITView {
public components: number[]|null, //
public directiveRegistry: DirectiveDefList|null, //
public pipeRegistry: PipeDefList|null, //
public firstChild: TNode|null, //
public firstChild: ITNode|null, //
public schemas: SchemaMetadata[]|null, //
public consts: TConstants|null, //
) {}
@@ -152,7 +150,7 @@ export const TViewConstructor = class TView implements ITView {
}
};
export const TNodeConstructor = class TNode implements ITNode {
class TNode implements ITNode {
constructor(
public tView_: TView, //
public type: TNodeType, //
@@ -176,8 +174,8 @@ export const TNodeConstructor = class TNode implements ITNode {
public child: ITNode|null, //
public parent: TElementNode|TContainerNode|null, //
public projection: number|(ITNode|RNode[])[]|null, //
public styles: TStylingContext|null, //
public classes: TStylingContext|null, //
public styles: string|null, //
public classes: string|null, //
public classBindings: TStylingRange, //
public styleBindings: TStylingRange, //
) {}
@@ -206,7 +204,6 @@ export const TNodeConstructor = class TNode implements ITNode {
if (this.flags & TNodeFlags.hasClassInput) flags.push('TNodeFlags.hasClassInput');
if (this.flags & TNodeFlags.hasContentQuery) flags.push('TNodeFlags.hasContentQuery');
if (this.flags & TNodeFlags.hasStyleInput) flags.push('TNodeFlags.hasStyleInput');
if (this.flags & TNodeFlags.hasInitialStyling) flags.push('TNodeFlags.hasInitialStyling');
if (this.flags & TNodeFlags.hasHostBindings) flags.push('TNodeFlags.hasHostBindings');
if (this.flags & TNodeFlags.isComponentHost) flags.push('TNodeFlags.isComponentHost');
if (this.flags & TNodeFlags.isDirectiveHost) flags.push('TNodeFlags.isDirectiveHost');
@@ -233,9 +230,54 @@ export const TNodeConstructor = class TNode implements ITNode {
buf.push('</', this.tagName || this.type_, '>');
return buf.join('');
}
};
function processTNodeChildren(tNode: TNode | null, buf: string[]) {
get styleBindings_(): DebugStyleBindings { return toDebugStyleBinding(this, false); }
get classBindings_(): DebugStyleBindings { return toDebugStyleBinding(this, true); }
}
export const TNodeDebug = TNode;
export type TNodeDebug = TNode;
export interface DebugStyleBindings extends Array<DebugStyleBinding|string|null> {
[0]: string|null;
}
export interface DebugStyleBinding {
key: TStylingKey;
index: number;
isTemplate: boolean;
prevDuplicate: boolean;
nextDuplicate: boolean;
prevIndex: number;
nextIndex: number;
}
function toDebugStyleBinding(tNode: TNode, isClassBased: boolean): DebugStyleBindings {
const tData = tNode.tView_.data;
const bindings: DebugStyleBindings = [] as any;
const range = isClassBased ? tNode.classBindings : tNode.styleBindings;
const prev = getTStylingRangePrev(range);
const next = getTStylingRangeNext(range);
let isTemplate = next !== 0;
let cursor = isTemplate ? next : prev;
while (cursor !== 0) {
const itemKey = tData[cursor] as TStylingKey;
const itemRange = tData[cursor + 1] as TStylingRange;
bindings.unshift({
key: itemKey,
index: cursor,
isTemplate: isTemplate,
prevDuplicate: getTStylingRangePrevDuplicate(itemRange),
nextDuplicate: getTStylingRangeNextDuplicate(itemRange),
nextIndex: getTStylingRangeNext(itemRange),
prevIndex: getTStylingRangePrev(itemRange),
});
if (cursor === prev) isTemplate = false;
cursor = getTStylingRangePrev(itemRange);
}
bindings.unshift(isClassBased ? tNode.classes : tNode.styles);
return bindings;
}
function processTNodeChildren(tNode: ITNode | null, buf: string[]) {
while (tNode) {
buf.push((tNode as any as{template_: string}).template_);
tNode = tNode.next;
@@ -389,8 +431,6 @@ export class LViewDebug {
export interface DebugNode {
html: string|null;
native: Node;
styles: DebugNodeStyling|null;
classes: DebugNodeStyling|null;
nodes: DebugNode[]|null;
component: LViewDebug|null;
}
@@ -401,10 +441,10 @@ export interface DebugNode {
* @param tNode
* @param lView
*/
export function toDebugNodes(tNode: TNode | null, lView: LView): DebugNode[]|null {
export function toDebugNodes(tNode: ITNode | null, lView: LView): DebugNode[]|null {
if (tNode) {
const debugNodes: DebugNode[] = [];
let tNodeCursor: TNode|null = tNode;
let tNodeCursor: ITNode|null = tNode;
while (tNodeCursor) {
debugNodes.push(buildDebugNode(tNodeCursor, lView, tNodeCursor.index));
tNodeCursor = tNodeCursor.next;
@@ -415,19 +455,13 @@ export function toDebugNodes(tNode: TNode | null, lView: LView): DebugNode[]|nul
}
}
export function buildDebugNode(tNode: TNode, lView: LView, nodeIndex: number): DebugNode {
export function buildDebugNode(tNode: ITNode, lView: LView, nodeIndex: number): DebugNode {
const rawValue = lView[nodeIndex];
const native = unwrapRNode(rawValue);
const componentLViewDebug = toDebug(readLViewValue(rawValue));
const styles = isStylingContext(tNode.styles) ?
new NodeStylingDebug(tNode.styles as any as TStylingContext, tNode, lView, false) :
null;
const classes = isStylingContext(tNode.classes) ?
new NodeStylingDebug(tNode.classes as any as TStylingContext, tNode, lView, true) :
null;
return {
html: toHtml(native),
native: native as any, styles, classes,
native: native as any,
nodes: toDebugNodes(tNode.child, lView),
component: componentLViewDebug,
};
@@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {bindingUpdated} from '../bindings';
import {TNode} from '../interfaces/node';
import {SanitizerFn} from '../interfaces/sanitization';
import {TVIEW} from '../interfaces/view';
import {LView, TVIEW} from '../interfaces/view';
import {getLView, getSelectedIndex, nextBindingIndex} from '../state';
import {elementPropertyInternal, storePropertyBindingMetadata} from './shared';
import {elementPropertyInternal, setInputsForProperty, storePropertyBindingMetadata} from './shared';
/**
@@ -42,3 +42,16 @@ export function ɵɵproperty<T>(
}
return ɵɵproperty;
}
/**
* Given `<div style="..." my-dir>` and `MyDir` with `@Input('style')` we need to write to
* directive input.
*/
export function setDirectiveInputsWhichShadowsStyling(
tNode: TNode, lView: LView, value: any, isClassBased: boolean) {
const inputs = tNode.inputs !;
const property = isClassBased ? 'class' : 'style';
// We support both 'class' and `className` hence the fallback.
const stylingInputs = inputs[property] || (isClassBased && inputs['className']);
setInputsForProperty(lView, stylingInputs, property, value);
}
@@ -31,16 +31,14 @@ import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRoo
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {ActiveElementFlags, enterView, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingIndex, setBindingRoot, setCheckNoChangesMode, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {renderStylingMap, writeStylingValueDirectly} from '../styling/bindings';
import {clearActiveHostElement, enterView, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingIndex, setBindingRoot, setCheckNoChangesMode, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {NO_CHANGE} from '../tokens';
import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
import {getInitialStylingValue} from '../util/styling_utils';
import {getLViewParent} from '../util/view_traversal_utils';
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils';
import {selectIndexInternal} from './advance';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug';
@@ -102,15 +100,6 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
} else {
// If it's not a number, it's a host binding function that needs to be executed.
if (instruction !== null) {
// Each directive gets a uniqueId value that is the same for both
// create and update calls when the hostBindings function is called. The
// directive uniqueId is not set anywhere--it is just incremented between
// each hostBindings call and is useful for helping instruction code
// uniquely determine which directive is currently active when executed.
// It is important that this be called first before the actual instructions
// are run because this way the first directive ID value is not zero.
incrementActiveDirectiveId();
setBindingIndex(bindingRootIndex);
const hostCtx = lView[currentDirectiveIndex];
instruction(RenderFlags.Update, hostCtx, currentElementIndex);
@@ -123,10 +112,11 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
// iterate over those directives which actually have `hostBindings`.
currentDirectiveIndex++;
}
setBindingRoot(bindingRootIndex);
}
}
} finally {
setActiveHostElement(selectedIndex);
clearActiveHostElement();
}
}
@@ -493,9 +483,13 @@ export function refreshView<T>(
incrementInitPhaseFlags(lView, InitPhaseState.AfterViewInitHooksToBeRun);
}
}
} finally {
if (tView.firstUpdatePass === true) {
// We need to make sure that we only flip the flag on successful `refreshView` only
// Don't do this in `finally` block.
// If we did this in `finally` block then an exception could block the execution of styling
// instructions which in turn would be unable to insert themselves into the styling linked
// list. The result of this would be that if the exception would not be throw on subsequent CD
// the styling would be unable to process it data and reflect to the DOM.
tView.firstUpdatePass = false;
}
@@ -508,7 +502,7 @@ export function refreshView<T>(
if (!checkNoChangesMode) {
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
}
} finally {
leaveViewProcessExit();
}
}
@@ -538,7 +532,7 @@ function executeTemplate<T>(
lView: LView, templateFn: ComponentTemplate<T>, rf: RenderFlags, context: T) {
const prevSelectedIndex = getSelectedIndex();
try {
setActiveHostElement(null);
clearActiveHostElement();
if (rf & RenderFlags.Update && lView.length > HEADER_OFFSET) {
// When we're updating, inherently select 0 so we don't
// have to generate that instruction for most update blocks.
@@ -546,9 +540,7 @@ function executeTemplate<T>(
}
templateFn(rf, context);
} finally {
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
executeElementExitFn();
}
executeElementExitFn();
setSelectedIndex(prevSelectedIndex);
}
}
@@ -742,10 +734,8 @@ function assertHostNodeExists(rElement: RElement, elementOrSelector: RElement |
* @param encapsulation View Encapsulation defined for component that requests host element.
*/
export function locateHostElement(
rendererFactory: RendererFactory3, elementOrSelector: RElement | string,
renderer: Renderer3, elementOrSelector: RElement | string,
encapsulation: ViewEncapsulation): RElement {
const renderer = rendererFactory.createRenderer(null, null);
if (isProceduralRenderer(renderer)) {
// When using native Shadow DOM, do not clear host element to allow native slot projection
const preserveContent = encapsulation === ViewEncapsulation.ShadowDom;
@@ -814,7 +804,7 @@ export function createTNode(
adjustedIndex: number, tagName: string | null, attrs: TAttributes | null): TNode {
ngDevMode && ngDevMode.tNode++;
let injectorIndex = tParent ? tParent.injectorIndex : -1;
return ngDevMode ? new TNodeConstructor(
return ngDevMode ? new TNodeDebug(
tView, // tView_: TView
type, // type: TNodeType
adjustedIndex, // index: number
@@ -837,8 +827,8 @@ export function createTNode(
null, // child: ITNode|null
tParent, // parent: TElementNode|TContainerNode|null
null, // projection: number|(ITNode|RNode[])[]|null
null, // styles: TStylingContext|null
null, // classes: TStylingContext|null
null, // styles: string|null
null, // classes: string|null
0 as any, // classBindings: TStylingRange;
0 as any, // styleBindings: TStylingRange;
) :
@@ -1264,7 +1254,7 @@ function instantiateAllDirectives(
}
}
function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNode) {
function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
const expando = tView.expandoInstructions !;
@@ -1272,25 +1262,27 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
const elementIndex = tNode.index - HEADER_OFFSET;
try {
setActiveHostElement(elementIndex);
for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>;
const directive = viewData[i];
const directive = lView[i];
if (def.hostBindings !== null || def.hostVars !== 0 || def.hostAttrs !== null) {
// It is important that this be called first before the actual instructions
// are run because this way the first directive ID value is not zero.
incrementActiveDirectiveId();
invokeHostBindingsInCreationMode(def, directive, tNode);
} else if (firstCreatePass) {
expando.push(null);
}
}
} finally {
setActiveHostElement(null);
clearActiveHostElement();
}
}
// TODO(COMMIT): jsdoc
/**
* Invoke the host bindings in creation mode.
*
* @param def `DirectiveDef` which may contain the `hostBindings` function.
* @param directive Instance of directive.
* @param tNode Associated `TNode`.
*/
export function invokeHostBindingsInCreationMode(
def: DirectiveDef<any>, directive: any, tNode: TNode) {
if (def.hostBindings !== null) {
@@ -1300,11 +1292,11 @@ export function invokeHostBindingsInCreationMode(
}
/**
* Generates a new block in TView.expandoInstructions for this node.
*
* Each expando block starts with the element index (turned negative so we can distinguish
* it from the hostVar count) and the directive count. See more in VIEW_DATA.md.
*/
* Generates a new block in TView.expandoInstructions for this node.
*
* Each expando block starts with the element index (turned negative so we can distinguish
* it from the hostVar count) and the directive count. See more in VIEW_DATA.md.
*/
export function generateExpandoInstructionBlock(
tView: TView, tNode: TNode, directiveCount: number): void {
ngDevMode && assertEqual(
@@ -1993,4 +1985,3 @@ export function textBindingInternal(lView: LView, index: number, value: string):
const renderer = lView[RENDERER];
isProceduralRenderer(renderer) ? renderer.setValue(element, value) : element.textContent = value;
}
@@ -5,11 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {getLView, getSelectedIndex} from '../state';
import {getLView,} from '../state';
import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation';
import {stylePropInternal} from './styling';
import {checkStylingProperty} from './styling';
/**
@@ -43,7 +42,7 @@ export function ɵɵstylePropInterpolate1(
valueSuffix?: string | null): typeof ɵɵstylePropInterpolate1 {
const lView = getLView();
const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate1;
}
@@ -80,7 +79,7 @@ export function ɵɵstylePropInterpolate2(
valueSuffix?: string | null): typeof ɵɵstylePropInterpolate2 {
const lView = getLView();
const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate2;
}
@@ -119,7 +118,7 @@ export function ɵɵstylePropInterpolate3(
valueSuffix?: string | null): typeof ɵɵstylePropInterpolate3 {
const lView = getLView();
const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate3;
}
@@ -160,7 +159,7 @@ export function ɵɵstylePropInterpolate4(
v3: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate4 {
const lView = getLView();
const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate4;
}
@@ -205,7 +204,7 @@ export function ɵɵstylePropInterpolate5(
const lView = getLView();
const interpolatedValue =
interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate5;
}
@@ -252,7 +251,7 @@ export function ɵɵstylePropInterpolate6(
const lView = getLView();
const interpolatedValue =
interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate6;
}
@@ -302,7 +301,7 @@ export function ɵɵstylePropInterpolate7(
const lView = getLView();
const interpolatedValue =
interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate7;
}
@@ -354,7 +353,7 @@ export function ɵɵstylePropInterpolate8(
const lView = getLView();
const interpolatedValue = interpolation8(
lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolate8;
}
@@ -392,6 +391,6 @@ export function ɵɵstylePropInterpolateV(
prop: string, values: any[], valueSuffix?: string | null): typeof ɵɵstylePropInterpolateV {
const lView = getLView();
const interpolatedValue = interpolationV(lView, values);
stylePropInternal(getSelectedIndex(), prop, interpolatedValue as string, valueSuffix);
checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
return ɵɵstylePropInterpolateV;
}
File diff suppressed because it is too large Load Diff
+52 -131
View File
@@ -77,111 +77,6 @@ export const enum TNodeFlags {
* that actually have directives with host bindings.
*/
hasHostBindings = 0x80,
/** Bit #9 - This bit is set if the node has initial styling */
hasInitialStyling = 0x100,
/**
* Bit #10 - Whether or not there are class-based map bindings present.
*
* Examples include:
* 1. `<div [class]="x">`
* 2. `@HostBinding('class') x`
*/
hasClassMapBindings = 0x200,
/**
* Bit #11 - Whether or not there are any class-based prop bindings present.
*
* Examples include:
* 1. `<div [class.name]="x">`
* 2. `@HostBinding('class.name') x`
*/
hasClassPropBindings = 0x400,
/**
* Bit #12 - whether or not there are any active [class] and [class.name] bindings
*/
hasClassPropAndMapBindings = hasClassMapBindings | hasClassPropBindings,
/**
* Bit #13 - Whether or not the context contains one or more class-based template bindings.
*
* Examples include:
* 1. `<div [class]="x">`
* 2. `<div [class.name]="x">`
*/
hasTemplateClassBindings = 0x800,
/**
* Bit #14 - Whether or not the context contains one or more class-based host bindings.
*
* Examples include:
* 1. `@HostBinding('class') x`
* 2. `@HostBinding('class.name') x`
*/
hasHostClassBindings = 0x1000,
/**
* Bit #15 - Whether or not there are two or more sources for a class property in the context.
*
* Examples include:
* 1. prop + prop: `<div [class.active]="x" dir-that-sets-active-class>`
* 2. map + prop: `<div [class]="x" [class.foo]>`
* 3. map + map: `<div [class]="x" dir-that-sets-class>`
*/
hasDuplicateClassBindings = 0x2000,
/**
* Bit #16 - Whether or not there are style-based map bindings present.
*
* Examples include:
* 1. `<div [style]="x">`
* 2. `@HostBinding('style') x`
*/
hasStyleMapBindings = 0x4000,
/**
* Bit #17 - Whether or not there are any style-based prop bindings present.
*
* Examples include:
* 1. `<div [style.prop]="x">`
* 2. `@HostBinding('style.prop') x`
*/
hasStylePropBindings = 0x8000,
/**
* Bit #18 - whether or not there are any active [style] and [style.prop] bindings
*/
hasStylePropAndMapBindings = hasStyleMapBindings | hasStylePropBindings,
/**
* Bit #19 - Whether or not the context contains one or more style-based template bindings.
*
* Examples include:
* 1. `<div [style]="x">`
* 2. `<div [style.prop]="x">`
*/
hasTemplateStyleBindings = 0x10000,
/**
* Bit #20 - Whether or not the context contains one or more style-based host bindings.
*
* Examples include:
* 1. `@HostBinding('style') x`
* 2. `@HostBinding('style.prop') x`
*/
hasHostStyleBindings = 0x20000,
/**
* Bit #21 - Whether or not there are two or more sources for a style property in the context.
*
* Examples include:
* 1. prop + prop: `<div [style.width]="x" dir-that-sets-width>`
* 2. map + prop: `<div [style]="x" [style.prop]>`
* 3. map + map: `<div [style]="x" dir-that-sets-style>`
*/
hasDuplicateStyleBindings = 0x40000,
}
/**
@@ -588,20 +483,8 @@ export interface TNode {
* This field will be populated if and when:
*
* - There are one or more initial styles on an element (e.g. `<div style="width:200px">`)
* - There are one or more style bindings on an element (e.g. `<div [style.width]="w">`)
*
* If and when there are only initial styles (no bindings) then an instance of `StylingMapArray`
* will be used here. Otherwise an instance of `TStylingContext` will be created when there
* are one or more style bindings on an element.
*
* During element creation this value is likely to be populated with an instance of
* `StylingMapArray` and only when the bindings are evaluated (which happens during
* update mode) then it will be converted to a `TStylingContext` if any style bindings
* are encountered. If and when this happens then the existing `StylingMapArray` value
* will be placed into the initial styling slot in the newly created `TStylingContext`.
*/
// TODO(misko): `Remove StylingMapArray|TStylingContext|null` in follow up PR.
styles: StylingMapArray|TStylingContext|string|null;
styles: string|null;
/**
* A collection of all class bindings and/or static class values for an element.
@@ -609,20 +492,8 @@ export interface TNode {
* This field will be populated if and when:
*
* - There are one or more initial classes on an element (e.g. `<div class="one two three">`)
* - There are one or more class bindings on an element (e.g. `<div [class.foo]="f">`)
*
* If and when there are only initial classes (no bindings) then an instance of `StylingMapArray`
* will be used here. Otherwise an instance of `TStylingContext` will be created when there
* are one or more class bindings on an element.
*
* During element creation this value is likely to be populated with an instance of
* `StylingMapArray` and only when the bindings are evaluated (which happens during
* update mode) then it will be converted to a `TStylingContext` if any class bindings
* are encountered. If and when this happens then the existing `StylingMapArray` value
* will be placed into the initial styling slot in the newly created `TStylingContext`.
*/
// TODO(misko): `Remove StylingMapArray|TStylingContext|null` in follow up PR.
classes: StylingMapArray|TStylingContext|string|null;
classes: string|null;
/**
* Stores the head/tail index of the class bindings.
@@ -842,3 +713,53 @@ export type TNodeWithLocalRefs = TContainerNode | TElementNode | TElementContain
* - `<ng-template #tplRef>` - `tplRef` should point to the `TemplateRef` instance;
*/
export type LocalRefExtractor = (tNode: TNodeWithLocalRefs, currentView: LView) => any;
/**
* Returns `true` if the `TNode` has a directive which has `@Input()` for `class` binding.
*
* ```
* <div my-dir [class]="exp"></div>
* ```
* and
* ```
* @Directive({
* })
* class MyDirective {
* @Input()
* class: string;
* }
* ```
*
* In the above case it is necessary to write the reconciled styling information into the
* directive's input.
*
* @param tNode
*/
export function hasClassInput(tNode: TNode) {
return (tNode.flags & TNodeFlags.hasClassInput) !== 0;
}
/**
* Returns `true` if the `TNode` has a directive which has `@Input()` for `style` binding.
*
* ```
* <div my-dir [style]="exp"></div>
* ```
* and
* ```
* @Directive({
* })
* class MyDirective {
* @Input()
* class: string;
* }
* ```
*
* In the above case it is necessary to write the reconciled styling information into the
* directive's input.
*
* @param tNode
*/
export function hasStyleInput(tNode: TNode) {
return (tNode.flags & TNodeFlags.hasStyleInput) !== 0;
}
@@ -642,3 +642,8 @@ export function getTStylingRangeNextDuplicate(tStylingRange: TStylingRange): boo
export function setTStylingRangeNextDuplicate(tStylingRange: TStylingRange): TStylingRange {
return ((tStylingRange as any as number) | StylingRange.NEXT_DUPLICATE) as any;
}
export function getTStylingRangeTail(tStylingRange: TStylingRange): number {
const next = getTStylingRangeNext(tStylingRange);
return next === 0 ? getTStylingRangePrev(tStylingRange) : next;
}
+11 -1
View File
@@ -471,7 +471,17 @@ export interface TView {
/** Whether or not this template has been processed in creation mode. */
firstCreatePass: boolean;
/** Whether or not the first update for this template has been processed. */
/**
* Whether or not this template has been processed in update mode (e.g. change detected)
*
* `firstUpdatePass` is used by styling to set up `TData` to contain metadata about the styling
* instructions. (Mainly to build up a linked list of styling priority order.)
*
* Typically this function gets cleared after first execution. If exception is thrown then this
* flag can remain turned un until there is first successful (no exception) pass. This means that
* individual styling instructions keep track of if they have already been added to the linked
* list to prevent double adding.
*/
firstUpdatePass: boolean;
/** Static data equivalent of LView.data[]. Contains TNodes, PipeDefInternal or TI18n. */

Some files were not shown because too many files have changed in this diff Show More