From 21a9a41c5bba5f95166db830366e94b72a118ee8 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sat, 11 Jan 2020 11:23:53 -0800 Subject: [PATCH] fix(common): cleanup the StylingDiffer and related code (#34307) Since I was learning the codebase and had a hard time understanding what was going on I've done a bunch of changes in one commit that under normal circumstances should have been split into several commits. Because this code is likely going to be overwritten with Misko's changes I'm not going to spend the time with trying to split this up. Overall I've done the following: - I processed review feedback from #34307 - I did a bunch of renaming to make the code easier to understand - I refactored some internal functions that were either inefficient or hard to read - I also updated lots of type signatures to correct them and to remove many casts in the code PR Close #34307 --- integration/_payload-limits.json | 2 +- .../common/src/directives/ng_class_impl.ts | 40 +-- .../common/src/directives/ng_style_impl.ts | 6 +- .../common/src/directives/styling_differ.ts | 297 ++++++++++-------- .../common/test/directives/ng_class_spec.ts | 18 +- .../common/test/directives/ng_style_spec.ts | 25 +- .../test/directives/styling_differ_spec.ts | 154 ++++----- .../ngcc/test/integration/ngcc_spec.ts | 3 +- 8 files changed, 309 insertions(+), 236 deletions(-) diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index f7ecc299a3..34682d492d 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -39,7 +39,7 @@ "master": { "uncompressed": { "runtime-es2015": 2289, - "main-es2015": 266648, + "main-es2015": 267182, "polyfills-es2015": 36808, "5-es2015": 751 } diff --git a/packages/common/src/directives/ng_class_impl.ts b/packages/common/src/directives/ng_class_impl.ts index ef48b17292..6232cfdf6f 100644 --- a/packages/common/src/directives/ng_class_impl.ts +++ b/packages/common/src/directives/ng_class_impl.ts @@ -26,7 +26,7 @@ export abstract class NgClassImpl { } @Injectable() -export class NgClassR2Impl implements NgClassImpl { +export class NgClassR2Impl extends NgClassImpl { // TODO(issue/24571): remove '!'. private _iterableDiffer !: IterableDiffer| null; // TODO(issue/24571): remove '!'. @@ -37,7 +37,9 @@ export class NgClassR2Impl implements NgClassImpl { constructor( private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers, - private _ngEl: ElementRef, private _renderer: Renderer2) {} + private _ngEl: ElementRef, private _renderer: Renderer2) { + super(); + } getValue() { return null; } @@ -48,7 +50,8 @@ export class NgClassR2Impl implements NgClassImpl { this._applyClasses(this._rawClass); } - setNgClass(value: string) { + + setNgClass(value: string|string[]|Set|{[klass: string]: any}) { this._removeClasses(this._rawClass); this._applyClasses(this._initialClasses); @@ -96,7 +99,7 @@ export class NgClassR2Impl implements NgClassImpl { this._toggleClass(record.item, true); } else { throw new Error( - `NgClass can only toggle CSS classes expressed as strings, got ${stringify(record.item)}`); + `NgClass can only toggle CSS classes expressed as strings, got: ${stringify(record.item)}`); } }); @@ -150,13 +153,13 @@ export class NgClassR2Impl implements NgClassImpl { } @Injectable() -export class NgClassR3Impl implements NgClassImpl { +export class NgClassR3Impl extends NgClassImpl { private _value: {[key: string]: boolean}|null = null; - private _ngClassDiffer = new StylingDiffer<{[key: string]: boolean}|null>( + private _ngClassDiffer = new StylingDiffer<{[key: string]: true}>( 'NgClass', StylingDifferOptions.TrimProperties| StylingDifferOptions.AllowSubKeys| StylingDifferOptions.AllowStringValue|StylingDifferOptions.ForceAsMap); - private _classStringDiffer: StylingDiffer<{[key: string]: boolean}>|null = null; + private _classStringDiffer: StylingDiffer<{[key: string]: true}>|null = null; getValue() { return this._value; } @@ -168,26 +171,23 @@ export class NgClassR3Impl implements NgClassImpl { this._classStringDiffer = this._classStringDiffer || new StylingDiffer('class', StylingDifferOptions.AllowStringValue | StylingDifferOptions.ForceAsMap); - this._classStringDiffer.setValue(value); + this._classStringDiffer.setInput(value); } setNgClass(value: string|string[]|Set|{[klass: string]: any}) { - this._ngClassDiffer.setValue(value); + this._ngClassDiffer.setInput(value); } applyChanges() { - const classChanged = - this._classStringDiffer ? this._classStringDiffer.hasValueChanged() : false; - const ngClassChanged = this._ngClassDiffer.hasValueChanged(); + const classChanged = this._classStringDiffer ? this._classStringDiffer.updateValue() : false; + const ngClassChanged = this._ngClassDiffer.updateValue(); if (classChanged || ngClassChanged) { - let value = this._ngClassDiffer.value; - if (this._classStringDiffer) { - let classValue = this._classStringDiffer.value; - if (classValue) { - value = value ? {...classValue, ...value} : classValue; - } - } - this._value = value; + let ngClassValue = this._ngClassDiffer.value; + let classValue = this._classStringDiffer ? this._classStringDiffer.value : null; + + // merge classValue and ngClassValue and set value + this._value = (classValue && ngClassValue) ? {...classValue, ...ngClassValue} : + classValue || ngClassValue; } } } diff --git a/packages/common/src/directives/ng_style_impl.ts b/packages/common/src/directives/ng_style_impl.ts index 5c0e87566d..e3c720ceaa 100644 --- a/packages/common/src/directives/ng_style_impl.ts +++ b/packages/common/src/directives/ng_style_impl.ts @@ -83,16 +83,16 @@ export class NgStyleR2Impl implements NgStyleImpl { @Injectable() export class NgStyleR3Impl implements NgStyleImpl { private _differ = - new StylingDiffer<{[key: string]: any}|null>('NgStyle', StylingDifferOptions.AllowUnits); + new StylingDiffer<{[key: string]: any}>('NgStyle', StylingDifferOptions.AllowUnits); private _value: {[key: string]: any}|null = null; getValue() { return this._value; } - setNgStyle(value: {[key: string]: any}|null) { this._differ.setValue(value); } + setNgStyle(value: {[key: string]: any}|null) { this._differ.setInput(value); } applyChanges() { - if (this._differ.hasValueChanged()) { + if (this._differ.updateValue()) { this._value = this._differ.value; } } diff --git a/packages/common/src/directives/styling_differ.ts b/packages/common/src/directives/styling_differ.ts index 792ab1a44a..bb4f13910c 100644 --- a/packages/common/src/directives/styling_differ.ts +++ b/packages/common/src/directives/styling_differ.ts @@ -13,7 +13,7 @@ * of how [style] and [class] behave in Angular. * * The differences are: - * - ngStyle and ngClass both **watch** their binding values for changes each time CD runs + * - ngStyle and ngClass both **deep-watch** their binding values for changes each time CD runs * while [style] and [class] bindings do not (they check for identity changes) * - ngStyle allows for unit-based keys (e.g. `{'max-width.px':value}`) and [style] does not * - ngClass supports arrays of class values and [class] only accepts map and string values @@ -24,8 +24,9 @@ * and unnecessary. Instead, ngClass and ngStyle should have their input values be converted * into something that the core-level [style] and [class] bindings understand. * - * This [StylingDiffer] class handles this conversion by creating a new input value each time - * the inner representation of the binding value have changed. + * This [StylingDiffer] class handles this conversion by creating a new output value each time + * the input value of the binding value has changed (either via identity change or deep collection + * content change). * * ## Why do we care about ngStyle/ngClass? * The styling algorithm code (documented inside of `render3/interfaces/styling.ts`) needs to @@ -40,8 +41,8 @@ * - If ngStyle/ngClass is used in combination with [style]/[class] bindings then the * styles and classes would fall out of sync and be applied and updated at * inconsistent times - * - Both ngClass/ngStyle do not respect [class.name] and [style.prop] bindings - * (they will write over them given the right combination of events) + * - Both ngClass/ngStyle should respect [class.name] and [style.prop] bindings (and not arbitrarily + * overwrite their changes) * * ``` *