|  | 
| 5 | 5 |  * Use of this source code is governed by an MIT-style license that can be | 
| 6 | 6 |  * found in the LICENSE file at https://angular.io/license | 
| 7 | 7 |  */ | 
|  | 8 | +import {Directive, DoCheck, Input, ɵRenderFlags, ɵdefineDirective, ɵelementStyling, ɵelementStylingApply, ɵelementStylingMap} from '@angular/core'; | 
| 8 | 9 | 
 | 
| 9 |  | -import {Directive, DoCheck, ElementRef, Input, IterableChanges, IterableDiffer, IterableDiffers, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer2, ɵisListLikeIterable as isListLikeIterable, ɵstringify as stringify} from '@angular/core'; | 
|  | 10 | +import {NgClassImpl, NgClassImplProvider} from './ng_class_impl'; | 
|  | 11 | + | 
|  | 12 | + | 
|  | 13 | + | 
|  | 14 | +/* | 
|  | 15 | + * NgClass (as well as NgStyle) behaves differently when loaded in the VE and when not. | 
|  | 16 | + * | 
|  | 17 | + * If the VE is present (which is for older versions of Angular) then NgClass will inject | 
|  | 18 | + * the legacy diffing algorithm as a service and delegate all styling changes to that. | 
|  | 19 | + * | 
|  | 20 | + * If the VE is not present then NgStyle will normalize (through the injected service) and | 
|  | 21 | + * then write all styling changes to the `[style]` binding directly (through a host binding). | 
|  | 22 | + * Then Angular will notice the host binding change and treat the changes as styling | 
|  | 23 | + * changes and apply them via the core styling instructions that exist within Angular. | 
|  | 24 | + */ | 
|  | 25 | + | 
|  | 26 | +// used when the VE is present | 
|  | 27 | +export const ngClassDirectiveDef__PRE_R3__ = undefined; | 
|  | 28 | + | 
|  | 29 | +// used when the VE is not present (note the directive will | 
|  | 30 | +// never be instantiated normally because it is apart of a | 
|  | 31 | +// base class) | 
|  | 32 | +export const ngClassDirectiveDef__POST_R3__ = ɵdefineDirective({ | 
|  | 33 | +  type: function() {} as any, | 
|  | 34 | +  selectors: null as any, | 
|  | 35 | +  factory: () => {}, | 
|  | 36 | +  hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) { | 
|  | 37 | +    if (rf & ɵRenderFlags.Create) { | 
|  | 38 | +      ɵelementStyling(null, null, null, ctx); | 
|  | 39 | +    } | 
|  | 40 | +    if (rf & ɵRenderFlags.Update) { | 
|  | 41 | +      ɵelementStylingMap(elIndex, ctx.getValue(), null, ctx); | 
|  | 42 | +      ɵelementStylingApply(elIndex, ctx); | 
|  | 43 | +    } | 
|  | 44 | +  } | 
|  | 45 | +}); | 
|  | 46 | + | 
|  | 47 | +export const ngClassDirectiveDef = ngClassDirectiveDef__PRE_R3__; | 
|  | 48 | + | 
|  | 49 | +/** | 
|  | 50 | + * Serves as the base non-VE container for NgClass. | 
|  | 51 | + * | 
|  | 52 | + * While this is a base class that NgClass extends from, the | 
|  | 53 | + * class itself acts as a container for non-VE code to setup | 
|  | 54 | + * a link to the `[class]` host binding (via the static | 
|  | 55 | + * `ngDirectiveDef` property on the class). | 
|  | 56 | + * | 
|  | 57 | + * Note that the `ngDirectiveDef` property's code is switched | 
|  | 58 | + * depending if VE is present or not (this allows for the | 
|  | 59 | + * binding code to be set only for newer versions of Angular). | 
|  | 60 | + * | 
|  | 61 | + * @publicApi | 
|  | 62 | + */ | 
|  | 63 | +export class NgClassBase { | 
|  | 64 | +  static ngDirectiveDef: any = ngClassDirectiveDef; | 
|  | 65 | + | 
|  | 66 | +  constructor(protected _delegate: NgClassImpl) {} | 
|  | 67 | + | 
|  | 68 | +  getValue() { return this._delegate.getValue(); } | 
|  | 69 | +} | 
| 10 | 70 | 
 | 
| 11 | 71 | /** | 
| 12 | 72 |  * @ngModule CommonModule | 
| @@ -36,126 +96,17 @@ import {Directive, DoCheck, ElementRef, Input, IterableChanges, IterableDiffer, | 
| 36 | 96 |  * | 
| 37 | 97 |  * @publicApi | 
| 38 | 98 |  */ | 
| 39 |  | -@Directive({selector: '[ngClass]'}) | 
| 40 |  | -export class NgClass implements DoCheck { | 
| 41 |  | -  // TODO(issue/24571): remove '!'. | 
| 42 |  | -  private _iterableDiffer !: IterableDiffer<string>| null; | 
| 43 |  | -  // TODO(issue/24571): remove '!'. | 
| 44 |  | -  private _keyValueDiffer !: KeyValueDiffer<string, any>| null; | 
| 45 |  | -  private _initialClasses: string[] = []; | 
| 46 |  | -  // TODO(issue/24571): remove '!'. | 
| 47 |  | -  private _rawClass !: string[] | Set<string>| {[klass: string]: any}; | 
| 48 |  | - | 
| 49 |  | -  constructor( | 
| 50 |  | -      private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers, | 
| 51 |  | -      private _ngEl: ElementRef, private _renderer: Renderer2) {} | 
|  | 99 | +@Directive({selector: '[ngClass]', providers: [NgClassImplProvider]}) | 
|  | 100 | +export class NgClass extends NgClassBase implements DoCheck { | 
|  | 101 | +  constructor(delegate: NgClassImpl) { super(delegate); } | 
| 52 | 102 | 
 | 
| 53 | 103 |   @Input('class') | 
| 54 |  | -  set klass(value: string) { | 
| 55 |  | -    this._removeClasses(this._initialClasses); | 
| 56 |  | -    this._initialClasses = typeof value === 'string' ? value.split(/\s+/) : []; | 
| 57 |  | -    this._applyClasses(this._initialClasses); | 
| 58 |  | -    this._applyClasses(this._rawClass); | 
| 59 |  | -  } | 
|  | 104 | +  set klass(value: string) { this._delegate.setClass(value); } | 
| 60 | 105 | 
 | 
| 61 |  | -  @Input() | 
|  | 106 | +  @Input('ngClass') | 
| 62 | 107 |   set ngClass(value: string|string[]|Set<string>|{[klass: string]: any}) { | 
| 63 |  | -    this._removeClasses(this._rawClass); | 
| 64 |  | -    this._applyClasses(this._initialClasses); | 
| 65 |  | - | 
| 66 |  | -    this._iterableDiffer = null; | 
| 67 |  | -    this._keyValueDiffer = null; | 
| 68 |  | - | 
| 69 |  | -    this._rawClass = typeof value === 'string' ? value.split(/\s+/) : value; | 
| 70 |  | - | 
| 71 |  | -    if (this._rawClass) { | 
| 72 |  | -      if (isListLikeIterable(this._rawClass)) { | 
| 73 |  | -        this._iterableDiffer = this._iterableDiffers.find(this._rawClass).create(); | 
| 74 |  | -      } else { | 
| 75 |  | -        this._keyValueDiffer = this._keyValueDiffers.find(this._rawClass).create(); | 
| 76 |  | -      } | 
| 77 |  | -    } | 
| 78 |  | -  } | 
| 79 |  | - | 
| 80 |  | -  ngDoCheck(): void { | 
| 81 |  | -    if (this._iterableDiffer) { | 
| 82 |  | -      const iterableChanges = this._iterableDiffer.diff(this._rawClass as string[]); | 
| 83 |  | -      if (iterableChanges) { | 
| 84 |  | -        this._applyIterableChanges(iterableChanges); | 
| 85 |  | -      } | 
| 86 |  | -    } else if (this._keyValueDiffer) { | 
| 87 |  | -      const keyValueChanges = this._keyValueDiffer.diff(this._rawClass as{[k: string]: any}); | 
| 88 |  | -      if (keyValueChanges) { | 
| 89 |  | -        this._applyKeyValueChanges(keyValueChanges); | 
| 90 |  | -      } | 
| 91 |  | -    } | 
| 92 |  | -  } | 
| 93 |  | - | 
| 94 |  | -  private _applyKeyValueChanges(changes: KeyValueChanges<string, any>): void { | 
| 95 |  | -    changes.forEachAddedItem((record) => this._toggleClass(record.key, record.currentValue)); | 
| 96 |  | -    changes.forEachChangedItem((record) => this._toggleClass(record.key, record.currentValue)); | 
| 97 |  | -    changes.forEachRemovedItem((record) => { | 
| 98 |  | -      if (record.previousValue) { | 
| 99 |  | -        this._toggleClass(record.key, false); | 
| 100 |  | -      } | 
| 101 |  | -    }); | 
|  | 108 | +    this._delegate.setNgClass(value); | 
| 102 | 109 |   } | 
| 103 | 110 | 
 | 
| 104 |  | -  private _applyIterableChanges(changes: IterableChanges<string>): void { | 
| 105 |  | -    changes.forEachAddedItem((record) => { | 
| 106 |  | -      if (typeof record.item === 'string') { | 
| 107 |  | -        this._toggleClass(record.item, true); | 
| 108 |  | -      } else { | 
| 109 |  | -        throw new Error( | 
| 110 |  | -            `NgClass can only toggle CSS classes expressed as strings, got ${stringify(record.item)}`); | 
| 111 |  | -      } | 
| 112 |  | -    }); | 
| 113 |  | - | 
| 114 |  | -    changes.forEachRemovedItem((record) => this._toggleClass(record.item, false)); | 
| 115 |  | -  } | 
| 116 |  | - | 
| 117 |  | -  /** | 
| 118 |  | -   * Applies a collection of CSS classes to the DOM element. | 
| 119 |  | -   * | 
| 120 |  | -   * For argument of type Set and Array CSS class names contained in those collections are always | 
| 121 |  | -   * added. | 
| 122 |  | -   * For argument of type Map CSS class name in the map's key is toggled based on the value (added | 
| 123 |  | -   * for truthy and removed for falsy). | 
| 124 |  | -   */ | 
| 125 |  | -  private _applyClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}) { | 
| 126 |  | -    if (rawClassVal) { | 
| 127 |  | -      if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) { | 
| 128 |  | -        (<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, true)); | 
| 129 |  | -      } else { | 
| 130 |  | -        Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, !!rawClassVal[klass])); | 
| 131 |  | -      } | 
| 132 |  | -    } | 
| 133 |  | -  } | 
| 134 |  | - | 
| 135 |  | -  /** | 
| 136 |  | -   * Removes a collection of CSS classes from the DOM element. This is mostly useful for cleanup | 
| 137 |  | -   * purposes. | 
| 138 |  | -   */ | 
| 139 |  | -  private _removeClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}) { | 
| 140 |  | -    if (rawClassVal) { | 
| 141 |  | -      if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) { | 
| 142 |  | -        (<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, false)); | 
| 143 |  | -      } else { | 
| 144 |  | -        Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, false)); | 
| 145 |  | -      } | 
| 146 |  | -    } | 
| 147 |  | -  } | 
| 148 |  | - | 
| 149 |  | -  private _toggleClass(klass: string, enabled: boolean): void { | 
| 150 |  | -    klass = klass.trim(); | 
| 151 |  | -    if (klass) { | 
| 152 |  | -      klass.split(/\s+/g).forEach(klass => { | 
| 153 |  | -        if (enabled) { | 
| 154 |  | -          this._renderer.addClass(this._ngEl.nativeElement, klass); | 
| 155 |  | -        } else { | 
| 156 |  | -          this._renderer.removeClass(this._ngEl.nativeElement, klass); | 
| 157 |  | -        } | 
| 158 |  | -      }); | 
| 159 |  | -    } | 
| 160 |  | -  } | 
|  | 111 | +  ngDoCheck() { this._delegate.applyChanges(); } | 
| 161 | 112 | } | 
0 commit comments