Skip to content

Commit 25b476a

Browse files
damyanpetevkdinevdkamburov
authored
refactor(grid): Host class binding refactor (#12749)
* refactor(grid): cleanup host class binding * refactor(action-strip): host class binding * refactor(chip): host class binding * refactor(chip-area): host class binding * refactor(column-actions): host class binding * chore(lint): add attr.class hostbinding rule --------- Co-authored-by: Konstantin Dinev <[email protected]> Co-authored-by: Deyan Kamburov <[email protected]>
1 parent dfdcd71 commit 25b476a

File tree

11 files changed

+117
-124
lines changed

11 files changed

+117
-124
lines changed

projects/igniteui-angular/.eslintrc.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,14 @@
4141
],
4242
"id-blacklist": "off",
4343
"id-match": "off",
44-
"no-underscore-dangle": "off"
44+
"no-underscore-dangle": "off",
45+
"no-restricted-syntax": [
46+
"error",
47+
{
48+
"selector": "Decorator[expression.callee.name=HostBinding] Literal[value='attr.class']",
49+
"message": "Do not use `attr.class` HostBinding in public-facing components"
50+
}
51+
]
4552
}
4653
},
4754
{

projects/igniteui-angular/src/lib/action-strip/action-strip.component.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ describe('igxActionStrip', () => {
9999
const asQuery = fixture.debugElement.query(By.css('igx-action-strip'));
100100
expect(asQuery.nativeElement.style.display).toBe('none');
101101
});
102+
103+
it('should change displayDensity runtime correctly', () => {
104+
// density with custom class applied
105+
actionStripElement.nativeElement.classList.add('custom');
106+
fixture.detectChanges();
107+
108+
expect(actionStripElement.nativeElement.classList).toEqual(jasmine.arrayWithExactContents(['custom', 'igx-action-strip']));
109+
110+
actionStrip.displayDensity = 'cosy';
111+
fixture.detectChanges();
112+
expect(actionStripElement.nativeElement.classList).toEqual(
113+
jasmine.arrayWithExactContents(['custom', 'igx-action-strip', 'igx-action-strip--cosy'])
114+
);
115+
116+
actionStrip.displayDensity = 'compact';
117+
fixture.detectChanges();
118+
expect(actionStripElement.nativeElement.classList).toEqual(
119+
jasmine.arrayWithExactContents(['custom', 'igx-action-strip', 'igx-action-strip--compact'])
120+
);
121+
});
102122
});
103123

104124
describe('render content as menu', () => {

projects/igniteui-angular/src/lib/action-strip/action-strip.component.ts

Lines changed: 23 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,6 @@ export class IgxActionStripComponent extends DisplayDensityBase implements After
107107
return this._hidden;
108108
}
109109

110-
/**
111-
* Host `class.igx-action-strip` binding.
112-
*
113-
* @hidden
114-
* @internal
115-
*/
116-
@Input('class')
117-
public hostClass: string;
118-
119110
/**
120111
* Gets/Sets the resource strings.
121112
*
@@ -136,7 +127,7 @@ export class IgxActionStripComponent extends DisplayDensityBase implements After
136127

137128
/**
138129
* Hide or not the Action Strip based on if it is a menu.
139-
*
130+
*
140131
* @hidden
141132
* @internal
142133
*/
@@ -180,34 +171,6 @@ export class IgxActionStripComponent extends DisplayDensityBase implements After
180171
super(_displayDensityOptions);
181172
}
182173

183-
/**
184-
* Getter for the 'display' property of the current `IgxActionStrip`
185-
*
186-
* @hidden
187-
* @internal
188-
*/
189-
@HostBinding('style.display')
190-
public get display(): string {
191-
return this._hidden ? 'none' : 'flex';
192-
}
193-
194-
/**
195-
* Host `attr.class` binding.
196-
*
197-
* @hidden
198-
* @internal
199-
*/
200-
@HostBinding('attr.class')
201-
public get hostClasses(): string {
202-
const classes = [this.getComponentDensityClass('igx-action-strip')];
203-
// The custom classes should be at the end.
204-
if (!classes.includes('igx-action-strip')) {
205-
classes.push('igx-action-strip');
206-
}
207-
classes.push(this.hostClass);
208-
return classes.join(' ');
209-
}
210-
211174
/**
212175
* Menu Items list.
213176
*
@@ -227,6 +190,28 @@ export class IgxActionStripComponent extends DisplayDensityBase implements After
227190
return [... this._menuItems.toArray(), ...actions];
228191
}
229192

193+
194+
/**
195+
* Getter for the 'display' property of the current `IgxActionStrip`
196+
*/
197+
@HostBinding('style.display')
198+
private get display(): string {
199+
return this._hidden ? 'none' : 'flex';
200+
}
201+
202+
/**
203+
* Host `attr.class` binding.
204+
*/
205+
@HostBinding('class')
206+
private get hostClasses(): string {
207+
let hostClass = this.getComponentDensityClass('igx-action-strip');
208+
if (hostClass !== 'igx-action-strip') {
209+
// action strip requires the base class to be always present:
210+
hostClass = `igx-action-strip ${hostClass}`;
211+
}
212+
return hostClass;
213+
}
214+
230215
/**
231216
* @hidden
232217
* @internal

projects/igniteui-angular/src/lib/chips/chip.component.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,6 @@ export class IgxChipComponent extends DisplayDensityBase {
219219
@Input()
220220
public selectIcon: TemplateRef<any>;
221221

222-
/**
223-
* @hidden
224-
* @internal
225-
*/
226-
@Input()
227-
public class = '';
228-
229222
/**
230223
* An @Input property that defines if the `IgxChipComponent` is disabled. When disabled it restricts user interactions
231224
* like focusing on click or tab, selection on click or Space, dragging.
@@ -464,26 +457,6 @@ export class IgxChipComponent extends DisplayDensityBase {
464457
@Output()
465458
public dragDrop = new EventEmitter<IChipEnterDragAreaEventArgs>();
466459

467-
/**
468-
* @hidden
469-
* @internal
470-
*/
471-
@HostBinding('attr.class')
472-
public get hostClass(): string {
473-
const classes = [this.getComponentDensityClass('igx-chip')];
474-
475-
// Add the base class first for each density
476-
if (!classes.includes('igx-chip')) {
477-
classes.unshift('igx-chip');
478-
}
479-
480-
classes.push(this.disabled ? 'igx-chip--disabled' : '');
481-
482-
// The custom classes should be at the end.
483-
classes.push(this.class);
484-
return classes.join(' ').toString().trim();
485-
}
486-
487460
/**
488461
* Property that contains a reference to the `IgxDragDirective` the `IgxChipComponent` uses for dragging behavior.
489462
*
@@ -574,6 +547,20 @@ export class IgxChipComponent extends DisplayDensityBase {
574547
super(_displayDensityOptions);
575548
}
576549

550+
@HostBinding('class')
551+
private get hostClass(): string {
552+
const classes = [this.getComponentDensityClass('igx-chip')];
553+
554+
// Add the base class first for each density
555+
if (!classes.includes('igx-chip')) {
556+
classes.unshift('igx-chip');
557+
}
558+
559+
classes.push(this.disabled ? 'igx-chip--disabled' : '');
560+
561+
return classes.join(' ').toString().trim();
562+
}
563+
577564
/**
578565
* @hidden
579566
* @internal

projects/igniteui-angular/src/lib/chips/chip.spec.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, ViewChild, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
2-
import { TestBed, waitForAsync } from '@angular/core/testing';
2+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
33
import { FormsModule } from '@angular/forms';
44
import { By } from '@angular/platform-browser';
55
import { IgxIconModule } from '../icon/public_api';
@@ -17,7 +17,7 @@ import { ControlsFunction } from '../test-utils/controls-functions.spec';
1717
@Component({
1818
template: `
1919
<igx-chips-area #chipsArea>
20-
<igx-chip #chipElem *ngFor="let chip of chipList"
20+
<igx-chip #chipElem *ngFor="let chip of chipList" class="custom"
2121
[id]="chip.id" [draggable]="chip.draggable"
2222
[removable]="chip.removable" [selectable]="chip.selectable"
2323
[displayDensity]="chip.density" (remove)="chipRemoved($event)">
@@ -93,14 +93,15 @@ class TestChipsLabelAndSuffixComponent {
9393

9494

9595
describe('IgxChip', () => {
96-
const CHIP_TEXT_CLASS = '.igx-chip__text';
97-
const CHIP_CLASS = '.igx-chip';
98-
const CHIP_COMPACT_CLASS = '.igx-chip--compact';
99-
const CHIP_COSY_CLASS = '.igx-chip--cosy';
100-
const CHIP_ITEM_CLASS = '.igx-chip__item';
96+
const CHIP_TEXT_CLASS = 'igx-chip__text';
97+
const CHIP_CLASS = 'igx-chip';
98+
const CHIP_DISABLED_CLASS = 'igx-chip--disabled';
99+
const CHIP_COMPACT_CLASS = 'igx-chip--compact';
100+
const CHIP_COSY_CLASS = 'igx-chip--cosy';
101+
const CHIP_ITEM_CLASS = 'igx-chip__item';
101102
const CHIP_GHOST_COMP_CLASS = 'igx-chip__ghost--compact';
102103

103-
let fix;
104+
let fix: ComponentFixture<TestChipComponent | TestChipsLabelAndSuffixComponent>;
104105
let chipArea;
105106

106107
configureTestSuite();
@@ -149,12 +150,12 @@ describe('IgxChip', () => {
149150

150151
it('should set text in chips correctly', () => {
151152
const chipElements = chipArea[0].queryAll(By.directive(IgxChipComponent));
152-
const firstChipTextElement = chipElements[0].queryAllNodes(By.css(CHIP_TEXT_CLASS));
153+
const firstChipTextElement = chipElements[0].queryAllNodes(By.css(`.${CHIP_TEXT_CLASS}`));
153154
const firstChipText = firstChipTextElement[0].nativeNode.innerHTML;
154155

155156
expect(firstChipText).toContain('Country');
156157

157-
const secondChipTextElement = chipElements[1].queryAllNodes(By.css(CHIP_TEXT_CLASS));
158+
const secondChipTextElement = chipElements[1].queryAllNodes(By.css(`.${CHIP_TEXT_CLASS}`));
158159
const secondChipText = secondChipTextElement[0].nativeNode.innerHTML;
159160

160161
expect(secondChipText).toContain('City');
@@ -177,11 +178,21 @@ describe('IgxChip', () => {
177178
expect(secondComponent.componentInstance.displayDensity).toEqual(DisplayDensity.comfortable);
178179

179180
// Assert default css class is applied
180-
const comfortableComponents = fix.debugElement.queryAll(By.css(CHIP_CLASS));
181+
const comfortableComponents = fix.debugElement.queryAll(By.css(`.${CHIP_CLASS}`));
181182

182183
expect(comfortableComponents.length).toEqual(9);
183184
expect(comfortableComponents[0].nativeElement).toBe(firstComponent.nativeElement);
184185
expect(comfortableComponents[1].nativeElement).toBe(secondComponent.nativeElement);
186+
187+
expect(comfortableComponents[0].nativeElement.classList).toEqual(
188+
jasmine.arrayWithExactContents(['custom', CHIP_CLASS])
189+
);
190+
191+
firstComponent.componentInstance.disabled = true;
192+
fix.detectChanges();
193+
expect(comfortableComponents[0].nativeElement.classList).toEqual(
194+
jasmine.arrayWithExactContents(['custom', CHIP_CLASS, CHIP_DISABLED_CLASS])
195+
);
185196
});
186197

187198
it('should make chip compact when density is set to compact', () => {
@@ -191,10 +202,14 @@ describe('IgxChip', () => {
191202
expect(thirdComponent.componentInstance.displayDensity).toEqual(DisplayDensity.compact);
192203

193204
// Assert compact css class is added
194-
const compactComponents = fix.debugElement.queryAll(By.css(CHIP_COMPACT_CLASS));
205+
const compactComponents = fix.debugElement.queryAll(By.css(`.${CHIP_COMPACT_CLASS}`));
195206

196207
expect(compactComponents.length).toEqual(1);
197208
expect(compactComponents[0].nativeElement).toBe(thirdComponent.nativeElement);
209+
210+
expect(compactComponents[0].nativeElement.classList).toEqual(
211+
jasmine.arrayWithExactContents(['custom', CHIP_CLASS, CHIP_COMPACT_CLASS])
212+
);
198213
});
199214

200215
it('should make chip cosy when density is set to cosy', () => {
@@ -204,18 +219,22 @@ describe('IgxChip', () => {
204219
expect(fourthComponent.componentInstance.displayDensity).toEqual(DisplayDensity.cosy);
205220

206221
// Assert cosy css class is added
207-
const cosyComponents = fix.debugElement.queryAll(By.css(CHIP_COSY_CLASS));
222+
const cosyComponents = fix.debugElement.queryAll(By.css(`.${CHIP_COSY_CLASS}`));
208223

209224
expect(cosyComponents.length).toEqual(1);
210225
expect(cosyComponents[0].nativeElement).toBe(fourthComponent.nativeElement);
226+
227+
expect(cosyComponents[0].nativeElement.classList).toEqual(
228+
jasmine.arrayWithExactContents(['custom', CHIP_CLASS, CHIP_COSY_CLASS])
229+
);
211230
});
212231

213232
it('should set correctly color of chip when color is set through code', () => {
214233
const chipColor = 'rgb(255, 0, 0)';
215234

216235
const components = fix.debugElement.queryAll(By.directive(IgxChipComponent));
217236
const firstComponent = components[0];
218-
const chipAreaElem = firstComponent.queryAll(By.css(CHIP_ITEM_CLASS))[0];
237+
const chipAreaElem = firstComponent.queryAll(By.css(`.${CHIP_ITEM_CLASS}`))[0];
219238

220239
firstComponent.componentInstance.color = chipColor;
221240

projects/igniteui-angular/src/lib/chips/chips-area.component.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,6 @@ export interface IChipsAreaSelectEventArgs extends IBaseChipsAreaEventArgs {
6666
})
6767
export class IgxChipsAreaComponent implements DoCheck, AfterViewInit, OnDestroy {
6868

69-
/**
70-
* @hidden
71-
* @internal
72-
*/
73-
@Input()
74-
public class = '';
75-
76-
/**
77-
* @hidden
78-
* @internal
79-
*/
80-
@HostBinding('attr.class')
81-
public get hostClass() {
82-
const classes = ['igx-chip-area'];
83-
classes.push(this.class);
84-
85-
return classes.join(' ');
86-
}
87-
8869
/**
8970
* Returns the `role` attribute of the chips area.
9071
*
@@ -204,6 +185,9 @@ export class IgxChipsAreaComponent implements DoCheck, AfterViewInit, OnDestroy
204185

205186
protected destroy$ = new Subject<boolean>();
206187

188+
@HostBinding('class')
189+
private hostClass = 'igx-chip-area';
190+
207191
private modifiedChipsArray: IgxChipComponent[];
208192
private _differ: IterableDiffer<IgxChipComponent> | null = null;
209193

projects/igniteui-angular/src/lib/chips/chips-area.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ describe('IgxChipsArea ', () => {
102102
const CHIP_REMOVE_BUTTON = 'igx-chip__remove';
103103
const CHIP_SELECT_ICON = 'igx-chip__select';
104104
const CHIP_SELECT_ICON_HIDDEN = 'igx-chip__select--hidden';
105-
const TEST_CHIP_AREA_CLASS = 'igx-chip-area customClass';
105+
const CHIP_AREA_CLASS = 'igx-chip-area';
106106

107107
let fix;
108108
let chipArea: IgxChipsAreaComponent;
@@ -127,7 +127,7 @@ describe('IgxChipsArea ', () => {
127127
});
128128

129129
it('should add chips when adding data items ', () => {
130-
expect(chipAreaElement.nativeElement.className).toEqual(TEST_CHIP_AREA_CLASS);
130+
expect(chipAreaElement.nativeElement.classList).toEqual(jasmine.arrayWithExactContents(['customClass', CHIP_AREA_CLASS]));
131131
expect(chipAreaElement.nativeElement.children.length).toEqual(2);
132132

133133
fix.componentInstance.chipList.push({ id: 'Town', text: 'Town', removable: true, selectable: true, draggable: true });

projects/igniteui-angular/src/lib/grids/column-actions/column-actions.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class IgxColumnActionsComponent implements DoCheck {
6262
* this.columnHidingUI.cssClass = 'column-chooser';
6363
* ```
6464
*/
65-
@HostBinding('attr.class')
65+
@HostBinding('class')
6666
public cssClass = 'igx-column-actions';
6767
/**
6868
* Gets/sets the max height of the columns area.

0 commit comments

Comments
 (0)