feat(ivy): convert [ngStyle] and [ngClass] to use ivy styling bindings (#28711)
Prior to this fix, both the `NgStyle` and `NgClass` directives made use of `Renderer2` and this dependency raised issues for future versions of Angular that cannot inject it. This patch ensures that there are two versions of both directives: one for the VE and another for Ivy. Jira Issue: FW-882 PR Close #28711
This commit is contained in:
parent
d0e81eb593
commit
cfb2d176f8
|
@ -21,7 +21,7 @@
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime": 1440,
|
"runtime": 1440,
|
||||||
"main": 194626,
|
"main": 207765,
|
||||||
"polyfills": 38390
|
"polyfills": 38390
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,15 @@ export {registerLocaleData} from './i18n/locale_data';
|
||||||
export {Plural, NumberFormatStyle, FormStyle, Time, TranslationWidth, FormatWidth, NumberSymbol, WeekDay, getNumberOfCurrencyDigits, getCurrencySymbol, getLocaleDayPeriods, getLocaleDayNames, getLocaleMonthNames, getLocaleId, getLocaleEraNames, getLocaleWeekEndRange, getLocaleFirstDayOfWeek, getLocaleDateFormat, getLocaleDateTimeFormat, getLocaleExtraDayPeriodRules, getLocaleExtraDayPeriods, getLocalePluralCase, getLocaleTimeFormat, getLocaleNumberSymbol, getLocaleNumberFormat, getLocaleCurrencyName, getLocaleCurrencySymbol} from './i18n/locale_data_api';
|
export {Plural, NumberFormatStyle, FormStyle, Time, TranslationWidth, FormatWidth, NumberSymbol, WeekDay, getNumberOfCurrencyDigits, getCurrencySymbol, getLocaleDayPeriods, getLocaleDayNames, getLocaleMonthNames, getLocaleId, getLocaleEraNames, getLocaleWeekEndRange, getLocaleFirstDayOfWeek, getLocaleDateFormat, getLocaleDateTimeFormat, getLocaleExtraDayPeriodRules, getLocaleExtraDayPeriods, getLocalePluralCase, getLocaleTimeFormat, getLocaleNumberSymbol, getLocaleNumberFormat, getLocaleCurrencyName, getLocaleCurrencySymbol} from './i18n/locale_data_api';
|
||||||
export {parseCookieValue as ɵparseCookieValue} from './cookie';
|
export {parseCookieValue as ɵparseCookieValue} from './cookie';
|
||||||
export {CommonModule, DeprecatedI18NPipesModule} from './common_module';
|
export {CommonModule, DeprecatedI18NPipesModule} from './common_module';
|
||||||
export {NgClass, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
|
export {NgClass, NgClassBase, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgStyleBase, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
|
||||||
export {DOCUMENT} from './dom_tokens';
|
export {DOCUMENT} from './dom_tokens';
|
||||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe, KeyValuePipe, KeyValue} from './pipes/index';
|
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe, KeyValuePipe, KeyValue} from './pipes/index';
|
||||||
export {DeprecatedDatePipe, DeprecatedCurrencyPipe, DeprecatedDecimalPipe, DeprecatedPercentPipe} from './pipes/deprecated/index';
|
export {DeprecatedDatePipe, DeprecatedCurrencyPipe, DeprecatedDecimalPipe, DeprecatedPercentPipe} from './pipes/deprecated/index';
|
||||||
export {PLATFORM_BROWSER_ID as ɵPLATFORM_BROWSER_ID, PLATFORM_SERVER_ID as ɵPLATFORM_SERVER_ID, PLATFORM_WORKER_APP_ID as ɵPLATFORM_WORKER_APP_ID, PLATFORM_WORKER_UI_ID as ɵPLATFORM_WORKER_UI_ID, isPlatformBrowser, isPlatformServer, isPlatformWorkerApp, isPlatformWorkerUi} from './platform_id';
|
export {PLATFORM_BROWSER_ID as ɵPLATFORM_BROWSER_ID, PLATFORM_SERVER_ID as ɵPLATFORM_SERVER_ID, PLATFORM_WORKER_APP_ID as ɵPLATFORM_WORKER_APP_ID, PLATFORM_WORKER_UI_ID as ɵPLATFORM_WORKER_UI_ID, isPlatformBrowser, isPlatformServer, isPlatformWorkerApp, isPlatformWorkerUi} from './platform_id';
|
||||||
export {VERSION} from './version';
|
export {VERSION} from './version';
|
||||||
export {ViewportScroller, NullViewportScroller as ɵNullViewportScroller} from './viewport_scroller';
|
export {ViewportScroller, NullViewportScroller as ɵNullViewportScroller} from './viewport_scroller';
|
||||||
|
|
||||||
|
export {NgClassImplProvider__POST_R3__ as ɵNgClassImplProvider__POST_R3__, NgClassR2Impl as ɵNgClassR2Impl, NgClassImpl as ɵNgClassImpl} from './directives/ng_class_impl';
|
||||||
|
export {NgStyleImplProvider__POST_R3__ as ɵNgStyleImplProvider__POST_R3__, NgStyleR2Impl as ɵNgStyleR2Impl, NgStyleImpl as ɵNgStyleImpl} from './directives/ng_style_impl';
|
||||||
|
export {ngStyleDirectiveDef__POST_R3__ as ɵngStyleDirectiveDef__POST_R3__} from './directives/ng_style';
|
||||||
|
export {ngClassDirectiveDef__POST_R3__ as ɵngClassDirectiveDef__POST_R3__} from './directives/ng_class';
|
||||||
|
|
|
@ -7,18 +7,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Provider} from '@angular/core';
|
import {Provider} from '@angular/core';
|
||||||
|
import {NgClass, NgClassBase} from './ng_class';
|
||||||
import {NgClass} from './ng_class';
|
|
||||||
import {NgComponentOutlet} from './ng_component_outlet';
|
import {NgComponentOutlet} from './ng_component_outlet';
|
||||||
import {NgForOf, NgForOfContext} from './ng_for_of';
|
import {NgForOf, NgForOfContext} from './ng_for_of';
|
||||||
import {NgIf, NgIfContext} from './ng_if';
|
import {NgIf, NgIfContext} from './ng_if';
|
||||||
import {NgPlural, NgPluralCase} from './ng_plural';
|
import {NgPlural, NgPluralCase} from './ng_plural';
|
||||||
import {NgStyle} from './ng_style';
|
import {NgStyle, NgStyleBase} from './ng_style';
|
||||||
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from './ng_switch';
|
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from './ng_switch';
|
||||||
import {NgTemplateOutlet} from './ng_template_outlet';
|
import {NgTemplateOutlet} from './ng_template_outlet';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
NgClass,
|
NgClass,
|
||||||
|
NgClassBase,
|
||||||
NgComponentOutlet,
|
NgComponentOutlet,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
NgForOfContext,
|
NgForOfContext,
|
||||||
|
@ -27,10 +27,11 @@ export {
|
||||||
NgPlural,
|
NgPlural,
|
||||||
NgPluralCase,
|
NgPluralCase,
|
||||||
NgStyle,
|
NgStyle,
|
||||||
|
NgStyleBase,
|
||||||
NgSwitch,
|
NgSwitch,
|
||||||
NgSwitchCase,
|
NgSwitchCase,
|
||||||
NgSwitchDefault,
|
NgSwitchDefault,
|
||||||
NgTemplateOutlet
|
NgTemplateOutlet,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,68 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {Directive, DoCheck, Input, ɵRenderFlags, ɵdefineDirective, ɵelementStyling, ɵelementStylingApply, ɵelementStylingMap} from '@angular/core';
|
||||||
|
|
||||||
import {Directive, DoCheck, ElementRef, Input, IterableChanges, IterableDiffer, IterableDiffers, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer2, ɵisListLikeIterable as isListLikeIterable, ɵstringify as stringify} from '@angular/core';
|
import {NgClassImpl, NgClassImplProvider} from './ng_class_impl';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NgClass (as well as NgStyle) behaves differently when loaded in the VE and when not.
|
||||||
|
*
|
||||||
|
* If the VE is present (which is for older versions of Angular) then NgClass will inject
|
||||||
|
* the legacy diffing algorithm as a service and delegate all styling changes to that.
|
||||||
|
*
|
||||||
|
* If the VE is not present then NgStyle will normalize (through the injected service) and
|
||||||
|
* then write all styling changes to the `[style]` binding directly (through a host binding).
|
||||||
|
* Then Angular will notice the host binding change and treat the changes as styling
|
||||||
|
* changes and apply them via the core styling instructions that exist within Angular.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// used when the VE is present
|
||||||
|
export const ngClassDirectiveDef__PRE_R3__ = undefined;
|
||||||
|
|
||||||
|
// used when the VE is not present (note the directive will
|
||||||
|
// never be instantiated normally because it is apart of a
|
||||||
|
// base class)
|
||||||
|
export const ngClassDirectiveDef__POST_R3__ = ɵdefineDirective({
|
||||||
|
type: function() {} as any,
|
||||||
|
selectors: null as any,
|
||||||
|
factory: () => {},
|
||||||
|
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
|
||||||
|
if (rf & ɵRenderFlags.Create) {
|
||||||
|
ɵelementStyling(null, null, null, ctx);
|
||||||
|
}
|
||||||
|
if (rf & ɵRenderFlags.Update) {
|
||||||
|
ɵelementStylingMap(elIndex, ctx.getValue(), null, ctx);
|
||||||
|
ɵelementStylingApply(elIndex, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ngClassDirectiveDef = ngClassDirectiveDef__PRE_R3__;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serves as the base non-VE container for NgClass.
|
||||||
|
*
|
||||||
|
* While this is a base class that NgClass extends from, the
|
||||||
|
* class itself acts as a container for non-VE code to setup
|
||||||
|
* a link to the `[class]` host binding (via the static
|
||||||
|
* `ngDirectiveDef` property on the class).
|
||||||
|
*
|
||||||
|
* Note that the `ngDirectiveDef` property's code is switched
|
||||||
|
* depending if VE is present or not (this allows for the
|
||||||
|
* binding code to be set only for newer versions of Angular).
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
|
*/
|
||||||
|
export class NgClassBase {
|
||||||
|
static ngDirectiveDef: any = ngClassDirectiveDef;
|
||||||
|
|
||||||
|
constructor(protected _delegate: NgClassImpl) {}
|
||||||
|
|
||||||
|
getValue() { return this._delegate.getValue(); }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
|
@ -36,126 +96,17 @@ import {Directive, DoCheck, ElementRef, Input, IterableChanges, IterableDiffer,
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Directive({selector: '[ngClass]'})
|
@Directive({selector: '[ngClass]', providers: [NgClassImplProvider]})
|
||||||
export class NgClass implements DoCheck {
|
export class NgClass extends NgClassBase implements DoCheck {
|
||||||
// TODO(issue/24571): remove '!'.
|
constructor(delegate: NgClassImpl) { super(delegate); }
|
||||||
private _iterableDiffer !: IterableDiffer<string>| null;
|
|
||||||
// TODO(issue/24571): remove '!'.
|
|
||||||
private _keyValueDiffer !: KeyValueDiffer<string, any>| null;
|
|
||||||
private _initialClasses: string[] = [];
|
|
||||||
// TODO(issue/24571): remove '!'.
|
|
||||||
private _rawClass !: string[] | Set<string>| {[klass: string]: any};
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
|
||||||
private _ngEl: ElementRef, private _renderer: Renderer2) {}
|
|
||||||
|
|
||||||
@Input('class')
|
@Input('class')
|
||||||
set klass(value: string) {
|
set klass(value: string) { this._delegate.setClass(value); }
|
||||||
this._removeClasses(this._initialClasses);
|
|
||||||
this._initialClasses = typeof value === 'string' ? value.split(/\s+/) : [];
|
|
||||||
this._applyClasses(this._initialClasses);
|
|
||||||
this._applyClasses(this._rawClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
@Input('ngClass')
|
||||||
set ngClass(value: string|string[]|Set<string>|{[klass: string]: any}) {
|
set ngClass(value: string|string[]|Set<string>|{[klass: string]: any}) {
|
||||||
this._removeClasses(this._rawClass);
|
this._delegate.setNgClass(value);
|
||||||
this._applyClasses(this._initialClasses);
|
|
||||||
|
|
||||||
this._iterableDiffer = null;
|
|
||||||
this._keyValueDiffer = null;
|
|
||||||
|
|
||||||
this._rawClass = typeof value === 'string' ? value.split(/\s+/) : value;
|
|
||||||
|
|
||||||
if (this._rawClass) {
|
|
||||||
if (isListLikeIterable(this._rawClass)) {
|
|
||||||
this._iterableDiffer = this._iterableDiffers.find(this._rawClass).create();
|
|
||||||
} else {
|
|
||||||
this._keyValueDiffer = this._keyValueDiffers.find(this._rawClass).create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngDoCheck(): void {
|
ngDoCheck() { this._delegate.applyChanges(); }
|
||||||
if (this._iterableDiffer) {
|
|
||||||
const iterableChanges = this._iterableDiffer.diff(this._rawClass as string[]);
|
|
||||||
if (iterableChanges) {
|
|
||||||
this._applyIterableChanges(iterableChanges);
|
|
||||||
}
|
|
||||||
} else if (this._keyValueDiffer) {
|
|
||||||
const keyValueChanges = this._keyValueDiffer.diff(this._rawClass as{[k: string]: any});
|
|
||||||
if (keyValueChanges) {
|
|
||||||
this._applyKeyValueChanges(keyValueChanges);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _applyKeyValueChanges(changes: KeyValueChanges<string, any>): void {
|
|
||||||
changes.forEachAddedItem((record) => this._toggleClass(record.key, record.currentValue));
|
|
||||||
changes.forEachChangedItem((record) => this._toggleClass(record.key, record.currentValue));
|
|
||||||
changes.forEachRemovedItem((record) => {
|
|
||||||
if (record.previousValue) {
|
|
||||||
this._toggleClass(record.key, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _applyIterableChanges(changes: IterableChanges<string>): void {
|
|
||||||
changes.forEachAddedItem((record) => {
|
|
||||||
if (typeof record.item === 'string') {
|
|
||||||
this._toggleClass(record.item, true);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`NgClass can only toggle CSS classes expressed as strings, got ${stringify(record.item)}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
changes.forEachRemovedItem((record) => this._toggleClass(record.item, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies a collection of CSS classes to the DOM element.
|
|
||||||
*
|
|
||||||
* For argument of type Set and Array CSS class names contained in those collections are always
|
|
||||||
* added.
|
|
||||||
* For argument of type Map CSS class name in the map's key is toggled based on the value (added
|
|
||||||
* for truthy and removed for falsy).
|
|
||||||
*/
|
|
||||||
private _applyClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}) {
|
|
||||||
if (rawClassVal) {
|
|
||||||
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
|
|
||||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, true));
|
|
||||||
} else {
|
|
||||||
Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, !!rawClassVal[klass]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a collection of CSS classes from the DOM element. This is mostly useful for cleanup
|
|
||||||
* purposes.
|
|
||||||
*/
|
|
||||||
private _removeClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}) {
|
|
||||||
if (rawClassVal) {
|
|
||||||
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
|
|
||||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, false));
|
|
||||||
} else {
|
|
||||||
Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _toggleClass(klass: string, enabled: boolean): void {
|
|
||||||
klass = klass.trim();
|
|
||||||
if (klass) {
|
|
||||||
klass.split(/\s+/g).forEach(klass => {
|
|
||||||
if (enabled) {
|
|
||||||
this._renderer.addClass(this._ngEl.nativeElement, klass);
|
|
||||||
} else {
|
|
||||||
this._renderer.removeClass(this._ngEl.nativeElement, klass);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
import {ElementRef, Injectable, IterableChanges, IterableDiffer, IterableDiffers, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer2, ɵisListLikeIterable as isListLikeIterable, ɵstringify as stringify} from '@angular/core';
|
||||||
|
|
||||||
|
import {StylingDiffer, StylingDifferOptions} from './styling_differ';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as a token for an injected service within the NgClass directive.
|
||||||
|
*
|
||||||
|
* NgClass behaves differenly whether or not VE is being used or not. If
|
||||||
|
* present then the legacy ngClass diffing algorithm will be used as an
|
||||||
|
* injected service. Otherwise the new diffing algorithm (which delegates
|
||||||
|
* to the `[class]` binding) will be used. This toggle behavior is done so
|
||||||
|
* via the ivy_switch mechanism.
|
||||||
|
*/
|
||||||
|
export abstract class NgClassImpl {
|
||||||
|
abstract setClass(value: string): void;
|
||||||
|
abstract setNgClass(value: string|string[]|Set<string>|{[klass: string]: any}): void;
|
||||||
|
abstract applyChanges(): void;
|
||||||
|
abstract getValue(): {[key: string]: any}|null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NgClassR2Impl implements NgClassImpl {
|
||||||
|
// TODO(issue/24571): remove '!'.
|
||||||
|
private _iterableDiffer !: IterableDiffer<string>| null;
|
||||||
|
// TODO(issue/24571): remove '!'.
|
||||||
|
private _keyValueDiffer !: KeyValueDiffer<string, any>| null;
|
||||||
|
private _initialClasses: string[] = [];
|
||||||
|
// TODO(issue/24571): remove '!'.
|
||||||
|
private _rawClass !: string[] | Set<string>| {[klass: string]: any};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
||||||
|
private _ngEl: ElementRef, private _renderer: Renderer2) {}
|
||||||
|
|
||||||
|
getValue() { return null; }
|
||||||
|
|
||||||
|
setClass(value: string) {
|
||||||
|
this._removeClasses(this._initialClasses);
|
||||||
|
this._initialClasses = typeof value === 'string' ? value.split(/\s+/) : [];
|
||||||
|
this._applyClasses(this._initialClasses);
|
||||||
|
this._applyClasses(this._rawClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
setNgClass(value: string) {
|
||||||
|
this._removeClasses(this._rawClass);
|
||||||
|
this._applyClasses(this._initialClasses);
|
||||||
|
|
||||||
|
this._iterableDiffer = null;
|
||||||
|
this._keyValueDiffer = null;
|
||||||
|
|
||||||
|
this._rawClass = typeof value === 'string' ? value.split(/\s+/) : value;
|
||||||
|
|
||||||
|
if (this._rawClass) {
|
||||||
|
if (isListLikeIterable(this._rawClass)) {
|
||||||
|
this._iterableDiffer = this._iterableDiffers.find(this._rawClass).create();
|
||||||
|
} else {
|
||||||
|
this._keyValueDiffer = this._keyValueDiffers.find(this._rawClass).create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyChanges() {
|
||||||
|
if (this._iterableDiffer) {
|
||||||
|
const iterableChanges = this._iterableDiffer.diff(this._rawClass as string[]);
|
||||||
|
if (iterableChanges) {
|
||||||
|
this._applyIterableChanges(iterableChanges);
|
||||||
|
}
|
||||||
|
} else if (this._keyValueDiffer) {
|
||||||
|
const keyValueChanges = this._keyValueDiffer.diff(this._rawClass as{[k: string]: any});
|
||||||
|
if (keyValueChanges) {
|
||||||
|
this._applyKeyValueChanges(keyValueChanges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _applyKeyValueChanges(changes: KeyValueChanges<string, any>): void {
|
||||||
|
changes.forEachAddedItem((record) => this._toggleClass(record.key, record.currentValue));
|
||||||
|
changes.forEachChangedItem((record) => this._toggleClass(record.key, record.currentValue));
|
||||||
|
changes.forEachRemovedItem((record) => {
|
||||||
|
if (record.previousValue) {
|
||||||
|
this._toggleClass(record.key, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _applyIterableChanges(changes: IterableChanges<string>): void {
|
||||||
|
changes.forEachAddedItem((record) => {
|
||||||
|
if (typeof record.item === 'string') {
|
||||||
|
this._toggleClass(record.item, true);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`NgClass can only toggle CSS classes expressed as strings, got ${stringify(record.item)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
changes.forEachRemovedItem((record) => this._toggleClass(record.item, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a collection of CSS classes to the DOM element.
|
||||||
|
*
|
||||||
|
* For argument of type Set and Array CSS class names contained in those collections are always
|
||||||
|
* added.
|
||||||
|
* For argument of type Map CSS class name in the map's key is toggled based on the value (added
|
||||||
|
* for truthy and removed for falsy).
|
||||||
|
*/
|
||||||
|
private _applyClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}) {
|
||||||
|
if (rawClassVal) {
|
||||||
|
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
|
||||||
|
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, true));
|
||||||
|
} else {
|
||||||
|
Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, !!rawClassVal[klass]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a collection of CSS classes from the DOM element. This is mostly useful for cleanup
|
||||||
|
* purposes.
|
||||||
|
*/
|
||||||
|
private _removeClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}) {
|
||||||
|
if (rawClassVal) {
|
||||||
|
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
|
||||||
|
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, false));
|
||||||
|
} else {
|
||||||
|
Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleClass(klass: string, enabled: boolean): void {
|
||||||
|
klass = klass.trim();
|
||||||
|
if (klass) {
|
||||||
|
klass.split(/\s+/g).forEach(klass => {
|
||||||
|
if (enabled) {
|
||||||
|
this._renderer.addClass(this._ngEl.nativeElement, klass);
|
||||||
|
} else {
|
||||||
|
this._renderer.removeClass(this._ngEl.nativeElement, klass);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NgClassR3Impl implements NgClassImpl {
|
||||||
|
private _value: {[key: string]: boolean}|null = null;
|
||||||
|
private _ngClassDiffer = new StylingDiffer<{[key: string]: boolean}|null>(
|
||||||
|
'NgClass', StylingDifferOptions.TrimProperties|
|
||||||
|
StylingDifferOptions.AllowSubKeys|
|
||||||
|
StylingDifferOptions.AllowStringValue|StylingDifferOptions.ForceAsMap);
|
||||||
|
private _classStringDiffer: StylingDiffer<{[key: string]: boolean}>|null = null;
|
||||||
|
|
||||||
|
getValue() { return this._value; }
|
||||||
|
|
||||||
|
setClass(value: string) {
|
||||||
|
// early exit incase the binding gets emitted as an empty value which
|
||||||
|
// means there is no reason to instantiate and diff the values...
|
||||||
|
if (!value && !this._classStringDiffer) return;
|
||||||
|
|
||||||
|
this._classStringDiffer = this._classStringDiffer ||
|
||||||
|
new StylingDiffer('class',
|
||||||
|
StylingDifferOptions.AllowStringValue | StylingDifferOptions.ForceAsMap);
|
||||||
|
this._classStringDiffer.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setNgClass(value: string|string[]|Set<string>|{[klass: string]: any}) {
|
||||||
|
this._ngClassDiffer.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyChanges() {
|
||||||
|
const classChanged =
|
||||||
|
this._classStringDiffer ? this._classStringDiffer.hasValueChanged() : false;
|
||||||
|
const ngClassChanged = this._ngClassDiffer.hasValueChanged();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the implementation for both NgStyleR2Impl and NgStyleR3Impl are
|
||||||
|
// not ivy_switch'd away, instead they are only hooked up into the
|
||||||
|
// DI via NgStyle's directive's provider property.
|
||||||
|
export const NgClassImplProvider__PRE_R3__ = {
|
||||||
|
provide: NgClassImpl,
|
||||||
|
useClass: NgClassR2Impl
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NgClassImplProvider__POST_R3__ = {
|
||||||
|
provide: NgClassImpl,
|
||||||
|
useClass: NgClassR3Impl
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NgClassImplProvider = NgClassImplProvider__PRE_R3__;
|
|
@ -5,8 +5,68 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {Directive, DoCheck, Input, ɵRenderFlags, ɵdefineDirective, ɵelementStyling, ɵelementStylingApply, ɵelementStylingMap} from '@angular/core';
|
||||||
|
|
||||||
import {Directive, DoCheck, ElementRef, Input, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer2} from '@angular/core';
|
import {NgStyleImpl, NgStyleImplProvider} from './ng_style_impl';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NgStyle (as well as NgClass) behaves differently when loaded in the VE and when not.
|
||||||
|
*
|
||||||
|
* If the VE is present (which is for older versions of Angular) then NgStyle will inject
|
||||||
|
* the legacy diffing algorithm as a service and delegate all styling changes to that.
|
||||||
|
*
|
||||||
|
* If the VE is not present then NgStyle will normalize (through the injected service) and
|
||||||
|
* then write all styling changes to the `[style]` binding directly (through a host binding).
|
||||||
|
* Then Angular will notice the host binding change and treat the changes as styling
|
||||||
|
* changes and apply them via the core styling instructions that exist within Angular.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// used when the VE is present
|
||||||
|
export const ngStyleDirectiveDef__PRE_R3__ = undefined;
|
||||||
|
|
||||||
|
// used when the VE is not present (note the directive will
|
||||||
|
// never be instantiated normally because it is apart of a
|
||||||
|
// base class)
|
||||||
|
export const ngStyleDirectiveDef__POST_R3__ = ɵdefineDirective({
|
||||||
|
type: function() {} as any,
|
||||||
|
selectors: null as any,
|
||||||
|
factory: () => {},
|
||||||
|
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
|
||||||
|
if (rf & ɵRenderFlags.Create) {
|
||||||
|
ɵelementStyling(null, null, null, ctx);
|
||||||
|
}
|
||||||
|
if (rf & ɵRenderFlags.Update) {
|
||||||
|
ɵelementStylingMap(elIndex, null, ctx.getValue(), ctx);
|
||||||
|
ɵelementStylingApply(elIndex, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ngStyleDirectiveDef = ngStyleDirectiveDef__PRE_R3__;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serves as the base non-VE container for NgStyle.
|
||||||
|
*
|
||||||
|
* While this is a base class that NgStyle extends from, the
|
||||||
|
* class itself acts as a container for non-VE code to setup
|
||||||
|
* a link to the `[style]` host binding (via the static
|
||||||
|
* `ngDirectiveDef` property on the class).
|
||||||
|
*
|
||||||
|
* Note that the `ngDirectiveDef` property's code is switched
|
||||||
|
* depending if VE is present or not (this allows for the
|
||||||
|
* binding code to be set only for newer versions of Angular).
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
|
*/
|
||||||
|
export class NgStyleBase {
|
||||||
|
static ngDirectiveDef: any = ngStyleDirectiveDef;
|
||||||
|
|
||||||
|
constructor(protected _delegate: NgStyleImpl) {}
|
||||||
|
|
||||||
|
getValue() { return this._delegate.getValue(); }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
|
@ -44,58 +104,12 @@ import {Directive, DoCheck, ElementRef, Input, KeyValueChanges, KeyValueDiffer,
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Directive({selector: '[ngStyle]'})
|
@Directive({selector: '[ngStyle]', providers: [NgStyleImplProvider]})
|
||||||
export class NgStyle implements DoCheck {
|
export class NgStyle extends NgStyleBase implements DoCheck {
|
||||||
// TODO(issue/24571): remove '!'.
|
constructor(delegate: NgStyleImpl) { super(delegate); }
|
||||||
private _ngStyle !: {[key: string]: string};
|
|
||||||
// TODO(issue/24571): remove '!'.
|
|
||||||
private _differ !: KeyValueDiffer<string, string|number>;
|
|
||||||
|
|
||||||
constructor(
|
@Input('ngStyle')
|
||||||
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer2) {}
|
set ngStyle(value: {[klass: string]: any}|null) { this._delegate.setNgStyle(value); }
|
||||||
|
|
||||||
@Input()
|
ngDoCheck() { this._delegate.applyChanges(); }
|
||||||
set ngStyle(
|
|
||||||
/**
|
|
||||||
* A map of style properties, specified as colon-separated
|
|
||||||
* key-value pairs.
|
|
||||||
* * The key is a style name, with an optional `.<unit>` suffix
|
|
||||||
* (such as 'top.px', 'font-style.em').
|
|
||||||
* * The value is an expression to be evaluated.
|
|
||||||
*/
|
|
||||||
values: {[key: string]: string}) {
|
|
||||||
this._ngStyle = values;
|
|
||||||
if (!this._differ && values) {
|
|
||||||
this._differ = this._differs.find(values).create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the new styles if needed.
|
|
||||||
*/
|
|
||||||
ngDoCheck() {
|
|
||||||
if (this._differ) {
|
|
||||||
const changes = this._differ.diff(this._ngStyle);
|
|
||||||
if (changes) {
|
|
||||||
this._applyChanges(changes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _applyChanges(changes: KeyValueChanges<string, string|number>): void {
|
|
||||||
changes.forEachRemovedItem((record) => this._setStyle(record.key, null));
|
|
||||||
changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
|
|
||||||
changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setStyle(nameAndUnit: string, value: string|number|null|undefined): void {
|
|
||||||
const [name, unit] = nameAndUnit.split('.');
|
|
||||||
value = value != null && unit ? `${value}${unit}` : value;
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
this._renderer.setStyle(this._ngEl.nativeElement, name, value as string);
|
|
||||||
} else {
|
|
||||||
this._renderer.removeStyle(this._ngEl.nativeElement, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
import {ElementRef, Injectable, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer2} from '@angular/core';
|
||||||
|
|
||||||
|
import {StylingDiffer, StylingDifferOptions} from './styling_differ';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as a token for an injected service within the NgStyle directive.
|
||||||
|
*
|
||||||
|
* NgStyle behaves differenly whether or not VE is being used or not. If
|
||||||
|
* present then the legacy ngClass diffing algorithm will be used as an
|
||||||
|
* injected service. Otherwise the new diffing algorithm (which delegates
|
||||||
|
* to the `[style]` binding) will be used. This toggle behavior is done so
|
||||||
|
* via the ivy_switch mechanism.
|
||||||
|
*/
|
||||||
|
export abstract class NgStyleImpl {
|
||||||
|
abstract getValue(): {[key: string]: any}|null;
|
||||||
|
abstract setNgStyle(value: {[key: string]: any}|null): void;
|
||||||
|
abstract applyChanges(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NgStyleR2Impl implements NgStyleImpl {
|
||||||
|
// TODO(issue/24571): remove '!'.
|
||||||
|
private _ngStyle !: {[key: string]: string};
|
||||||
|
// TODO(issue/24571): remove '!'.
|
||||||
|
private _differ !: KeyValueDiffer<string, string|number>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _ngEl: ElementRef, private _differs: KeyValueDiffers, private _renderer: Renderer2) {}
|
||||||
|
|
||||||
|
getValue() { return null; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of style properties, specified as colon-separated
|
||||||
|
* key-value pairs.
|
||||||
|
* * The key is a style name, with an optional `.<unit>` suffix
|
||||||
|
* (such as 'top.px', 'font-style.em').
|
||||||
|
* * The value is an expression to be evaluated.
|
||||||
|
*/
|
||||||
|
setNgStyle(values: {[key: string]: string}) {
|
||||||
|
this._ngStyle = values;
|
||||||
|
if (!this._differ && values) {
|
||||||
|
this._differ = this._differs.find(values).create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the new styles if needed.
|
||||||
|
*/
|
||||||
|
applyChanges() {
|
||||||
|
if (this._differ) {
|
||||||
|
const changes = this._differ.diff(this._ngStyle);
|
||||||
|
if (changes) {
|
||||||
|
this._applyChanges(changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _applyChanges(changes: KeyValueChanges<string, string|number>): void {
|
||||||
|
changes.forEachRemovedItem((record) => this._setStyle(record.key, null));
|
||||||
|
changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
|
||||||
|
changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setStyle(nameAndUnit: string, value: string|number|null|undefined): void {
|
||||||
|
const [name, unit] = nameAndUnit.split('.');
|
||||||
|
value = value != null && unit ? `${value}${unit}` : value;
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
this._renderer.setStyle(this._ngEl.nativeElement, name, value as string);
|
||||||
|
} else {
|
||||||
|
this._renderer.removeStyle(this._ngEl.nativeElement, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NgStyleR3Impl implements NgStyleImpl {
|
||||||
|
private _differ =
|
||||||
|
new StylingDiffer<{[key: string]: any}|null>('NgStyle', StylingDifferOptions.AllowUnits);
|
||||||
|
|
||||||
|
private _value: {[key: string]: any}|null = null;
|
||||||
|
|
||||||
|
getValue() { return this._value; }
|
||||||
|
|
||||||
|
setNgStyle(value: {[key: string]: any}|null) { this._differ.setValue(value); }
|
||||||
|
|
||||||
|
applyChanges() {
|
||||||
|
if (this._differ.hasValueChanged()) {
|
||||||
|
this._value = this._differ.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the implementation for both NgClassR2Impl and NgClassR3Impl are
|
||||||
|
// not ivy_switch'd away, instead they are only hooked up into the
|
||||||
|
// DI via NgStyle's directive's provider property.
|
||||||
|
export const NgStyleImplProvider__PRE_R3__ = {
|
||||||
|
provide: NgStyleImpl,
|
||||||
|
useClass: NgStyleR2Impl
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NgStyleImplProvider__POST_R3__ = {
|
||||||
|
provide: NgStyleImpl,
|
||||||
|
useClass: NgStyleR3Impl
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NgStyleImplProvider = NgStyleImplProvider__PRE_R3__;
|
|
@ -0,0 +1,302 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to diff and convert ngStyle/ngClass instructions into [style] and [class] bindings.
|
||||||
|
*
|
||||||
|
* ngStyle and ngClass both accept various forms of input and behave differently than that
|
||||||
|
* 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
|
||||||
|
* 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
|
||||||
|
* - ngClass allows for multiple className keys (space-separated) within an array or map
|
||||||
|
* (as the * key) while [class] only accepts a simple key/value map object
|
||||||
|
*
|
||||||
|
* Having Angular understand and adapt to all the different forms of behavior is complicated
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* ## Why do we care about ngStyle/ngClass?
|
||||||
|
* The styling algorithm code (documented inside of `render3/interfaces/styling.ts`) needs to
|
||||||
|
* respect and understand the styling values emitted through ngStyle and ngClass (when they
|
||||||
|
* are present and used in a template).
|
||||||
|
*
|
||||||
|
* Instead of having these directives manage styling on their own, they should be included
|
||||||
|
* into the Angular styling algorithm that exists for [style] and [class] bindings.
|
||||||
|
*
|
||||||
|
* Here's why:
|
||||||
|
*
|
||||||
|
* - 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)
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <!-- if `w1` is updated then it will always override `w2`
|
||||||
|
* if `w2` is updated then it will always override `w1`
|
||||||
|
* if both are updated at the same time then `w1` wins -->
|
||||||
|
* <div [ngStyle]="{width:w1}" [style.width]="w2">...</div>
|
||||||
|
*
|
||||||
|
* <!-- if `w1` is updated then it will always lose to `w2`
|
||||||
|
* if `w2` is updated then it will always override `w1`
|
||||||
|
* if both are updated at the same time then `w2` wins -->
|
||||||
|
* <div [style]="{width:w1}" [style.width]="w2">...</div>
|
||||||
|
* ```
|
||||||
|
* - ngClass/ngStyle were written as a directives and made use of maps, closures and other
|
||||||
|
* expensive data structures which were evaluated each time CD runs
|
||||||
|
*/
|
||||||
|
export class StylingDiffer<T> {
|
||||||
|
public readonly value: T|null = null;
|
||||||
|
|
||||||
|
private _lastSetValue: {[key: string]: any}|string|string[]|null = null;
|
||||||
|
private _lastSetValueType: StylingDifferValueTypes = StylingDifferValueTypes.Null;
|
||||||
|
private _lastSetValueIdentityChange = false;
|
||||||
|
|
||||||
|
constructor(private _name: string, private _options: StylingDifferOptions) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets (updates) the styling value within the differ.
|
||||||
|
*
|
||||||
|
* Only when `hasValueChanged` is called then this new value will be evaluted
|
||||||
|
* and checked against the previous value.
|
||||||
|
*
|
||||||
|
* @param value the new styling value provided from the ngClass/ngStyle binding
|
||||||
|
*/
|
||||||
|
setValue(value: {[key: string]: any}|string[]|string|null) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
this._lastSetValueType = StylingDifferValueTypes.Array;
|
||||||
|
} else if (value instanceof Set) {
|
||||||
|
this._lastSetValueType = StylingDifferValueTypes.Set;
|
||||||
|
} else if (value && typeof value === 'string') {
|
||||||
|
if (!(this._options & StylingDifferOptions.AllowStringValue)) {
|
||||||
|
throw new Error(this._name + ' string values are not allowed');
|
||||||
|
}
|
||||||
|
this._lastSetValueType = StylingDifferValueTypes.String;
|
||||||
|
} else {
|
||||||
|
this._lastSetValueType = value ? StylingDifferValueTypes.Map : StylingDifferValueTypes.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._lastSetValueIdentityChange = true;
|
||||||
|
this._lastSetValue = value || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the value has changed.
|
||||||
|
*
|
||||||
|
* This function can be called right after `setValue()` is called, but it can also be
|
||||||
|
* called incase the existing value (if it's a collection) changes internally. If the
|
||||||
|
* value is indeed a collection it will do the necessary diffing work and produce a
|
||||||
|
* new object value as assign that to `value`.
|
||||||
|
*
|
||||||
|
* @returns whether or not the value has changed in some way.
|
||||||
|
*/
|
||||||
|
hasValueChanged(): boolean {
|
||||||
|
let valueHasChanged = this._lastSetValueIdentityChange;
|
||||||
|
if (!valueHasChanged && !(this._lastSetValueType & StylingDifferValueTypes.Collection))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let finalValue: {[key: string]: any}|string|null = null;
|
||||||
|
const trimValues = (this._options & StylingDifferOptions.TrimProperties) ? true : false;
|
||||||
|
const parseOutUnits = (this._options & StylingDifferOptions.AllowUnits) ? true : false;
|
||||||
|
const allowSubKeys = (this._options & StylingDifferOptions.AllowSubKeys) ? true : false;
|
||||||
|
|
||||||
|
switch (this._lastSetValueType) {
|
||||||
|
// case 1: [input]="string"
|
||||||
|
case StylingDifferValueTypes.String:
|
||||||
|
const tokens = (this._lastSetValue as string).split(/\s+/g);
|
||||||
|
if (this._options & StylingDifferOptions.ForceAsMap) {
|
||||||
|
finalValue = {};
|
||||||
|
tokens.forEach((token, i) => (finalValue as{[key: string]: any})[token] = true);
|
||||||
|
} else {
|
||||||
|
finalValue = tokens.reduce((str, token, i) => str + (i ? ' ' : '') + token);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case 2: [input]="{key:value}"
|
||||||
|
case StylingDifferValueTypes.Map:
|
||||||
|
const map: {[key: string]: any} = this._lastSetValue as{[key: string]: any};
|
||||||
|
const keys = Object.keys(map);
|
||||||
|
if (!valueHasChanged) {
|
||||||
|
if (this.value) {
|
||||||
|
// we know that the classExp value exists and that it is
|
||||||
|
// a map (otherwise an identity change would have occurred)
|
||||||
|
valueHasChanged = mapHasChanged(keys, this.value as{[key: string]: any}, map);
|
||||||
|
} else {
|
||||||
|
valueHasChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueHasChanged) {
|
||||||
|
finalValue =
|
||||||
|
bulidMapFromValues(this._name, trimValues, parseOutUnits, allowSubKeys, map, keys);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case 3a: [input]="[str1, str2, ...]"
|
||||||
|
// case 3b: [input]="Set"
|
||||||
|
case StylingDifferValueTypes.Array:
|
||||||
|
case StylingDifferValueTypes.Set:
|
||||||
|
const values = Array.from(this._lastSetValue as string[] | Set<string>);
|
||||||
|
if (!valueHasChanged) {
|
||||||
|
const keys = Object.keys(this.value !);
|
||||||
|
valueHasChanged = !arrayEqualsArray(keys, values);
|
||||||
|
}
|
||||||
|
if (valueHasChanged) {
|
||||||
|
finalValue =
|
||||||
|
bulidMapFromValues(this._name, trimValues, parseOutUnits, allowSubKeys, values);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case 4: [input]="null|undefined"
|
||||||
|
default:
|
||||||
|
finalValue = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueHasChanged) {
|
||||||
|
(this as any).value = finalValue !;
|
||||||
|
}
|
||||||
|
|
||||||
|
return valueHasChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various options that are consumed by the [StylingDiffer] class.
|
||||||
|
*/
|
||||||
|
export const enum StylingDifferOptions {
|
||||||
|
None = 0b00000,
|
||||||
|
TrimProperties = 0b00001,
|
||||||
|
AllowSubKeys = 0b00010,
|
||||||
|
AllowStringValue = 0b00100,
|
||||||
|
AllowUnits = 0b01000,
|
||||||
|
ForceAsMap = 0b10000,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The different types of inputs that the [StylingDiffer] can deal with
|
||||||
|
*/
|
||||||
|
const enum StylingDifferValueTypes {
|
||||||
|
Null = 0b0000,
|
||||||
|
String = 0b0001,
|
||||||
|
Map = 0b0010,
|
||||||
|
Array = 0b0100,
|
||||||
|
Set = 0b1000,
|
||||||
|
Collection = 0b1110,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builds and returns a map based on the values input value
|
||||||
|
*
|
||||||
|
* If the `keys` param is provided then the `values` param is treated as a
|
||||||
|
* string map. Otherwise `values` is treated as a string array.
|
||||||
|
*/
|
||||||
|
function bulidMapFromValues(
|
||||||
|
errorPrefix: string, trim: boolean, parseOutUnits: boolean, allowSubKeys: boolean,
|
||||||
|
values: {[key: string]: any} | string[], keys?: string[]) {
|
||||||
|
const map: {[key: string]: any} = {};
|
||||||
|
if (keys) {
|
||||||
|
// case 1: map
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
let key = keys[i];
|
||||||
|
key = trim ? key.trim() : key;
|
||||||
|
const value = (values as{[key: string]: any})[key];
|
||||||
|
setMapValues(map, key, value, parseOutUnits, allowSubKeys);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// case 2: array
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
let value = (values as string[])[i];
|
||||||
|
assertValidValue(errorPrefix, value);
|
||||||
|
value = trim ? value.trim() : value;
|
||||||
|
setMapValues(map, value, true, false, allowSubKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertValidValue(errorPrefix: string, value: any) {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`${errorPrefix} can only toggle CSS classes expressed as strings, got ${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMapValues(
|
||||||
|
map: {[key: string]: any}, key: string, value: any, parseOutUnits: boolean,
|
||||||
|
allowSubKeys: boolean) {
|
||||||
|
if (allowSubKeys && key.indexOf(' ') > 0) {
|
||||||
|
const innerKeys = key.split(/\s+/g);
|
||||||
|
for (let j = 0; j < innerKeys.length; j++) {
|
||||||
|
setIndividualMapValue(map, innerKeys[j], value, parseOutUnits);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIndividualMapValue(map, key, value, parseOutUnits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIndividualMapValue(
|
||||||
|
map: {[key: string]: any}, key: string, value: any, parseOutUnits: boolean) {
|
||||||
|
if (parseOutUnits) {
|
||||||
|
const values = normalizeStyleKeyAndValue(key, value);
|
||||||
|
value = values.value;
|
||||||
|
key = values.key;
|
||||||
|
}
|
||||||
|
map[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeStyleKeyAndValue(key: string, value: string | null) {
|
||||||
|
const index = key.indexOf('.');
|
||||||
|
if (index > 0) {
|
||||||
|
const unit = key.substr(index + 1); // ignore the . ([width.px]="'40'" => "40px")
|
||||||
|
key = key.substring(0, index);
|
||||||
|
if (value != null) { // we should not convert null values to string
|
||||||
|
value += unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {key, value};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapHasChanged(keys: string[], a: {[key: string]: any}, b: {[key: string]: any}) {
|
||||||
|
const oldKeys = Object.keys(a);
|
||||||
|
const newKeys = keys;
|
||||||
|
|
||||||
|
// the keys are different which means the map changed
|
||||||
|
if (!arrayEqualsArray(oldKeys, newKeys)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < newKeys.length; i++) {
|
||||||
|
const key = newKeys[i];
|
||||||
|
if (a[key] !== b[key]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayEqualsArray(a: any[] | null, b: any[] | null) {
|
||||||
|
if (a && b) {
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (b.indexOf(a[i]) === -1) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -105,7 +105,7 @@ export {
|
||||||
export {NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref';
|
export {NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AttributeMarker
|
AttributeMarker
|
||||||
} from './interfaces/node';
|
} from './interfaces/node';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||||
import {assertNotEqual} from '../../util/assert';
|
|
||||||
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||||
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
||||||
import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerIndex} from '../interfaces/player';
|
import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerIndex} from '../interfaces/player';
|
||||||
|
@ -418,16 +417,9 @@ export function updateContextWithBindings(
|
||||||
const directiveMultiStylesStartIndex =
|
const directiveMultiStylesStartIndex =
|
||||||
multiStylesStartIndex + totalCurrentStyleBindings * StylingIndex.Size;
|
multiStylesStartIndex + totalCurrentStyleBindings * StylingIndex.Size;
|
||||||
const cachedStyleMapIndex = cachedStyleMapValues.length;
|
const cachedStyleMapIndex = cachedStyleMapValues.length;
|
||||||
|
registerMultiMapEntry(
|
||||||
// this means that ONLY directive style styling (like ngStyle) was used
|
context, directiveIndex, false, directiveMultiStylesStartIndex,
|
||||||
// therefore the root directive will still need to be filled in
|
filteredStyleBindingNames.length);
|
||||||
if (directiveIndex > 0 &&
|
|
||||||
cachedStyleMapValues.length <= MapBasedOffsetValuesIndex.ValuesStartPosition) {
|
|
||||||
cachedStyleMapValues.push(0, directiveMultiStylesStartIndex, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedStyleMapValues.push(
|
|
||||||
0, directiveMultiStylesStartIndex, null, filteredStyleBindingNames.length);
|
|
||||||
|
|
||||||
for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < cachedStyleMapIndex;
|
for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < cachedStyleMapIndex;
|
||||||
i += MapBasedOffsetValuesIndex.Size) {
|
i += MapBasedOffsetValuesIndex.Size) {
|
||||||
|
@ -441,16 +433,9 @@ export function updateContextWithBindings(
|
||||||
const directiveMultiClassesStartIndex =
|
const directiveMultiClassesStartIndex =
|
||||||
multiClassesStartIndex + totalCurrentClassBindings * StylingIndex.Size;
|
multiClassesStartIndex + totalCurrentClassBindings * StylingIndex.Size;
|
||||||
const cachedClassMapIndex = cachedClassMapValues.length;
|
const cachedClassMapIndex = cachedClassMapValues.length;
|
||||||
|
registerMultiMapEntry(
|
||||||
// this means that ONLY directive class styling (like ngClass) was used
|
context, directiveIndex, true, directiveMultiClassesStartIndex,
|
||||||
// therefore the root directive will still need to be filled in
|
filteredClassBindingNames.length);
|
||||||
if (directiveIndex > 0 &&
|
|
||||||
cachedClassMapValues.length <= MapBasedOffsetValuesIndex.ValuesStartPosition) {
|
|
||||||
cachedClassMapValues.push(0, directiveMultiClassesStartIndex, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedClassMapValues.push(
|
|
||||||
0, directiveMultiClassesStartIndex, null, filteredClassBindingNames.length);
|
|
||||||
|
|
||||||
for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < cachedClassMapIndex;
|
for (let i = MapBasedOffsetValuesIndex.ValuesStartPosition; i < cachedClassMapIndex;
|
||||||
i += MapBasedOffsetValuesIndex.Size) {
|
i += MapBasedOffsetValuesIndex.Size) {
|
||||||
|
@ -619,7 +604,7 @@ export function updateStylingMap(
|
||||||
}
|
}
|
||||||
|
|
||||||
const multiStylesStartIndex = getMultiStylesStartIndex(context);
|
const multiStylesStartIndex = getMultiStylesStartIndex(context);
|
||||||
let multiClassesStartIndex = getMultiClassStartIndex(context);
|
let multiClassesStartIndex = getMultiClassesStartIndex(context);
|
||||||
let multiClassesEndIndex = context.length;
|
let multiClassesEndIndex = context.length;
|
||||||
|
|
||||||
if (!ignoreAllStyleUpdates) {
|
if (!ignoreAllStyleUpdates) {
|
||||||
|
@ -862,6 +847,7 @@ function patchStylingMapIntoContext(
|
||||||
valuesEntryShapeChange = true; // some values are missing
|
valuesEntryShapeChange = true; // some values are missing
|
||||||
const ctxValue = getValue(context, ctxIndex);
|
const ctxValue = getValue(context, ctxIndex);
|
||||||
const ctxFlag = getPointers(context, ctxIndex);
|
const ctxFlag = getPointers(context, ctxIndex);
|
||||||
|
const ctxDirective = getDirectiveIndexFromEntry(context, ctxIndex);
|
||||||
if (ctxValue != null) {
|
if (ctxValue != null) {
|
||||||
valuesEntryShapeChange = true;
|
valuesEntryShapeChange = true;
|
||||||
}
|
}
|
||||||
|
@ -1293,7 +1279,7 @@ function getMultiStartIndex(context: StylingContext): number {
|
||||||
return getMultiOrSingleIndex(context[StylingIndex.MasterFlagPosition]) as number;
|
return getMultiOrSingleIndex(context[StylingIndex.MasterFlagPosition]) as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMultiClassStartIndex(context: StylingContext): number {
|
function getMultiClassesStartIndex(context: StylingContext): number {
|
||||||
const classCache = context[StylingIndex.CachedMultiClasses];
|
const classCache = context[StylingIndex.CachedMultiClasses];
|
||||||
return classCache
|
return classCache
|
||||||
[MapBasedOffsetValuesIndex.ValuesStartPosition +
|
[MapBasedOffsetValuesIndex.ValuesStartPosition +
|
||||||
|
@ -1625,15 +1611,31 @@ export function getDirectiveIndexFromEntry(context: StylingContext, index: numbe
|
||||||
return value & DirectiveOwnerAndPlayerBuilderIndex.BitMask;
|
return value & DirectiveOwnerAndPlayerBuilderIndex.BitMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDirectiveIndexFromRegistry(context: StylingContext, directive: any) {
|
function getDirectiveIndexFromRegistry(context: StylingContext, directiveRef: any) {
|
||||||
const index =
|
let directiveIndex: number;
|
||||||
getDirectiveRegistryValuesIndexOf(context[StylingIndex.DirectiveRegistryPosition], directive);
|
|
||||||
ngDevMode &&
|
const dirs = context[StylingIndex.DirectiveRegistryPosition];
|
||||||
assertNotEqual(
|
let index = getDirectiveRegistryValuesIndexOf(dirs, directiveRef);
|
||||||
index, -1,
|
if (index === -1) {
|
||||||
`The provided directive ${directive} has not been allocated to the element\'s style/class bindings`);
|
// if the directive was not allocated then this means that styling is
|
||||||
return index > 0 ? index / DirectiveRegistryValuesIndex.Size : 0;
|
// being applied in a dynamic way AFTER the element was already instantiated
|
||||||
// return index / DirectiveRegistryValuesIndex.Size;
|
index = dirs.length;
|
||||||
|
directiveIndex = index > 0 ? index / DirectiveRegistryValuesIndex.Size : 0;
|
||||||
|
|
||||||
|
dirs.push(null, null, null, null);
|
||||||
|
dirs[index + DirectiveRegistryValuesIndex.DirectiveValueOffset] = directiveRef;
|
||||||
|
dirs[index + DirectiveRegistryValuesIndex.DirtyFlagOffset] = false;
|
||||||
|
dirs[index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] = -1;
|
||||||
|
|
||||||
|
const classesStartIndex =
|
||||||
|
getMultiClassesStartIndex(context) || StylingIndex.SingleStylesStartPosition;
|
||||||
|
registerMultiMapEntry(context, directiveIndex, true, context.length);
|
||||||
|
registerMultiMapEntry(context, directiveIndex, false, classesStartIndex);
|
||||||
|
} else {
|
||||||
|
directiveIndex = index > 0 ? index / DirectiveRegistryValuesIndex.Size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return directiveIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDirectiveRegistryValuesIndexOf(
|
function getDirectiveRegistryValuesIndexOf(
|
||||||
|
@ -1936,3 +1938,21 @@ function hyphenate(value: string): string {
|
||||||
return value.replace(
|
return value.replace(
|
||||||
/[a-z][A-Z]/g, match => `${match.charAt(0)}-${match.charAt(1).toLowerCase()}`);
|
/[a-z][A-Z]/g, match => `${match.charAt(0)}-${match.charAt(1).toLowerCase()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function registerMultiMapEntry(
|
||||||
|
context: StylingContext, directiveIndex: number, entryIsClassBased: boolean,
|
||||||
|
startPosition: number, count = 0) {
|
||||||
|
const cachedValues =
|
||||||
|
context[entryIsClassBased ? StylingIndex.CachedMultiClasses : StylingIndex.CachedMultiStyles];
|
||||||
|
if (directiveIndex > 0) {
|
||||||
|
const limit = MapBasedOffsetValuesIndex.ValuesStartPosition +
|
||||||
|
(directiveIndex * MapBasedOffsetValuesIndex.Size);
|
||||||
|
while (cachedValues.length < limit) {
|
||||||
|
// this means that ONLY directive class styling (like ngClass) was used
|
||||||
|
// therefore the root directive will still need to be filled in as well
|
||||||
|
// as any other directive spaces incase they only used static values
|
||||||
|
cachedValues.push(0, startPosition, null, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedValues.push(0, startPosition, null, count);
|
||||||
|
}
|
||||||
|
|
|
@ -56,6 +56,16 @@ export function allocStylingContext(
|
||||||
element: RElement | null, templateStyleContext: StylingContext): StylingContext {
|
element: RElement | null, templateStyleContext: StylingContext): StylingContext {
|
||||||
// each instance gets a copy
|
// each instance gets a copy
|
||||||
const context = templateStyleContext.slice() as any as StylingContext;
|
const context = templateStyleContext.slice() as any as StylingContext;
|
||||||
|
|
||||||
|
// the HEADER values contain arrays which also need
|
||||||
|
// to be copied over into the new context
|
||||||
|
for (let i = 0; i < StylingIndex.SingleStylesStartPosition; i++) {
|
||||||
|
const value = templateStyleContext[i];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
context[i] = value.slice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context[StylingIndex.ElementPosition] = element;
|
context[StylingIndex.ElementPosition] = element;
|
||||||
|
|
||||||
// this will prevent any other directives from extending the context
|
// this will prevent any other directives from extending the context
|
||||||
|
|
|
@ -91,7 +91,7 @@ class BoxWithOverriddenStylesComponent {
|
||||||
|
|
||||||
<box-with-overridden-styles
|
<box-with-overridden-styles
|
||||||
style="display:block"
|
style="display:block"
|
||||||
[style]="{'border-radius':'50px', 'border': '50px solid teal'}">
|
[style]="{'border-radius':'50px', 'border': '50px solid teal'}" [ngStyle]="{transform:'rotate(50deg)'}">
|
||||||
</box-with-overridden-styles>
|
</box-with-overridden-styles>
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -722,6 +722,9 @@
|
||||||
{
|
{
|
||||||
"name": "getMatchingBindingIndex"
|
"name": "getMatchingBindingIndex"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getMultiClassesStartIndex"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getMultiOrSingleIndex"
|
"name": "getMultiOrSingleIndex"
|
||||||
},
|
},
|
||||||
|
@ -1100,6 +1103,9 @@
|
||||||
{
|
{
|
||||||
"name": "refreshDynamicEmbeddedViews"
|
"name": "refreshDynamicEmbeddedViews"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "registerMultiMapEntry"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "registerPostOrderHooks"
|
"name": "registerPostOrderHooks"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1693,18 +1693,6 @@ describe('style and class based bindings', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a directive is provided that isn\'t registered', () => {
|
|
||||||
const template = createEmptyStylingContext();
|
|
||||||
const knownDir = {};
|
|
||||||
const unknownDir = {};
|
|
||||||
updateContextWithBindings(template, knownDir, null, ['color']);
|
|
||||||
|
|
||||||
const ctx = allocStylingContext(element, template);
|
|
||||||
updateStyleProp(ctx, 0, 'blue', knownDir);
|
|
||||||
|
|
||||||
expect(() => { updateStyleProp(ctx, 0, 'blue', unknownDir); }).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use a different sanitizer when a different directive\'s binding is updated',
|
it('should use a different sanitizer when a different directive\'s binding is updated',
|
||||||
() => {
|
() => {
|
||||||
const getStyles = trackStylesFactory();
|
const getStyles = trackStylesFactory();
|
||||||
|
@ -1766,6 +1754,62 @@ describe('style and class based bindings', () => {
|
||||||
expect(((ctx[colorIndex] as number) & StylingFlags.Sanitize) > 0).toBeFalsy();
|
expect(((ctx[colorIndex] as number) & StylingFlags.Sanitize) > 0).toBeFalsy();
|
||||||
expect(getStyles(ctx, dirWithoutSanitizer)).toEqual({color: 'green'});
|
expect(getStyles(ctx, dirWithoutSanitizer)).toEqual({color: 'green'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should automatically register a styling context with a foreign directive if styling is applied with said directive',
|
||||||
|
() => {
|
||||||
|
const template = createEmptyStylingContext();
|
||||||
|
const knownDir = {};
|
||||||
|
const foreignDir = {};
|
||||||
|
updateContextWithBindings(template, knownDir);
|
||||||
|
|
||||||
|
const ctx = allocStylingContext(element, template);
|
||||||
|
expect(ctx[StylingIndex.DirectiveRegistryPosition]).toEqual([
|
||||||
|
null, //
|
||||||
|
-1, //
|
||||||
|
false, //
|
||||||
|
null, //
|
||||||
|
knownDir, //
|
||||||
|
2, //
|
||||||
|
false, //
|
||||||
|
null, //
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(ctx[StylingIndex.CachedMultiClasses].length)
|
||||||
|
.toEqual(template[StylingIndex.CachedMultiClasses].length);
|
||||||
|
expect(ctx[StylingIndex.CachedMultiClasses]).toEqual([0, 0, 9, null, 0, 0, 9, null, 0]);
|
||||||
|
|
||||||
|
expect(ctx[StylingIndex.CachedMultiStyles].length)
|
||||||
|
.toEqual(template[StylingIndex.CachedMultiStyles].length);
|
||||||
|
expect(ctx[StylingIndex.CachedMultiStyles]).toEqual([0, 0, 9, null, 0, 0, 9, null, 0]);
|
||||||
|
|
||||||
|
updateStylingMap(ctx, 'foo', null, foreignDir);
|
||||||
|
expect(ctx[StylingIndex.DirectiveRegistryPosition]).toEqual([
|
||||||
|
null, //
|
||||||
|
-1, //
|
||||||
|
false, //
|
||||||
|
null, //
|
||||||
|
knownDir, //
|
||||||
|
2, //
|
||||||
|
false, //
|
||||||
|
null, //
|
||||||
|
foreignDir, //
|
||||||
|
-1, //
|
||||||
|
true, //
|
||||||
|
null, //
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(ctx[StylingIndex.CachedMultiClasses].length)
|
||||||
|
.not.toEqual(template[StylingIndex.CachedMultiClasses].length);
|
||||||
|
expect(ctx[StylingIndex.CachedMultiClasses]).toEqual([
|
||||||
|
1, 0, 9, null, 0, 0, 9, null, 0, 0, 9, 'foo', 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(ctx[StylingIndex.CachedMultiStyles].length)
|
||||||
|
.not.toEqual(template[StylingIndex.CachedMultiStyles].length);
|
||||||
|
expect(ctx[StylingIndex.CachedMultiStyles]).toEqual([
|
||||||
|
0, 0, 9, null, 0, 0, 9, null, 0, 0, 9, null, 0
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip issuing style updates if there is nothing to update upon first render', () => {
|
it('should skip issuing style updates if there is nothing to update upon first render', () => {
|
||||||
|
|
|
@ -207,15 +207,24 @@ export declare class LowerCasePipe implements PipeTransform {
|
||||||
transform(value: string): string;
|
transform(value: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class NgClass implements DoCheck {
|
export declare class NgClass extends NgClassBase implements DoCheck {
|
||||||
klass: string;
|
klass: string;
|
||||||
ngClass: string | string[] | Set<string> | {
|
ngClass: string | string[] | Set<string> | {
|
||||||
[klass: string]: any;
|
[klass: string]: any;
|
||||||
};
|
};
|
||||||
constructor(_iterableDiffers: IterableDiffers, _keyValueDiffers: KeyValueDiffers, _ngEl: ElementRef, _renderer: Renderer2);
|
constructor(delegate: NgClassImpl);
|
||||||
ngDoCheck(): void;
|
ngDoCheck(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export declare class NgClassBase {
|
||||||
|
protected _delegate: NgClassImpl;
|
||||||
|
constructor(_delegate: NgClassImpl);
|
||||||
|
getValue(): {
|
||||||
|
[key: string]: any;
|
||||||
|
} | null;
|
||||||
|
static ngDirectiveDef: any;
|
||||||
|
}
|
||||||
|
|
||||||
export declare class NgComponentOutlet implements OnChanges, OnDestroy {
|
export declare class NgComponentOutlet implements OnChanges, OnDestroy {
|
||||||
ngComponentOutlet: Type<any>;
|
ngComponentOutlet: Type<any>;
|
||||||
ngComponentOutletContent: any[][];
|
ngComponentOutletContent: any[][];
|
||||||
|
@ -283,14 +292,23 @@ export declare class NgPluralCase {
|
||||||
constructor(value: string, template: TemplateRef<Object>, viewContainer: ViewContainerRef, ngPlural: NgPlural);
|
constructor(value: string, template: TemplateRef<Object>, viewContainer: ViewContainerRef, ngPlural: NgPlural);
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class NgStyle implements DoCheck {
|
export declare class NgStyle extends NgStyleBase implements DoCheck {
|
||||||
ngStyle: {
|
ngStyle: {
|
||||||
[key: string]: string;
|
[klass: string]: any;
|
||||||
};
|
} | null;
|
||||||
constructor(_differs: KeyValueDiffers, _ngEl: ElementRef, _renderer: Renderer2);
|
constructor(delegate: NgStyleImpl);
|
||||||
ngDoCheck(): void;
|
ngDoCheck(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export declare class NgStyleBase {
|
||||||
|
protected _delegate: NgStyleImpl;
|
||||||
|
constructor(_delegate: NgStyleImpl);
|
||||||
|
getValue(): {
|
||||||
|
[key: string]: any;
|
||||||
|
} | null;
|
||||||
|
static ngDirectiveDef: any;
|
||||||
|
}
|
||||||
|
|
||||||
export declare class NgSwitch {
|
export declare class NgSwitch {
|
||||||
ngSwitch: any;
|
ngSwitch: any;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue