From 297dc2bc02ac7a576baefd0046863bfd2129a78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Thu, 18 Oct 2018 14:47:53 -0700 Subject: [PATCH] fix(ivy): ensure `ngClass` works with [class] bindings (#26559) PR Close #26559 --- packages/core/src/render3/i18n.ts | 4 +- packages/core/src/render3/index.ts | 5 +- packages/core/src/render3/instructions.ts | 70 +++++++++++++------ packages/core/src/render3/interfaces/node.ts | 5 +- .../core/src/render3/interfaces/styling.ts | 24 ++++--- .../styling/class_and_style_bindings.ts | 35 +++++++--- packages/core/src/render3/tokens.ts | 15 ++++ .../bundle.golden_symbols.json | 9 +++ .../bundling/todo/bundle.golden_symbols.json | 9 +++ .../todo_r2/bundle.golden_symbols.json | 9 +++ .../core/test/render3/integration_spec.ts | 60 +++++++++++++++- packages/core/test/render3/properties_spec.ts | 3 +- 12 files changed, 199 insertions(+), 49 deletions(-) create mode 100644 packages/core/src/render3/tokens.ts diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 6fb9988309988..3d62a72c2db7c 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +import {NO_CHANGE} from '../../src/render3/tokens'; + import {assertEqual, assertLessThan} from './assert'; -import {NO_CHANGE, _getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, load, resetComponentState} from './instructions'; +import {_getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, load, resetComponentState} from './instructions'; import {LContainer, NATIVE, RENDER_PARENT} from './interfaces/container'; import {TElementNode, TNode, TNodeType} from './interfaces/node'; import {RComment, RElement} from './interfaces/renderer'; diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 65381ac09b67c..a34f2fec4a5e9 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -20,9 +20,6 @@ export {CssSelectorList} from './interfaces/projection'; // clang-format off export { - - NO_CHANGE, - bind, interpolation1, interpolation2, @@ -175,3 +172,5 @@ export { renderComponent, whenRendered, }; + +export {NO_CHANGE} from './tokens'; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 19580eedb33dc..059f146c5cd60 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -24,6 +24,7 @@ import {PlayerFactory} from './interfaces/player'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; +import {StylingIndex} from './interfaces/styling'; import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; @@ -31,6 +32,7 @@ import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; import {BoundPlayerFactory} from './styling/player_factory'; import {getStylingContext} from './styling/util'; +import {NO_CHANGE} from './tokens'; import {assertDataInRangeInternal, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isContentQueryHost, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util'; @@ -1337,14 +1339,7 @@ export function elementProperty( if (value === NO_CHANGE) return; const element = getNativeByIndex(index, viewData) as RElement | RComment; const tNode = getTNode(index, viewData); - // if tNode.inputs is undefined, a listener has created outputs, but inputs haven't - // yet been checked - if (tNode && tNode.inputs === undefined) { - // mark inputs as checked - tNode.inputs = generatePropertyAliases(tNode.flags, BindingDirection.Input); - } - - const inputData = tNode && tNode.inputs; + const inputData = initializeTNodeInputs(tNode); let dataValue: PropertyAliasValue|undefined; if (inputData && (dataValue = inputData[propName])) { setInputsForProperty(dataValue, value); @@ -1543,14 +1538,28 @@ export function elementStyling( styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, styleSanitizer?: StyleSanitizeFn | null): void { const tNode = previousOrParentTNode; + const inputData = initializeTNodeInputs(tNode); + if (!tNode.stylingTemplate) { + const hasClassInput = inputData && inputData.hasOwnProperty('class') ? true : false; + if (hasClassInput) { + tNode.flags |= TNodeFlags.hasClassInput; + } + // initialize the styling template. - tNode.stylingTemplate = - createStylingContextTemplate(classDeclarations, styleDeclarations, styleSanitizer); + tNode.stylingTemplate = createStylingContextTemplate( + classDeclarations, styleDeclarations, styleSanitizer, hasClassInput); } + if (styleDeclarations && styleDeclarations.length || classDeclarations && classDeclarations.length) { - elementStylingApply(tNode.index - HEADER_OFFSET); + const index = tNode.index - HEADER_OFFSET; + if (delegateToClassInput(tNode)) { + const stylingContext = getStylingContext(index, viewData); + const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string; + setInputsForProperty(tNode.inputs !['class'] !, initialClasses); + } + elementStylingApply(index); } } @@ -1640,9 +1649,17 @@ export function elementStyleProp( * removed (unset) from the element's styling. */ export function elementStylingMap( - index: number, classes: {[key: string]: any} | string | null, - styles?: {[styleName: string]: any} | null): void { - updateStylingMap(getStylingContext(index, viewData), classes, styles); + index: number, classes: {[key: string]: any} | string | NO_CHANGE | null, + styles?: {[styleName: string]: any} | NO_CHANGE | null): void { + const tNode = getTNode(index, viewData); + const stylingContext = getStylingContext(index, viewData); + if (delegateToClassInput(tNode) && classes !== NO_CHANGE) { + const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string; + const classInputVal = + (initialClasses.length ? (initialClasses + ' ') : '') + (classes as string); + setInputsForProperty(tNode.inputs !['class'] !, classInputVal); + } + updateStylingMap(stylingContext, classes, styles); } ////////////////////////// @@ -2577,14 +2594,6 @@ export function markDirty(component: T) { //// Bindings & interpolations /////////////////////////////// -export interface NO_CHANGE { - // This is a brand that ensures that this type can never match anything else - brand: 'NO_CHANGE'; -} - -/** A special value which designates that a value has not changed. */ -export const NO_CHANGE = {} as NO_CHANGE; - /** * Creates a single value binding. * @@ -2871,3 +2880,20 @@ function assertDataNext(index: number, arr?: any[]) { } export const CLEAN_PROMISE = _CLEAN_PROMISE; + +function initializeTNodeInputs(tNode: TNode | null) { + // If tNode.inputs is undefined, a listener has created outputs, but inputs haven't + // yet been checked. + if (tNode) { + if (tNode.inputs === undefined) { + // mark inputs as checked + tNode.inputs = generatePropertyAliases(tNode.flags, BindingDirection.Input); + } + return tNode.inputs; + } + return null; +} + +export function delegateToClassInput(tNode: TNode) { + return tNode.flags & TNodeFlags.hasClassInput; +} diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 8b8a56d6b194e..ee665b9bca035 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -39,8 +39,11 @@ export const enum TNodeFlags { /** This bit is set if the node has any content queries */ hasContentQuery = 0b00000000000000000100000000000000, + /** This bit is set if the node has any directives that contain [class properties */ + hasClassInput = 0b00000000000000001000000000000000, + /** The index of the first directive on this node is encoded on the most significant bits */ - DirectiveStartingIndexShift = 15, + DirectiveStartingIndexShift = 16, } /** diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index 880bc59ad0a2a..9259448a46eb0 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -156,7 +156,7 @@ export interface StylingContext extends Array { [0]: null; } */ export const enum StylingFlags { // Implies no configurations - None = 0b0000, + None = 0b00000, // Whether or not the entry or context itself is dirty - Dirty = 0b0001, + Dirty = 0b00001, // Whether or not this is a class-based assignment - Class = 0b0010, + Class = 0b00010, // Whether or not a sanitizer was applied to this property - Sanitize = 0b0100, + Sanitize = 0b00100, // Whether or not any player builders within need to produce new players - PlayerBuildersDirty = 0b1000, + PlayerBuildersDirty = 0b01000, + // If NgClass is present (or some other class handler) then it will handle the map expressions and + // initial classes + OnlyProcessSingleClasses = 0b10000, // The max amount of bits used to represent these configuration values - BitCountSize = 4, - // There are only three bits here + BitCountSize = 5, + // There are only five bits here BitMask = 0b1111 } @@ -211,8 +214,9 @@ export const enum StylingIndex { // Position of where the initial styles are stored in the styling context // This index must align with HOST, see interfaces/view.ts ElementPosition = 5, - // Position of where the last string-based CSS class value was stored - PreviousMultiClassValue = 6, + // Position of where the last string-based CSS class value was stored (or a cached version of the + // initial styles when a [class] directive is present) + PreviousOrCachedMultiClassValue = 6, // Position of where the last string-based CSS class value was stored PreviousMultiStyleValue = 7, // Location of single (prop) value entries are stored within the context diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts index c59e0857ccee8..86339f1102f64 100644 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ b/packages/core/src/render3/styling/class_and_style_bindings.ts @@ -11,6 +11,7 @@ import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerI import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer'; import {InitialStyles, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; import {LViewData, RootContext} from '../interfaces/view'; +import {NO_CHANGE} from '../tokens'; import {getRootContext} from '../util'; import {BoundPlayerFactory} from './player_factory'; @@ -45,7 +46,7 @@ const EMPTY_OBJ: {[key: string]: any} = {}; export function createStylingContextTemplate( initialClassDeclarations?: (string | boolean | InitialStylingFlags)[] | null, initialStyleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, - styleSanitizer?: StyleSanitizeFn | null): StylingContext { + styleSanitizer?: StyleSanitizeFn | null, onlyProcessSingleClasses?: boolean): StylingContext { const initialStylingValues: InitialStyles = [null]; const context: StylingContext = createEmptyStylingContext(null, styleSanitizer, initialStylingValues); @@ -80,6 +81,7 @@ export function createStylingContextTemplate( // make where the class offsets begin context[StylingIndex.ClassOffsetPosition] = totalStyleDeclarations; + const initialStaticClasses: string[]|null = onlyProcessSingleClasses ? [] : null; if (initialClassDeclarations) { let hasPassedDeclarations = false; for (let i = 0; i < initialClassDeclarations.length; i++) { @@ -93,6 +95,7 @@ export function createStylingContextTemplate( const value = initialClassDeclarations[++i] as boolean; initialStylingValues.push(value); classesLookup[className] = initialStylingValues.length - 1; + initialStaticClasses && initialStaticClasses.push(className); } else { classesLookup[className] = 0; } @@ -143,9 +146,15 @@ export function createStylingContextTemplate( // there is no initial value flag for the master index since it doesn't // reference an initial style value - setFlag(context, StylingIndex.MasterFlagPosition, pointers(0, 0, multiStart)); + const masterFlag = pointers(0, 0, multiStart) | + (onlyProcessSingleClasses ? StylingFlags.OnlyProcessSingleClasses : 0); + setFlag(context, StylingIndex.MasterFlagPosition, masterFlag); setContextDirty(context, initialStylingValues.length > 1); + if (initialStaticClasses) { + context[StylingIndex.PreviousOrCachedMultiClassValue] = initialStaticClasses.join(' '); + } + return context; } @@ -164,8 +173,8 @@ export function createStylingContextTemplate( */ export function updateStylingMap( context: StylingContext, classesInput: {[key: string]: any} | string | - BoundPlayerFactory| null, - stylesInput?: {[key: string]: any} | BoundPlayerFactory| + BoundPlayerFactory| NO_CHANGE | null, + stylesInput?: {[key: string]: any} | BoundPlayerFactory| NO_CHANGE | null): void { stylesInput = stylesInput || null; @@ -181,13 +190,14 @@ export function updateStylingMap( (classesInput as BoundPlayerFactory<{[key: string]: any}|string>) !.value : classesInput; const stylesValue = stylesPlayerBuilder ? stylesInput !.value : stylesInput; - // early exit (this is what's done to avoid using ctx.bind() to cache the value) - const ignoreAllClassUpdates = classesValue === context[StylingIndex.PreviousMultiClassValue]; - const ignoreAllStyleUpdates = stylesValue === context[StylingIndex.PreviousMultiStyleValue]; + const ignoreAllClassUpdates = limitToSingleClasses(context) || classesValue === NO_CHANGE || + classesValue === context[StylingIndex.PreviousOrCachedMultiClassValue]; + const ignoreAllStyleUpdates = + stylesValue === NO_CHANGE || stylesValue === context[StylingIndex.PreviousMultiStyleValue]; if (ignoreAllClassUpdates && ignoreAllStyleUpdates) return; - context[StylingIndex.PreviousMultiClassValue] = classesValue; + context[StylingIndex.PreviousOrCachedMultiClassValue] = classesValue; context[StylingIndex.PreviousMultiStyleValue] = stylesValue; let classNames: string[] = EMPTY_ARR; @@ -478,6 +488,8 @@ export function renderStyleAndClassBindings( const native = context[StylingIndex.ElementPosition] !; const multiStartIndex = getMultiStartIndex(context); const styleSanitizer = getStyleSanitizer(context); + const onlySingleClasses = limitToSingleClasses(context); + for (let i = StylingIndex.SingleStylesStartPosition; i < context.length; i += StylingIndex.Size) { // there is no point in rendering styles that have not changed on screen @@ -488,6 +500,7 @@ export function renderStyleAndClassBindings( const playerBuilder = getPlayerBuilder(context, i); const isClassBased = flag & StylingFlags.Class ? true : false; const isInSingleRegion = i < multiStartIndex; + const readInitialValue = !isClassBased || !onlySingleClasses; let valueToApply: string|boolean|null = value; @@ -506,7 +519,7 @@ export function renderStyleAndClassBindings( // note that this should always be a falsy check since `false` is used // for both class and style comparisons (styles can't be false and false // classes are turned off and should therefore defer to their initial values) - if (!valueExists(valueToApply, isClassBased)) { + if (!valueExists(valueToApply, isClassBased) && readInitialValue) { valueToApply = getInitialValue(context, flag); } @@ -765,6 +778,10 @@ export function isContextDirty(context: StylingContext): boolean { return isDirty(context, StylingIndex.MasterFlagPosition); } +export function limitToSingleClasses(context: StylingContext) { + return context[StylingIndex.MasterFlagPosition] & StylingFlags.OnlyProcessSingleClasses; +} + export function setContextDirty(context: StylingContext, isDirtyYes: boolean): void { setDirty(context, StylingIndex.MasterFlagPosition, isDirtyYes); } diff --git a/packages/core/src/render3/tokens.ts b/packages/core/src/render3/tokens.ts new file mode 100644 index 0000000000000..2ef57c0ab6edf --- /dev/null +++ b/packages/core/src/render3/tokens.ts @@ -0,0 +1,15 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * 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 + */ + +export interface NO_CHANGE { + // This is a brand that ensures that this type can never match anything else + brand: 'NO_CHANGE'; +} + +/** A special value which designates that a value has not changed. */ +export const NO_CHANGE = {} as NO_CHANGE; diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 6d2365b7348a6..0803942ab9d7b 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -446,6 +446,9 @@ { "name": "defineInjectable" }, + { + "name": "delegateToClassInput" + }, { "name": "destroyLView" }, @@ -740,6 +743,9 @@ { "name": "hasValueChanged" }, + { + "name": "initializeTNodeInputs" + }, { "name": "inject" }, @@ -833,6 +839,9 @@ { "name": "leaveView" }, + { + "name": "limitToSingleClasses" + }, { "name": "listener" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index b067ac070ce3f..5cec3c613e899 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -503,6 +503,9 @@ { "name": "defineInjectable" }, + { + "name": "delegateToClassInput" + }, { "name": "destroyLView" }, @@ -770,6 +773,9 @@ { "name": "hasValueChanged" }, + { + "name": "initializeTNodeInputs" + }, { "name": "inject" }, @@ -848,6 +854,9 @@ { "name": "leaveView" }, + { + "name": "limitToSingleClasses" + }, { "name": "listener" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 64f3526c9dd8d..43fe115bf60e0 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -1427,6 +1427,9 @@ { "name": "definePipe" }, + { + "name": "delegateToClassInput" + }, { "name": "destroyLView" }, @@ -1910,6 +1913,9 @@ { "name": "initDomAdapter" }, + { + "name": "initializeTNodeInputs" + }, { "name": "inject" }, @@ -2081,6 +2087,9 @@ { "name": "leaveView" }, + { + "name": "limitToSingleClasses" + }, { "name": "listener" }, diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 9426cd40d2d5b..e6fb1499ecd93 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -7,13 +7,13 @@ */ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; - import {RendererStyleFlags2, RendererType2} from '../../src/render/api'; import {AttributeMarker, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index'; -import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, enableBindings, disableBindings, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, enableBindings, disableBindings, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template, elementStylingMap} from '../../src/render3/instructions'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer'; +import {NO_CHANGE} from '../../src/render3/tokens'; import {HEADER_OFFSET, CONTEXT} from '../../src/render3/interfaces/view'; import {sanitizeUrl} from '../../src/sanitization/sanitization'; import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; @@ -1627,6 +1627,62 @@ describe('render3 integration test', () => { expect(fixture.html) .toEqual('Comp ContentTemp Content'); }); + + let mockClassDirective: DirWithClassDirective; + class DirWithClassDirective { + static ngDirectiveDef = defineDirective({ + type: DirWithClassDirective, + selectors: [['', 'DirWithClass', '']], + factory: () => mockClassDirective = new DirWithClassDirective(), + inputs: {'klass': 'class'} + }); + + public classesVal: string = ''; + set klass(value: string) { this.classesVal = value; } + } + + it('should delegate all initial classes to a [class] input binding if present on a directive on the same element', + () => { + /** + * + */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['DirWithClass']); + elementStyling([ + InitialStylingFlags.VALUES_MODE, 'apple', true, 'orange', true, 'banana', true + ]); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementStylingApply(0); + } + }, 1, 0, [DirWithClassDirective]); + + const fixture = new ComponentFixture(App); + expect(mockClassDirective !.classesVal).toEqual('apple orange banana'); + }); + + it('should update `[class]` and bindings in the provided directive if the input is matched', + () => { + /** + * + */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['DirWithClass']); + elementStyling(); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementStylingMap(0, 'cucumber grape'); + elementStylingApply(0); + } + }, 1, 0, [DirWithClassDirective]); + + const fixture = new ComponentFixture(App); + expect(mockClassDirective !.classesVal).toEqual('cucumber grape'); + }); }); }); diff --git a/packages/core/test/render3/properties_spec.ts b/packages/core/test/render3/properties_spec.ts index dc46b317ef09b..8c8f678851033 100644 --- a/packages/core/test/render3/properties_spec.ts +++ b/packages/core/test/render3/properties_spec.ts @@ -9,8 +9,9 @@ import {EventEmitter} from '@angular/core'; import {AttributeMarker, PublicFeature, defineComponent, template, defineDirective} from '../../src/render3/index'; -import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, load, reference, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, load, reference, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; +import {NO_CHANGE} from '../../src/render3/tokens'; import {pureFunction1, pureFunction2} from '../../src/render3/pure_function'; import {ComponentFixture, TemplateFixture, createComponent, renderToHtml, createDirective} from './render_util';