Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit c7f0f47

Browse files
The host class attribute should be considered static and should not be used as a ClassDirective selector. This means that without associated responsive APIs, the ClassDirective will not be instantiated for elements using only the class attribute.
* Use the `class` attribute as a responsive fallback value only. * Improve support of the ngClass directive for string and object assignments. Fixes #393.
1 parent 64a7c50 commit c7f0f47

File tree

3 files changed

+70
-48
lines changed

3 files changed

+70
-48
lines changed

src/lib/api/core/base-adapter.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective {
7373
} else if (typeof source === 'string') {
7474
this._cacheInputString(key, source);
7575
} else {
76-
throw new Error('Invalid class value provided. Did you want to cache the raw value?');
76+
throw new Error(
77+
`Invalid class value '${key}' provided. Did you want to cache the raw value?`
78+
);
7779
}
7880
}
7981

@@ -124,4 +126,17 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective {
124126
protected _cacheInputString(key = '', source?: string) {
125127
this._inputMap[key] = source;
126128
}
129+
130+
/**
131+
* Does this directive have 1 or more responsive keys defined
132+
* Note: we exclude the 'baseKey' key (which is NOT considered responsive)
133+
*/
134+
public get usesResponsiveAPI() {
135+
const numKeys = Object.keys(this._inputMap).length;
136+
let hasKeys = numKeys > 0;
137+
if ((numKeys == 1) && !!this._inputMap[this._baseKey]) {
138+
hasKeys = false;
139+
}
140+
return hasKeys;
141+
}
127142
}

src/lib/api/ext/class.spec.ts

+30-7
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,44 @@ describe('class directive', () => {
6565
});
6666

6767
it('should override base `class` values with responsive values', () => {
68-
createTestComponent(`<div class="class0" class.xs="class1 class2" ngClass.xs="what"></div>`);
68+
createTestComponent(`<div class="class0" ngClass.xs="what class2"></div>`);
6969

7070
expectNativeEl(fixture).toHaveCssClass('class0');
71-
expectNativeEl(fixture).not.toHaveCssClass('class1');
71+
expectNativeEl(fixture).not.toHaveCssClass('what');
7272
expectNativeEl(fixture).not.toHaveCssClass('class2');
7373

74+
// the CSS classes listed in the string (space delimited) are added,
75+
matchMedia.activate('xs');
76+
expectNativeEl(fixture).toHaveCssClass('class0');
77+
expectNativeEl(fixture).toHaveCssClass('what');
78+
expectNativeEl(fixture).toHaveCssClass('class2');
79+
80+
matchMedia.activate('lg');
81+
expectNativeEl(fixture).toHaveCssClass('class0');
82+
expectNativeEl(fixture).not.toHaveCssClass('what');
83+
expectNativeEl(fixture).not.toHaveCssClass('class2');
84+
});
85+
86+
it('should override base `class` values with responsive values', () => {
87+
createTestComponent(`
88+
<div class="class0" [ngClass.xs]="{'what':true, 'class2':true, 'class0':false}"></div>
89+
`);
90+
91+
expectNativeEl(fixture).toHaveCssClass('class0');
92+
expectNativeEl(fixture).not.toHaveCssClass('what');
93+
expectNativeEl(fixture).not.toHaveCssClass('class2');
94+
95+
// Object keys are CSS classes that get added when the expression given in
96+
// the value evaluates to a truthy value, otherwise they are removed.
7497
matchMedia.activate('xs');
7598
expectNativeEl(fixture).not.toHaveCssClass('class0');
76-
expectNativeEl(fixture).toHaveCssClass('class1');
99+
expectNativeEl(fixture).toHaveCssClass('what');
77100
expectNativeEl(fixture).toHaveCssClass('class2');
78101

79-
// matchMedia.activate('lg');
80-
// expectNativeEl(fixture).toHaveCssClass('class0');
81-
// expectNativeEl(fixture).not.toHaveCssClass('class1');
82-
// expectNativeEl(fixture).not.toHaveCssClass('class2');
102+
matchMedia.activate('lg');
103+
expectNativeEl(fixture).toHaveCssClass('class0');
104+
expectNativeEl(fixture).not.toHaveCssClass('what');
105+
expectNativeEl(fixture).not.toHaveCssClass('class2');
83106
});
84107

85108
it('should keep the raw existing `class` with responsive updates', () => {

src/lib/api/ext/class.ts

+24-40
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export type NgClassType = string | string[] | Set<string> | {[klass: string]: an
3535
*/
3636
@Directive({
3737
selector: `
38-
[class], [class.xs], [class.sm], [class.md], [class.lg], [class.xl],
38+
[class.xs], [class.sm], [class.md], [class.lg], [class.xl],
3939
[class.lt-sm], [class.lt-md], [class.lt-lg], [class.lt-xl],
4040
[class.gt-xs], [class.gt-sm], [class.gt-md], [class.gt-lg],
4141
@@ -77,41 +77,21 @@ export class ClassDirective extends BaseFxDirective
7777

7878
/** Deprecated selectors */
7979

80-
/**
81-
* Base class selector values get applied immediately and are considered destructive overwrites to
82-
* all previous class assignments
83-
*
84-
* Delegate to NgClass:klass setter and cache value for base fallback from responsive APIs.
85-
*/
86-
@Input('class')
87-
set classBase(val: string) {
88-
this._classAdapter.cacheInput('_rawClass', val, true);
89-
this._ngClassInstance.klass = val;
90-
}
80+
@Input('class.xs') set classXs(val: NgClassType) { this._classAdapter.cacheInput('classXs', val); }
81+
@Input('class.sm') set classSm(val: NgClassType) { this._classAdapter.cacheInput('classSm', val); }
82+
@Input('class.md') set classMd(val: NgClassType) { this._classAdapter.cacheInput('classMd', val); }
83+
@Input('class.lg') set classLg(val: NgClassType) { this._classAdapter.cacheInput('classLg', val); }
84+
@Input('class.xl') set classXl(val: NgClassType) { this._classAdapter.cacheInput('classXl', val); }
9185

92-
@Input('class.xs') set classXs(val: NgClassType) { this._classAdapter.cacheInput('classXs', val, true); }
93-
@Input('class.sm') set classSm(val: NgClassType) { this._classAdapter.cacheInput('classSm', val, true); }
94-
@Input('class.md') set classMd(val: NgClassType) { this._classAdapter.cacheInput('classMd', val, true); }
95-
@Input('class.lg') set classLg(val: NgClassType) { this._classAdapter.cacheInput('classLg', val, true); }
96-
@Input('class.xl') set classXl(val: NgClassType) { this._classAdapter.cacheInput('classXl', val, true); }
86+
@Input('class.lt-sm') set classLtSm(val: NgClassType) { this._classAdapter.cacheInput('classLtSm', val); }
87+
@Input('class.lt-md') set classLtMd(val: NgClassType) { this._classAdapter.cacheInput('classLtMd', val); }
88+
@Input('class.lt-lg') set classLtLg(val: NgClassType) { this._classAdapter.cacheInput('classLtLg', val); }
89+
@Input('class.lt-xl') set classLtXl(val: NgClassType) { this._classAdapter.cacheInput('classLtXl', val); }
9790

98-
@Input('class.lt-sm') set classLtSm(val: NgClassType) { this._classAdapter.cacheInput('classLtSm', val, true); }
99-
@Input('class.lt-md') set classLtMd(val: NgClassType) { this._classAdapter.cacheInput('classLtMd', val, true); }
100-
@Input('class.lt-lg') set classLtLg(val: NgClassType) { this._classAdapter.cacheInput('classLtLg', val, true); }
101-
@Input('class.lt-xl') set classLtXl(val: NgClassType) { this._classAdapter.cacheInput('classLtXl', val, true); }
102-
103-
@Input('class.gt-xs') set classGtXs(val: NgClassType) { this._classAdapter.cacheInput('classGtXs', val, true); }
104-
@Input('class.gt-sm') set classGtSm(val: NgClassType) { this._classAdapter.cacheInput('classGtSm', val, true); }
105-
@Input('class.gt-md') set classGtMd(val: NgClassType) { this._classAdapter.cacheInput('classGtMd', val, true); }
106-
@Input('class.gt-lg') set classGtLg(val: NgClassType) { this._classAdapter.cacheInput('classGtLg', val, true); }
107-
108-
/**
109-
* Initial value of the `class` attribute; used as
110-
* fallback and will be merged with nay `ngClass` values
111-
*/
112-
get initialClasses() : string {
113-
return this._classAdapter.queryInput('_rawClass') || "";
114-
}
91+
@Input('class.gt-xs') set classGtXs(val: NgClassType) { this._classAdapter.cacheInput('classGtXs', val); }
92+
@Input('class.gt-sm') set classGtSm(val: NgClassType) { this._classAdapter.cacheInput('classGtSm', val); }
93+
@Input('class.gt-md') set classGtMd(val: NgClassType) { this._classAdapter.cacheInput('classGtMd', val); }
94+
@Input('class.gt-lg') set classGtLg(val: NgClassType) { this._classAdapter.cacheInput('classGtLg', val); }
11595

11696
/* tslint:enable */
11797
constructor(protected monitor: MediaMonitor,
@@ -121,8 +101,9 @@ export class ClassDirective extends BaseFxDirective
121101

122102
super(monitor, _ngEl, _renderer);
123103

124-
this._classAdapter = new BaseFxDirectiveAdapter('class', monitor, _ngEl, _renderer);
125104
this._ngClassAdapter = new BaseFxDirectiveAdapter('ngClass', monitor, _ngEl, _renderer);
105+
this._classAdapter = new BaseFxDirectiveAdapter('class', monitor, _ngEl, _renderer);
106+
this._classAdapter.cacheInput('class', _ngEl.nativeElement.getAttribute('class') || '');
126107

127108
// Create an instance NgClass Directive instance only if `ngClass=""` has NOT been defined on
128109
// the same host element; since the responsive variations may be defined...
@@ -156,7 +137,9 @@ export class ClassDirective extends BaseFxDirective
156137
if (!this._classAdapter.hasMediaQueryListener) {
157138
this._configureMQListener();
158139
}
159-
this._ngClassInstance.ngDoCheck();
140+
if ( this._ngClassInstance) {
141+
this._ngClassInstance.ngDoCheck();
142+
}
160143
}
161144

162145
ngOnDestroy() {
@@ -174,11 +157,12 @@ export class ClassDirective extends BaseFxDirective
174157
* mql change events to onMediaQueryChange handlers
175158
*/
176159
protected _configureMQListener() {
177-
this._classAdapter.listenForMediaQueryChanges('class', '', (changes: MediaChange) => {
160+
const value = this._classAdapter.queryInput('class');
161+
this._classAdapter.listenForMediaQueryChanges('class', value, (changes: MediaChange) => {
178162
this._updateKlass(changes.value);
179163
});
180164

181-
this._ngClassAdapter.listenForMediaQueryChanges('ngClass', '', (changes: MediaChange) => {
165+
this._ngClassAdapter.listenForMediaQueryChanges('ngClass', value, (changes: MediaChange) => {
182166
this._updateNgClass(changes.value);
183167
this._ngClassInstance.ngDoCheck(); // trigger NgClass::_applyIterableChanges()
184168
});
@@ -189,11 +173,11 @@ export class ClassDirective extends BaseFxDirective
189173
* ::ngDoCheck() is not needed
190174
*/
191175
protected _updateKlass(value?: NgClassType) {
192-
let klass = value || this._classAdapter.queryInput('class') || '';
176+
let klass = value || this._classAdapter.queryInput('class');
193177
if (this._classAdapter.mqActivation) {
194178
klass = this._classAdapter.mqActivation.activatedInput;
195179
}
196-
this._ngClassInstance.klass = klass || this.initialClasses;
180+
this._ngClassInstance.klass = klass;
197181
}
198182

199183
/**

0 commit comments

Comments
 (0)