Skip to content

Commit 815a729

Browse files
crisbetojosephperrott
authored andcommitted
fix(badge): duplicate leftover badge after server-side render (#15417)
Fixes badges being left over if the current page was rendered via Angular Universal. This can cause issues, because the ids on the badges can clash.
1 parent aaec2d5 commit 815a729

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

src/lib/badge/badge.spec.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ describe('MatBadge', () => {
1111
let badgeDebugElement: DebugElement;
1212

1313
beforeEach(fakeAsync(() => {
14-
TestBed.configureTestingModule({
15-
imports: [MatBadgeModule],
16-
declarations: [BadgeTestApp],
17-
}).compileComponents();
14+
TestBed
15+
.configureTestingModule({
16+
imports: [MatBadgeModule],
17+
declarations: [BadgeTestApp, PreExistingBadge, NestedBadge],
18+
})
19+
.compileComponents();
1820

1921
fixture = TestBed.createComponent(BadgeTestApp);
2022
testComponent = fixture.debugElement.componentInstance;
@@ -184,6 +186,20 @@ describe('MatBadge', () => {
184186
expect(badgeContent.hasAttribute('aria-label')).toBe(false);
185187
});
186188

189+
it('should clear any pre-existing badges', () => {
190+
const preExistingFixture = TestBed.createComponent(PreExistingBadge);
191+
preExistingFixture.detectChanges();
192+
193+
expect(preExistingFixture.nativeElement.querySelectorAll('.mat-badge-content').length).toBe(1);
194+
});
195+
196+
it('should not clear badge content from child elements', () => {
197+
const preExistingFixture = TestBed.createComponent(NestedBadge);
198+
preExistingFixture.detectChanges();
199+
200+
expect(preExistingFixture.nativeElement.querySelectorAll('.mat-badge-content').length).toBe(2);
201+
});
202+
187203
});
188204

189205
/** Test component that contains a MatBadge. */
@@ -214,3 +230,27 @@ class BadgeTestApp {
214230
badgeDescription: string;
215231
badgeDisabled = false;
216232
}
233+
234+
235+
@Component({
236+
template: `
237+
<span matBadge="Hello">
238+
home
239+
<div class="mat-badge-content">Pre-existing badge</div>
240+
</span>
241+
`
242+
})
243+
class PreExistingBadge {
244+
}
245+
246+
247+
@Component({
248+
template: `
249+
<span matBadge="Hello">
250+
home
251+
<span matBadge="Hi">Something</span>
252+
</span>
253+
`
254+
})
255+
class NestedBadge {
256+
}

src/lib/badge/badge.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,12 @@ export class MatBadge extends _MatBadgeMixinBase implements OnDestroy, CanDisabl
175175
const rootNode = this._renderer || this._document;
176176
const badgeElement = rootNode.createElement('span');
177177
const activeClass = 'mat-badge-active';
178+
const contentClass = 'mat-badge-content';
178179

180+
// Clear any existing badges which may have persisted from a server-side render.
181+
this._clearExistingBadges(contentClass);
179182
badgeElement.setAttribute('id', `mat-badge-content-${this._id}`);
180-
badgeElement.classList.add('mat-badge-content');
183+
badgeElement.classList.add(contentClass);
181184
badgeElement.textContent = this.content;
182185

183186
if (this.description) {
@@ -226,4 +229,18 @@ export class MatBadge extends _MatBadgeMixinBase implements OnDestroy, CanDisabl
226229
}
227230
}
228231

232+
/** Clears any existing badges that might be left over from server-side rendering. */
233+
private _clearExistingBadges(cssClass: string) {
234+
const element = this._elementRef.nativeElement;
235+
let childCount = element.children.length;
236+
237+
// Use a reverse while, because we'll be removing elements from the list as we're iterating.
238+
while (childCount--) {
239+
const currentChild = element.children[childCount];
240+
241+
if (currentChild.classList.contains(cssClass)) {
242+
element.removeChild(currentChild);
243+
}
244+
}
245+
}
229246
}

0 commit comments

Comments
 (0)