Skip to content

Commit c5a32c2

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 ddeeb18 commit c5a32c2

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
@@ -177,9 +177,12 @@ export class MatBadge extends _MatBadgeMixinBase implements OnDestroy, CanDisabl
177177
const rootNode = this._renderer || this._document;
178178
const badgeElement = rootNode.createElement('span');
179179
const activeClass = 'mat-badge-active';
180+
const contentClass = 'mat-badge-content';
180181

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

185188
if (this._animationMode === 'NoopAnimations') {
@@ -232,4 +235,18 @@ export class MatBadge extends _MatBadgeMixinBase implements OnDestroy, CanDisabl
232235
}
233236
}
234237

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

0 commit comments

Comments
 (0)