Angular2 - Directive - NgClass

先来看基本用法

The result of an expression evaluation is interpreted differently depending on type of the expression evaluation result:

  • string - all the CSS classes listed in a string (space delimited) are added
  • Array - all the CSS classes (Array elements) are added
  • Object - each key corresponds to a CSS class name while values are interpreted as expressions evaluating to Boolean. If a given expression evaluates to true a corresponding CSS class is added - otherwise it is removed.

再源码

没想到这么长。。。

@Directive({selector: '[ngClass]', inputs: ['rawClass: ngClass', 'initialClasses: class']})
export class NgClass implements DoCheck, OnDestroy {
private _differ: any;
private _mode: string;
private _initialClasses = [];
private _rawClass;

constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {}

set initialClasses(v) {
this._applyInitialClasses(true);
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
this._applyInitialClasses(false);
this._applyClasses(this._rawClass, false);
}

set rawClass(v) {
this._cleanupClasses(this._rawClass);

if (isString(v)) {
v = v.split(' ');
}

this._rawClass = v;
if (isPresent(v)) {
if (isListLikeIterable(v)) {
this._differ = this._iterableDiffers.find(v).create(null);
this._mode = 'iterable';
} else {
this._differ = this._keyValueDiffers.find(v).create(null);
this._mode = 'keyValue';
}
} else {
this._differ = null;
}
}

ngDoCheck(): void {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._rawClass);
if (isPresent(changes)) {
if (this._mode == 'iterable') {
this._applyIterableChanges(changes);
} else {
this._applyKeyValueChanges(changes);
}
}
}
}

ngOnDestroy(): void { this._cleanupClasses(this._rawClass); }

private _cleanupClasses(rawClassVal): void {
this._applyClasses(rawClassVal, true);
this._applyInitialClasses(false);
}

private _applyKeyValueChanges(changes: 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: any): void {
changes.forEachAddedItem((record) => { this._toggleClass(record.item, true); });
changes.forEachRemovedItem((record) => { this._toggleClass(record.item, false); });
}

private _applyInitialClasses(isCleanup: boolean) {
this._initialClasses.forEach(className => this._toggleClass(className, !isCleanup));
}

private _applyClasses(rawClassVal: string[] | Set<string>| {[key: string]: string},
isCleanup: boolean) {
if (isPresent(rawClassVal)) {
if (isArray(rawClassVal)) {
(<string[]>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
} else if (rawClassVal instanceof Set) {
(<Set<string>>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
} else {
StringMapWrapper.forEach(<{[k: string]: string}>rawClassVal, (expVal, className) => {
if (expVal) this._toggleClass(className, !isCleanup);
});
}
}
}

private _toggleClass(className: string, enabled): void {
className = className.trim();
if (className.length > 0) {
if (className.indexOf(' ') > -1) {
var classes = className.split(/\s+/g);
for (var i = 0, len = classes.length; i < len; i++) {
this._renderer.setElementClass(this._ngEl.nativeElement, classes[i], enabled);
}
} else {
this._renderer.setElementClass(this._ngEl.nativeElement, className, enabled);
}
}
}
}

  • inputs - ng-class bound to NgClass.rawClass / class bound to NgClass.initialClasses考虑到ng给的还有原来html上的
set initialClasses(v) {
this._applyInitialClasses(true);
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
this._applyInitialClasses(false);
this._applyClasses(this._rawClass, false);
}

this._applyInitialClasses(true);

对于原html上class处理:
调用_toggleClass(className: string, false)
可能遇到 class=”xx yy zz” class=”ii oo pp”在同一个元素的情况,这里处理
先把classNamesplite成 数组 classes
然后把所有的class 都从element上去掉了- 因为第一步给了false


this._initialClasses = isPresent(v) && isString(v) ? v.split(‘ ‘) : [];
this._applyInitialClasses(false);

这里都是为了处理同一个元素出现多个 class属性的问题

this._applyClasses(this._rawClass, false);
如果有ngclass 就渲染上,没有就算了


set rawClass(v) {
this._cleanupClasses(this._rawClass);

if (isString(v)) {
v = v.split(' ');
}

this._rawClass = v;
if (isPresent(v)) {
if (isListLikeIterable(v)) {
this._differ = this._iterableDiffers.find(v).create(null);
this._mode = 'iterable';
} else {
this._differ = this._keyValueDiffers.find(v).create(null);
this._mode = 'keyValue';
}
} else {
this._differ = null;
}
}

_cleanupClasses 就是不管哪里进来的class 都从DOM干掉了

判断类型 如果是string 就赋值数组
如果是迭代或者obj 就 也返回一套数组但类型是 IterableDifferFactory[]并且设置mode.
string和obj mode是 keyValue, 数组就是iterable


ngDoCheck(): void {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._rawClass);
if (isPresent(changes)) {
if (this._mode == 'iterable') {
this._applyIterableChanges(changes);
} else {
this._applyKeyValueChanges(changes);
}
}
}
}

每次检查是否有区别,有的话就多的加上,少的干掉


ngOnDestroy(): void { this._cleanupClasses(this._rawClass); }

就是全都干掉


总的来说,从ngclass 和 class 来的数据

先要把元素清理干净,那些特殊情况
然后都分成数组缓存变量中

differ检查重新渲染的节奏,这里没有关inline的style