Skip to content

Commit dcd242a

Browse files
feat: add useRouter property to sidenav (#2812)
Co-authored-by: Akshat Patel <38994122+Akshat55@users.noreply.github.com>
1 parent 69eac85 commit dcd242a

File tree

6 files changed

+207
-69
lines changed

6 files changed

+207
-69
lines changed

src/ui-shell/sidenav/side-nav.component.spec.ts

Lines changed: 112 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class FooComponent { }
2121
<cds-sidenav-menu title="Example Title"></cds-sidenav-menu>
2222
<cds-sidenav-item
2323
[route]="route"
24+
[useRouter]="useRouter"
2425
(navigation)="onNavigation($event)">
2526
</cds-sidenav-item>
2627
</cds-sidenav>
@@ -31,6 +32,7 @@ class SideNavTest {
3132
hidden = false;
3233
allowExpansion = false;
3334
statusPromise = null;
35+
useRouter = false;
3436
onNavigation(event) {
3537
this.statusPromise = event;
3638
}
@@ -70,54 +72,119 @@ describe("SideNav", () => {
7072
expect(fixture.componentInstance instanceof SideNav).toBe(true);
7173
});
7274

73-
it("should emit the navigation status promise when the link is activated and call onNavigation", async () => {
74-
fixture = TestBed.createComponent(SideNavTest);
75-
wrapper = fixture.componentInstance;
76-
spyOn(wrapper, "onNavigation").and.callThrough();
77-
fixture.detectChanges();
78-
element = fixture.debugElement.query(By.css(".cds--side-nav__link"));
79-
element.nativeElement.click();
80-
fixture.detectChanges();
81-
expect(wrapper.onNavigation).toHaveBeenCalled();
82-
const status = await wrapper.statusPromise;
83-
expect(status).toBe(true);
84-
});
75+
describe("when useRouter is false", () => {
76+
let fixture;
8577

86-
it("should expand sidenav-menu on click", () => {
87-
fixture = TestBed.createComponent(SideNavTest);
88-
wrapper = fixture.componentInstance;
89-
fixture.detectChanges();
90-
element = fixture.debugElement.query(By.css(".cds--side-nav__submenu"));
91-
element.nativeElement.click();
92-
fixture.detectChanges();
93-
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("true");
94-
expect(element.componentInstance.expanded).toBe(true);
95-
element.nativeElement.click();
96-
fixture.detectChanges();
97-
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("false");
98-
expect(element.componentInstance.expanded).toBe(false);
99-
});
78+
beforeEach(() => {
79+
fixture = TestBed.createComponent(SideNavTest);
80+
fixture.componentInstance.useRouter = false;
81+
fixture.detectChanges();
82+
});
83+
84+
it("should emit the navigation status promise when the link is activated and call onNavigation", async () => {
85+
wrapper = fixture.componentInstance;
86+
spyOn(wrapper, "onNavigation").and.callThrough();
87+
fixture.detectChanges();
88+
element = fixture.debugElement.query(By.css(".cds--side-nav__link"));
89+
element.nativeElement.click();
90+
fixture.detectChanges();
91+
expect(wrapper.onNavigation).toHaveBeenCalled();
92+
const status = await wrapper.statusPromise;
93+
expect(status).toBe(true);
94+
});
10095

101-
it("should set the sidenav-menu title to Example Title", () => {
102-
fixture = TestBed.createComponent(SideNavTest);
103-
fixture.detectChanges();
104-
element = fixture.debugElement.query(By.css("cds-sidenav-menu"));
105-
expect(element.nativeElement.textContent).toEqual("Example Title");
96+
it("should expand sidenav-menu on click", () => {
97+
wrapper = fixture.componentInstance;
98+
fixture.detectChanges();
99+
element = fixture.debugElement.query(By.css(".cds--side-nav__submenu"));
100+
element.nativeElement.click();
101+
fixture.detectChanges();
102+
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("true");
103+
expect(element.componentInstance.expanded).toBe(true);
104+
element.nativeElement.click();
105+
fixture.detectChanges();
106+
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("false");
107+
expect(element.componentInstance.expanded).toBe(false);
108+
});
109+
110+
it("should set the sidenav-menu title to Example Title", () => {
111+
fixture.detectChanges();
112+
element = fixture.debugElement.query(By.css("cds-sidenav-menu"));
113+
expect(element.nativeElement.textContent).toEqual("Example Title");
114+
});
115+
116+
it("should toggle expanded on click", () => {
117+
wrapper = fixture.componentInstance;
118+
wrapper.allowExpansion = true;
119+
fixture.detectChanges();
120+
element = fixture.debugElement.query(By.css("cds-sidenav"));
121+
let sidenavButton = element.nativeElement.querySelector(
122+
".cds--side-nav__toggle",
123+
);
124+
element.componentInstance.expanded = false;
125+
sidenavButton.click();
126+
fixture.detectChanges();
127+
expect(element.componentInstance.expanded).toBe(true);
128+
sidenavButton.click();
129+
fixture.detectChanges();
130+
expect(element.componentInstance.expanded).toBe(false);
131+
});
106132
});
107133

108-
it("should toggle expanded on click", () => {
109-
fixture = TestBed.createComponent(SideNavTest);
110-
wrapper = fixture.componentInstance;
111-
wrapper.allowExpansion = true;
112-
fixture.detectChanges();
113-
element = fixture.debugElement.query(By.css("cds-sidenav"));
114-
let sidenavButton = element.nativeElement.querySelector(".cds--side-nav__toggle");
115-
element.componentInstance.expanded = false;
116-
sidenavButton.click();
117-
fixture.detectChanges();
118-
expect(element.componentInstance.expanded).toBe(true);
119-
sidenavButton.click();
120-
fixture.detectChanges();
121-
expect(element.componentInstance.expanded).toBe(false);
134+
describe("when useRouter is true", () => {
135+
let fixture;
136+
137+
beforeEach(() => {
138+
fixture = TestBed.createComponent(SideNavTest);
139+
fixture.componentInstance.useRouter = true;
140+
fixture.detectChanges();
141+
});
142+
143+
it("should not emit the navigation status promise when the link is activated and call onNavigation", async () => {
144+
wrapper = fixture.componentInstance;
145+
spyOn(wrapper, "onNavigation").and.callThrough();
146+
fixture.detectChanges();
147+
element = fixture.debugElement.query(By.css(".cds--side-nav__link"));
148+
element.nativeElement.click();
149+
fixture.detectChanges();
150+
expect(wrapper.onNavigation).not.toHaveBeenCalled();
151+
});
152+
153+
it("should expand sidenav-menu on click", () => {
154+
wrapper = fixture.componentInstance;
155+
fixture.detectChanges();
156+
element = fixture.debugElement.query(By.css(".cds--side-nav__submenu"));
157+
element.nativeElement.click();
158+
fixture.detectChanges();
159+
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("true");
160+
expect(element.componentInstance.expanded).toBe(true);
161+
element.nativeElement.click();
162+
fixture.detectChanges();
163+
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("false");
164+
expect(element.componentInstance.expanded).toBe(false);
165+
});
166+
167+
it("should set the sidenav-menu title to Example Title", () => {
168+
fixture.detectChanges();
169+
element = fixture.debugElement.query(By.css("cds-sidenav-menu"));
170+
expect(element.nativeElement.textContent).toEqual("Example Title");
171+
});
172+
173+
it("should toggle expanded on click", () => {
174+
wrapper = fixture.componentInstance;
175+
wrapper.allowExpansion = true;
176+
fixture.detectChanges();
177+
element = fixture.debugElement.query(By.css("cds-sidenav"));
178+
let sidenavButton = element.nativeElement.querySelector(
179+
".cds--side-nav__toggle",
180+
);
181+
element.componentInstance.expanded = false;
182+
sidenavButton.click();
183+
fixture.detectChanges();
184+
expect(element.componentInstance.expanded).toBe(true);
185+
sidenavButton.click();
186+
fixture.detectChanges();
187+
expect(element.componentInstance.expanded).toBe(false);
188+
});
122189
});
123190
});

src/ui-shell/sidenav/sidenav-item.component.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
Output,
66
EventEmitter,
77
OnChanges,
8-
HostBinding
8+
HostBinding,
9+
SimpleChanges
910
} from "@angular/core";
1011
import { DomSanitizer } from "@angular/platform-browser";
1112
import { Router } from "@angular/router";
@@ -16,22 +17,37 @@ import { Router } from "@angular/router";
1617
@Component({
1718
selector: "cds-sidenav-item, ibm-sidenav-item",
1819
template: `
19-
<a
20-
class="cds--side-nav__link"
21-
[ngClass]="{
20+
<a *ngIf="!useRouter; else sidenavItemRouterTpl"
21+
class="cds--side-nav__link"
22+
[ngClass]="{
2223
'cds--side-nav__item--active': active
2324
}"
24-
[href]="href"
25-
[attr.aria-current]="(active ? 'page' : null)"
26-
[attr.title]="title ? title : null"
27-
(click)="navigate($event)">
25+
[href]="href"
26+
[attr.aria-current]="(active ? 'page' : null)"
27+
[attr.title]="title ? title : null"
28+
(click)="navigate($event)">
29+
<ng-template [ngTemplateOutlet]="sidenavItemContentTpl"></ng-template>
30+
</a>
31+
32+
<ng-template #sidenavItemRouterTpl>
33+
<a
34+
[routerLink]="route"
35+
routerLinkActive="cds--side-nav__item--active"
36+
ariaCurrentWhenActive="page"
37+
[attr.title]="title ? title : null"
38+
class="cds--side-nav__link">
39+
<ng-template [ngTemplateOutlet]="sidenavItemContentTpl"></ng-template>
40+
</a>
41+
</ng-template>
42+
43+
<ng-template #sidenavItemContentTpl>
2844
<div *ngIf="!isSubMenu" class="cds--side-nav__icon">
2945
<ng-content select="svg, [icon]"></ng-content>
3046
</div>
3147
<span class="cds--side-nav__link-text">
3248
<ng-content></ng-content>
3349
</span>
34-
</a>
50+
</ng-template>
3551
`,
3652
styles: [`
3753
:host {
@@ -55,6 +71,11 @@ export class SideNavItem implements OnChanges {
5571
return this.domSanitizer.bypassSecurityTrustUrl(this._href) as string;
5672
}
5773

74+
/**
75+
* Use the routerLink attribute on <a> tag for navigation instead of using event handlers
76+
*/
77+
@Input() useRouter = true;
78+
5879
@HostBinding("class.cds--side-nav__item") get sideNav() {
5980
return !this.isSubMenu;
6081
}
@@ -104,13 +125,13 @@ export class SideNavItem implements OnChanges {
104125

105126
constructor(protected domSanitizer: DomSanitizer, @Optional() protected router: Router) {}
106127

107-
ngOnChanges(changes) {
128+
ngOnChanges(changes: SimpleChanges) {
108129
if (changes.active) {
109130
this.selected.emit(this.active);
110131
}
111132
}
112133

113-
navigate(event) {
134+
navigate(event: MouseEvent) {
114135
if (this.router && this.route) {
115136
event.preventDefault();
116137
const status = this.router.navigate(this.route, this.routeExtras);

src/ui-shell/sidenav/sidenav-menu.component.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { SideNavItemInterface } from "./sidenav-item.interface";
4848
[href]="menuItem.href"
4949
[route]="menuItem.route"
5050
[routeExtras]="menuItem.routeExtras"
51+
[useRouter]="useRouter"
5152
[isSubMenu]="true">
5253
{{ menuItem.content }}
5354
</cds-sidenav-item>
@@ -64,7 +65,12 @@ export class SideNavMenu implements AfterContentInit, OnDestroy {
6465
@HostBinding("attr.role") role = "listitem";
6566

6667
/**
67-
* Heading for the gorup
68+
* Use the routerLink attribute on <a> tag for navigation instead of using event handlers
69+
*/
70+
@Input() useRouter = false;
71+
72+
/**
73+
* Heading for the group
6874
*/
6975
@Input() title: string;
7076
/**

src/ui-shell/sidenav/sidenav.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ import { NavigationItem } from "../header/header-navigation-items.interface";
2525
[href]="navigationItem.href"
2626
[route]="navigationItem.route"
2727
[routeExtras]="navigationItem.routeExtras"
28+
[useRouter]="useRouter"
2829
[title]="navigationItem.title">
2930
{{ navigationItem.content }}
3031
</cds-sidenav-item>
3132
<cds-sidenav-menu
3233
*ngIf="navigationItem.type === 'menu'"
3334
[title]="navigationItem.title"
35+
[useRouter]="useRouter"
3436
[menuItems]="navigationItem.menuItems">
3537
</cds-sidenav-menu>
3638
</ng-container>
@@ -101,6 +103,11 @@ export class SideNav {
101103
*/
102104
@Input() navigationItems: NavigationItem[];
103105

106+
/**
107+
* Use the routerLink attribute on <a> tag for navigation instead of using event handlers
108+
*/
109+
@Input() useRouter = false;
110+
104111
constructor(public i18n: I18n) { }
105112

106113
toggle() {

src/ui-shell/sidenav/sidenav.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NgModule } from "@angular/core";
22
import { CommonModule } from "@angular/common";
3+
import { RouterModule } from "@angular/router";
34

45
import { I18nModule } from "carbon-components-angular/i18n";
56

@@ -21,7 +22,7 @@ export {
2122
SideNavItem,
2223
SideNavMenu
2324
],
24-
imports: [CommonModule, I18nModule],
25+
imports: [CommonModule, I18nModule, RouterModule],
2526
exports: [
2627
SideNav,
2728
SideNavItem,

0 commit comments

Comments
 (0)