Revert: "feat(ivy): convert [ngStyle] and [ngClass] to use ivy styling bindings" (#34616)

This change reverts https://github.com/angular/angular/pull/28711
NOTE: This change deletes code and creates a BROKEN SHA. If reverting this SHA needs to be reverted with the next SHA to get back into a valid state.

The change removes the fact that `NgStyle`/`NgClass` is special and colaborates with the `[style]`/`[class]` to merge its styles. By reverting to old behavior we have better backwards compatiblity since it is no longer treated special and simply overwrites the styles (same as VE)

PR Close #34616
This commit is contained in:
Miško Hevery 2020-01-03 15:30:40 -08:00
parent 4005815114
commit 69de7680f5
12 changed files with 172 additions and 1014 deletions

View File

@ -20,7 +20,7 @@ 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, getLocaleCurrencyCode, getLocaleCurrencyName, getLocaleCurrencySymbol, getLocaleDirection} 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, getLocaleCurrencyCode, getLocaleCurrencyName, getLocaleCurrencySymbol, getLocaleDirection} from './i18n/locale_data_api';
export {parseCookieValue as ɵparseCookieValue} from './cookie'; export {parseCookieValue as ɵparseCookieValue} from './cookie';
export {CommonModule} from './common_module'; export {CommonModule} from './common_module';
export {NgClass, NgClassBase, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgStyleBase, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index'; export {NgClass, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, 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 {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';

View File

@ -7,18 +7,17 @@
*/ */
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, NgStyleBase} from './ng_style'; import {NgStyle} 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,7 +26,6 @@ export {
NgPlural, NgPlural,
NgPluralCase, NgPluralCase,
NgStyle, NgStyle,
NgStyleBase,
NgSwitch, NgSwitch,
NgSwitchCase, NgSwitchCase,
NgSwitchDefault, NgSwitchDefault,

View File

@ -5,69 +5,9 @@
* 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, ɵɵclassMap, ɵɵdefineDirective} 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'; type NgClassSupportedTypes = string[] | Set<string>| {[klass: string]: any} | null | undefined;
/*
* 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,
hostVars: 2,
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
if (rf & ɵRenderFlags.Update) {
ɵɵclassMap(ctx.getValue());
}
}
});
export const ngClassDirectiveDef = ngClassDirectiveDef__PRE_R3__;
export const ngClassFactoryDef__PRE_R3__ = undefined;
export const ngClassFactoryDef__POST_R3__ = function() {};
export const ngClassFactoryDef = ngClassFactoryDef__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
* `ɵdir` property on the class).
*
* Note that the `ɵdir` 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 ɵdir: any = ngClassDirectiveDef;
static ɵfac: any = ngClassFactoryDef;
constructor(protected _delegate: NgClassImpl) {}
getValue() { return this._delegate.getValue(); }
}
/** /**
* @ngModule CommonModule * @ngModule CommonModule
@ -97,17 +37,124 @@ export class NgClassBase {
* *
* @publicApi * @publicApi
*/ */
@Directive({selector: '[ngClass]', providers: [NgClassImplProvider]}) @Directive({selector: '[ngClass]'})
export class NgClass extends NgClassBase implements DoCheck { export class NgClass implements DoCheck {
constructor(delegate: NgClassImpl) { super(delegate); } private _iterableDiffer: IterableDiffer<string>|null = null;
private _keyValueDiffer: KeyValueDiffer<string, any>|null = null;
private _initialClasses: string[] = [];
private _rawClass: NgClassSupportedTypes = null;
constructor(
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer2) {}
@Input('class') @Input('class')
set klass(value: string) { this._delegate.setClass(value); } set klass(value: string) {
this._removeClasses(this._initialClasses);
this._initialClasses = typeof value === 'string' ? value.split(/\s+/) : [];
this._applyClasses(this._initialClasses);
this._applyClasses(this._rawClass);
}
@Input('ngClass') @Input('ngClass')
set ngClass(value: string|string[]|Set<string>|{[klass: string]: any}) { set ngClass(value: string|string[]|Set<string>|{[klass: string]: any}) {
this._delegate.setNgClass(value); 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();
}
}
} }
ngDoCheck() { this._delegate.applyChanges(); } ngDoCheck() {
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: NgClassSupportedTypes) {
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: NgClassSupportedTypes) {
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);
}
});
}
}
} }

View File

@ -1,208 +0,0 @@
/**
* @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 extends 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) {
super();
}
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|string[]|Set<string>|{[klass: string]: any}) {
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 extends NgClassImpl {
private _value: {[key: string]: boolean}|null = null;
private _ngClassDiffer = new StylingDiffer<{[key: string]: true}>(
'NgClass', StylingDifferOptions.TrimProperties|
StylingDifferOptions.AllowSubKeys|
StylingDifferOptions.AllowStringValue|StylingDifferOptions.ForceAsMap);
private _classStringDiffer: StylingDiffer<{[key: string]: true}>|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.setInput(value);
}
setNgClass(value: string|string[]|Set<string>|{[klass: string]: any}) {
this._ngClassDiffer.setInput(value);
}
applyChanges() {
const classChanged = this._classStringDiffer ? this._classStringDiffer.updateValue() : false;
const ngClassChanged = this._ngClassDiffer.updateValue();
if (classChanged || ngClassChanged) {
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;
}
}
}
// 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__;

View File

@ -5,69 +5,8 @@
* 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, ɵɵstyleMap} 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;
export const ngStyleFactoryDef__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,
hostVars: 2,
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
if (rf & ɵRenderFlags.Update) {
ɵɵstyleMap(ctx.getValue());
}
}
});
export const ngStyleFactoryDef__POST_R3__ = function() {};
export const ngStyleDirectiveDef = ngStyleDirectiveDef__PRE_R3__;
export const ngStyleFactoryDef = 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
* `ɵdir` property on the class).
*
* Note that the `ɵdir` 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 ɵdir: any = ngStyleDirectiveDef;
static ɵfac: any = ngStyleFactoryDef;
constructor(protected _delegate: NgStyleImpl) {}
getValue() { return this._delegate.getValue(); }
}
/** /**
* @ngModule CommonModule * @ngModule CommonModule
@ -105,12 +44,45 @@ export class NgStyleBase {
* *
* @publicApi * @publicApi
*/ */
@Directive({selector: '[ngStyle]', providers: [NgStyleImplProvider]}) @Directive({selector: '[ngStyle]'})
export class NgStyle extends NgStyleBase implements DoCheck { export class NgStyle implements DoCheck {
constructor(delegate: NgStyleImpl) { super(delegate); } private _ngStyle: {[key: string]: string}|null = null;
private _differ: KeyValueDiffer<string, string|number>|null = null;
constructor(
private _ngEl: ElementRef, private _differs: KeyValueDiffers, private _renderer: Renderer2) {}
@Input('ngStyle') @Input('ngStyle')
set ngStyle(value: {[klass: string]: any}|null) { this._delegate.setNgStyle(value); } set ngStyle(values: {[klass: string]: any}|null) {
this._ngStyle = values;
ngDoCheck() { this._delegate.applyChanges(); } if (!this._differ && values) {
this._differ = this._differs.find(values).create();
}
}
ngDoCheck() {
if (this._differ) {
const changes = this._differ.diff(this._ngStyle !);
if (changes) {
this._applyChanges(changes);
}
}
}
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);
}
}
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));
}
} }

View File

@ -1,114 +0,0 @@
/**
* @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}>('NgStyle', StylingDifferOptions.AllowUnits);
private _value: {[key: string]: any}|null = null;
getValue() { return this._value; }
setNgStyle(value: {[key: string]: any}|null) { this._differ.setInput(value); }
applyChanges() {
if (this._differ.updateValue()) {
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__;

View File

@ -1,390 +0,0 @@
/**
* @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 **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
* - 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 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
* 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 should respect [class.name] and [style.prop] bindings (and not arbitrarily
* overwrite their changes)
*
* ```
* <!-- 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 extends({[key: string]: string | null} | {[key: string]: true})> {
/**
* Normalized string map representing the last value set via `setValue()` or null if no value has
* been set or the last set value was null
*/
public readonly value: T|null = null;
/**
* The last set value that was applied via `setValue()`
*/
private _inputValue: T|string|string[]|Set<string>|null = null;
/**
* The type of value that the `_lastSetValue` variable is
*/
private _inputValueType: StylingDifferValueTypes = StylingDifferValueTypes.Null;
/**
* Whether or not the last value change occurred because the variable itself changed reference
* (identity)
*/
private _inputValueIdentityChangeSinceLastCheck = false;
constructor(private _name: string, private _options: StylingDifferOptions) {}
/**
* Sets the input value for the differ and updates the output value if necessary.
*
* @param value the new styling input value provided from the ngClass/ngStyle binding
*/
setInput(value: T|string[]|string|Set<string>|null): void {
if (value !== this._inputValue) {
let type: StylingDifferValueTypes;
if (!value) { // matches empty strings, null, false and undefined
type = StylingDifferValueTypes.Null;
value = null;
} else if (Array.isArray(value)) {
type = StylingDifferValueTypes.Array;
} else if (value instanceof Set) {
type = StylingDifferValueTypes.Set;
} else if (typeof value === 'string') {
if (!(this._options & StylingDifferOptions.AllowStringValue)) {
throw new Error(this._name + ' string values are not allowed');
}
type = StylingDifferValueTypes.String;
} else {
type = StylingDifferValueTypes.StringMap;
}
this._inputValue = value;
this._inputValueType = type;
this._inputValueIdentityChangeSinceLastCheck = true;
this._processValueChange(true);
}
}
/**
* Checks the input value for identity or deep changes and updates output value if necessary.
*
* 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.
*/
updateValue(): boolean {
let valueHasChanged = this._inputValueIdentityChangeSinceLastCheck;
if (!this._inputValueIdentityChangeSinceLastCheck &&
(this._inputValueType & StylingDifferValueTypes.Collection)) {
valueHasChanged = this._processValueChange(false);
} else {
// this is set to false in the event that the value is a collection.
// This way (if the identity hasn't changed), then the algorithm can
// diff the collection value to see if the contents have mutated
// (otherwise the value change was processed during the time when
// the variable changed).
this._inputValueIdentityChangeSinceLastCheck = false;
}
return valueHasChanged;
}
/**
* Examines the last set value to see if there was a change in content.
*
* @param inputValueIdentityChanged whether or not the last set value changed in identity or not
* @returns `true` when the value has changed (either by identity or by shape if its a
* collection)
*/
private _processValueChange(inputValueIdentityChanged: boolean): boolean {
// if the inputValueIdentityChanged then we know that input has changed
let inputChanged = inputValueIdentityChanged;
let newOutputValue: T|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._inputValueType) {
// case 1: [input]="string"
case StylingDifferValueTypes.String: {
if (inputValueIdentityChanged) {
// process string input only if the identity has changed since the strings are immutable
const keys = (this._inputValue as string).split(/\s+/g);
if (this._options & StylingDifferOptions.ForceAsMap) {
newOutputValue = {} as T;
for (let i = 0; i < keys.length; i++) {
(newOutputValue as any)[keys[i]] = true;
}
} else {
newOutputValue = keys.join(' ');
}
}
break;
}
// case 2: [input]="{key:value}"
case StylingDifferValueTypes.StringMap: {
const inputMap = this._inputValue as T;
const inputKeys = Object.keys(inputMap);
if (!inputValueIdentityChanged) {
// if StringMap and the identity has not changed then output value must have already been
// initialized to a StringMap, so we can safely compare the input and output maps
inputChanged = mapsAreEqual(inputKeys, inputMap, this.value as T);
}
if (inputChanged) {
newOutputValue = bulidMapFromStringMap(
trimValues, parseOutUnits, allowSubKeys, inputMap, inputKeys) as T;
}
break;
}
// case 3a: [input]="[str1, str2, ...]"
// case 3b: [input]="Set"
case StylingDifferValueTypes.Array:
case StylingDifferValueTypes.Set: {
const inputKeys = Array.from(this._inputValue as string[] | Set<string>);
if (!inputValueIdentityChanged) {
const outputKeys = Object.keys(this.value !);
inputChanged = !keyArraysAreEqual(outputKeys, inputKeys);
}
if (inputChanged) {
newOutputValue =
bulidMapFromStringArray(this._name, trimValues, allowSubKeys, inputKeys) as T;
}
break;
}
// case 4: [input]="null|undefined"
default:
inputChanged = inputValueIdentityChanged;
newOutputValue = null;
break;
}
if (inputChanged) {
// update the readonly `value` property by casting it to `any` first
(this as any).value = newOutputValue;
}
return inputChanged;
}
}
/**
* 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, //
StringMap = 0b0010, //
Array = 0b0100, //
Set = 0b1000, //
Collection = 0b1110, //
}
/**
* @param trim whether the keys should be trimmed of leading or trailing whitespace
* @param parseOutUnits whether units like "px" should be parsed out of the key name and appended to
* the value
* @param allowSubKeys whether key needs to be subsplit by whitespace into multiple keys
* @param values values of the map
* @param keys keys of the map
* @return a normalized string map based on the input string map
*/
function bulidMapFromStringMap(
trim: boolean, parseOutUnits: boolean, allowSubKeys: boolean,
values: {[key: string]: string | null | true},
keys: string[]): {[key: string]: string | null | true} {
const map: {[key: string]: string | null | true} = {};
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = values[key];
if (value !== undefined) {
if (typeof value !== 'boolean') {
value = '' + value;
}
// Map uses untrimmed keys, so don't trim until passing to `setMapValues`
setMapValues(map, trim ? key.trim() : key, value, parseOutUnits, allowSubKeys);
}
}
return map;
}
/**
* @param trim whether the keys should be trimmed of leading or trailing whitespace
* @param parseOutUnits whether units like "px" should be parsed out of the key name and appended to
* the value
* @param allowSubKeys whether key needs to be subsplit by whitespace into multiple keys
* @param values values of the map
* @param keys keys of the map
* @return a normalized string map based on the input string array
*/
function bulidMapFromStringArray(
errorPrefix: string, trim: boolean, allowSubKeys: boolean,
keys: string[]): {[key: string]: true} {
const map: {[key: string]: true} = {};
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
ngDevMode && assertValidValue(errorPrefix, key);
key = trim ? key.trim() : key;
setMapValues(map, key, 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]: unknown}, key: string, value: string | null | true, 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]: unknown}, key: string, value: string | true | null,
parseOutUnits: boolean) {
if (parseOutUnits && typeof value === 'string') {
// parse out the unit (e.g. ".px") from the key and append it to the value
// e.g. for [width.px]="40" => ["width","40px"]
const unitIndex = key.indexOf('.');
if (unitIndex > 0) {
const unit = key.substr(unitIndex + 1); // skip over the "." in "width.px"
key = key.substring(0, unitIndex);
value += unit;
}
}
map[key] = value;
}
/**
* Compares two maps and returns true if they are equal
*
* @param inputKeys value of `Object.keys(inputMap)` it's unclear if this actually performs better
* @param inputMap map to compare
* @param outputMap map to compare
*/
function mapsAreEqual(
inputKeys: string[], inputMap: {[key: string]: unknown},
outputMap: {[key: string]: unknown}, ): boolean {
const outputKeys = Object.keys(outputMap);
if (inputKeys.length !== outputKeys.length) {
return true;
}
for (let i = 0, n = inputKeys.length; i <= n; i++) {
let key = inputKeys[i];
if (key !== outputKeys[i] || inputMap[key] !== outputMap[key]) {
return true;
}
}
return false;
}
/**
* Compares two Object.keys() arrays and returns true if they are equal.
*
* @param keyArray1 Object.keys() array to compare
* @param keyArray1 Object.keys() array to compare
*/
function keyArraysAreEqual(keyArray1: string[] | null, keyArray2: string[] | null): boolean {
if (!Array.isArray(keyArray1) || !Array.isArray(keyArray2)) {
return false;
}
if (keyArray1.length !== keyArray2.length) {
return false;
}
for (let i = 0; i < keyArray1.length; i++) {
if (keyArray1[i] !== keyArray2[i]) {
return false;
}
}
return true;
}

View File

@ -6,9 +6,5 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export {ngClassDirectiveDef__POST_R3__ as ɵngClassDirectiveDef__POST_R3__, ngClassFactoryDef__POST_R3__ as ɵngClassFactoryDef__POST_R3__} from './directives/ng_class';
export {NgClassImpl as ɵNgClassImpl, NgClassImplProvider__POST_R3__ as ɵNgClassImplProvider__POST_R3__, NgClassR2Impl as ɵNgClassR2Impl} from './directives/ng_class_impl';
export {ngStyleDirectiveDef__POST_R3__ as ɵngStyleDirectiveDef__POST_R3__, ngStyleFactoryDef__POST_R3__ as ɵngStyleFactoryDef__POST_R3__} from './directives/ng_style';
export {NgStyleImpl as ɵNgStyleImpl, NgStyleImplProvider__POST_R3__ as ɵNgStyleImplProvider__POST_R3__, NgStyleR2Impl as ɵNgStyleR2Impl} from './directives/ng_style_impl';
export {DomAdapter as ɵDomAdapter, getDOM as ɵgetDOM, setRootDomAdapter as ɵsetRootDomAdapter} from './dom_adapter'; export {DomAdapter as ɵDomAdapter, getDOM as ɵgetDOM, setRootDomAdapter as ɵsetRootDomAdapter} from './dom_adapter';
export {BrowserPlatformLocation as ɵBrowserPlatformLocation} from './location/platform_location'; export {BrowserPlatformLocation as ɵBrowserPlatformLocation} from './location/platform_location';

View File

@ -1,123 +0,0 @@
/**
* @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 {StylingDiffer, StylingDifferOptions} from '@angular/common/src/directives/styling_differ';
describe('StylingDiffer', () => {
it('should create a key/value object of values from a string', () => {
const d = new StylingDiffer(
'ngClass', StylingDifferOptions.ForceAsMap | StylingDifferOptions.AllowStringValue);
expect(d.value).toEqual(null);
d.setInput('one two');
expect(d.value).toEqual({one: true, two: true});
d.setInput('three');
expect(d.value).toEqual({three: true});
});
describe('setInput', () => {
it('should not emit that a value has changed if a new non-collection value was not set', () => {
const d = new StylingDiffer(
'ngClass', StylingDifferOptions.ForceAsMap | StylingDifferOptions.AllowStringValue);
expect(d.value).toEqual(null);
d.setInput('one two');
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({one: true, two: true});
expect(d.updateValue()).toBeFalsy();
expect(d.value).toEqual({one: true, two: true});
d.setInput('three');
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({three: true});
expect(d.updateValue()).toBeFalsy();
expect(d.value).toEqual({three: true});
d.setInput(null);
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual(null);
expect(d.updateValue()).toBeFalsy();
expect(d.value).toEqual(null);
});
});
describe('updateValue', () => {
it('should update the differ value if the contents of a input StringMap change', () => {
const d = new StylingDiffer('ngClass', StylingDifferOptions.ForceAsMap);
const myMap: {[key: string]: true} = {};
myMap['abc'] = true;
d.setInput(myMap);
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({abc: true});
expect(d.updateValue()).toBeFalsy();
myMap['def'] = true;
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({abc: true, def: true});
expect(d.updateValue()).toBeFalsy();
delete myMap['abc'];
delete myMap['def'];
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({});
expect(d.updateValue()).toBeFalsy();
});
it('should update the differ value if the contents of an input Array change', () => {
const d = new StylingDiffer('ngClass', StylingDifferOptions.ForceAsMap);
const myArray: string[] = [];
myArray.push('abc');
d.setInput(myArray);
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({abc: true});
expect(d.updateValue()).toBeFalsy();
myArray.push('def');
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({abc: true, def: true});
expect(d.updateValue()).toBeFalsy();
myArray.length = 0;
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({});
expect(d.updateValue()).toBeFalsy();
});
it('should update the differ value if the contents of an input Set change', () => {
const d = new StylingDiffer('ngClass', StylingDifferOptions.ForceAsMap);
const mySet = new Set<string>();
mySet.add('abc');
d.setInput(mySet);
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({abc: true});
expect(d.updateValue()).toBeFalsy();
mySet.add('def');
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({abc: true, def: true});
expect(d.updateValue()).toBeFalsy();
mySet.clear();
expect(d.updateValue()).toBeTruthy();
expect(d.value).toEqual({});
expect(d.updateValue()).toBeFalsy();
});
});
});

View File

@ -288,21 +288,23 @@ runInEachFileSystem(() => {
propertiesToConsider: ['esm2015'] propertiesToConsider: ['esm2015']
}); });
// In `@angular/common` the `NgClassR3Impl` class gets exported as something like // In `@angular/common` the `BrowserPlatformLocation` class gets exported as something like
// `ɵangular_packages_common_common_a`. // `ɵangular_packages_common_common_a`.
const jsContents = fs.readFile(_(`/node_modules/@angular/common/fesm2015/common.js`)); const jsContents = fs.readFile(_(`/node_modules/@angular/common/fesm2015/common.js`));
const exportedNameMatch = jsContents.match(/export.* NgClassR3Impl as ([^ ,}]+)/); const exportedNameMatch =
jsContents.match(/export.* BrowserPlatformLocation as ([^ ,}]+)/);
if (exportedNameMatch === null) { if (exportedNameMatch === null) {
return fail( return fail(
'Expected `/node_modules/@angular/common/fesm2015/common.js` to export `NgClassR3Impl` via an alias'); 'Expected `/node_modules/@angular/common/fesm2015/common.js` to export `BrowserPlatformLocation` via an alias');
} }
const exportedName = exportedNameMatch[1]; const exportedName = exportedNameMatch[1];
// We need to make sure that the flat typings file exports this directly // We need to make sure that the flat typings file exports this directly
const dtsContents = fs.readFile(_('/node_modules/@angular/common/common.d.ts')); const dtsContents = fs.readFile(_('/node_modules/@angular/common/common.d.ts'));
expect(dtsContents).toContain(`export declare class ${exportedName} extends ɵNgClassImpl`); expect(dtsContents)
.toContain(`export declare class ${exportedName} extends PlatformLocation`);
// And that ngcc's modifications to that class use the correct (exported) name // And that ngcc's modifications to that class use the correct (exported) name
expect(dtsContents).toContain(`static ɵprov: ɵngcc0.ɵɵInjectableDef<${exportedName}>`); expect(dtsContents).toContain(`static ɵfac: ɵngcc0.ɵɵFactoryDef<${exportedName}>`);
}); });
it('should add generic type for ModuleWithProviders and generate exports for private modules', it('should add generic type for ModuleWithProviders and generate exports for private modules',

View File

@ -12,19 +12,18 @@ import {Type} from '../core';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {Sanitizer} from '../sanitization/sanitizer'; import {Sanitizer} from '../sanitization/sanitizer';
import {assertDataInRange} from '../util/assert'; import {assertDataInRange} from '../util/assert';
import {assertComponentType} from './assert'; import {assertComponentType} from './assert';
import {getComponentDef} from './definition'; import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {registerPostOrderHooks} from './hooks'; import {registerPostOrderHooks} from './hooks';
import {CLEAN_PROMISE, addHostBindingsToExpandoInstructions, addToViewTree, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, growHostVarsSpace, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderInitialStyling, renderView} from './instructions/shared'; import {CLEAN_PROMISE, addHostBindingsToExpandoInstructions, addToViewTree, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, growHostVarsSpace, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderView} from './instructions/shared';
import {registerInitialStylingOnTNode} from './instructions/styling';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition'; import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType} from './interfaces/node'; import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player'; import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view'; import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, incrementActiveDirectiveId, leaveView, setActiveHostElement} from './state'; import {enterView, getPreviousOrParentTNode, incrementActiveDirectiveId, leaveView, setActiveHostElement} from './state';
import {computeStaticStyling} from './styling/static_styling';
import {setUpAttributes} from './util/attrs_utils'; import {setUpAttributes} from './util/attrs_utils';
import {publishDefaultGlobalUtils} from './util/global_utils'; import {publishDefaultGlobalUtils} from './util/global_utils';
import {defaultScheduler, stringifyForError} from './util/misc_utils'; import {defaultScheduler, stringifyForError} from './util/misc_utils';
@ -175,10 +174,9 @@ export function createRootComponentView(
const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null); const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null);
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs; const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
if (mergedAttrs !== null) { if (mergedAttrs !== null) {
registerInitialStylingOnTNode(tNode, mergedAttrs, 0); computeStaticStyling(tNode, mergedAttrs);
if (rNode !== null) { if (rNode !== null) {
setUpAttributes(renderer, rNode, mergedAttrs); setUpAttributes(renderer, rNode, mergedAttrs);
renderInitialStyling(renderer, rNode, tNode, false);
} }
} }
const componentView = createLView( const componentView = createLView(

View File

@ -189,25 +189,15 @@ export declare class LowerCasePipe implements PipeTransform {
transform(value: string): string; transform(value: string): string;
} }
export declare class NgClass extends NgClassBase implements DoCheck { export declare class NgClass implements DoCheck {
set klass(value: string); set klass(value: string);
set ngClass(value: string | string[] | Set<string> | { set ngClass(value: string | string[] | Set<string> | {
[klass: string]: any; [klass: string]: any;
}); });
constructor(delegate: NgClassImpl); constructor(_iterableDiffers: IterableDiffers, _keyValueDiffers: KeyValueDiffers, _ngEl: ElementRef, _renderer: Renderer2);
ngDoCheck(): void; ngDoCheck(): void;
} }
export declare class NgClassBase {
protected _delegate: NgClassImpl;
constructor(_delegate: NgClassImpl);
getValue(): {
[key: string]: any;
} | null;
static ɵdir: any;
static ɵfac: 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[][];
@ -275,24 +265,14 @@ 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 extends NgStyleBase implements DoCheck { export declare class NgStyle implements DoCheck {
set ngStyle(value: { set ngStyle(values: {
[klass: string]: any; [klass: string]: any;
} | null); } | null);
constructor(delegate: NgStyleImpl); constructor(_ngEl: ElementRef, _differs: KeyValueDiffers, _renderer: Renderer2);
ngDoCheck(): void; ngDoCheck(): void;
} }
export declare class NgStyleBase {
protected _delegate: NgStyleImpl;
constructor(_delegate: NgStyleImpl);
getValue(): {
[key: string]: any;
} | null;
static ɵdir: any;
static ɵfac: any;
}
export declare class NgSwitch { export declare class NgSwitch {
set ngSwitch(newValue: any); set ngSwitch(newValue: any);
} }