Skip to content

Commit 29cd138

Browse files
committed
feat: adds toolbar to the demo app
- Added new demo tasks in VSCode for serving and affected tasks. - Updated global styles in styles.scss for consistent layout. - Refactored app.component to remove NxWelcomeComponent and implement a new layout with NavBarComponent. - Improved app routing to include feature flags and home components. - Removed deprecated NxWelcomeComponent. - Updated feature flags service to use BehaviorSubject for state management. - Enhanced dev-toolbar styles and component structure for better maintainability. - Updated package-lock.json with new dependencies and versions.
1 parent 7926089 commit 29cd138

24 files changed

+1090
-1039
lines changed

.vscode/tasks.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,33 @@
117117
"showReuseMessage": false
118118
},
119119
"problemMatcher": []
120+
},
121+
{
122+
"label": "▶️ [Demo] Serve",
123+
"type": "shell",
124+
"command": "nx",
125+
"args": ["serve", "ngx-dev-toolbar-demo"],
126+
"problemMatcher": ["$tsc-watch"],
127+
"presentation": {
128+
"reveal": "always",
129+
"panel": "dedicated",
130+
"group": "demo-tasks",
131+
"close": false
132+
},
133+
"isBackground": true
134+
},
135+
{
136+
"label": "🔄 Affected Tasks",
137+
"type": "shell",
138+
"command": "nx",
139+
"args": ["affected", "-t", "lint,test,build,e2e"],
140+
141+
"presentation": {
142+
"reveal": "always",
143+
"panel": "dedicated",
144+
"group": "affected-tasks",
145+
"close": false
146+
}
120147
}
121148
]
122149
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
<app-nx-welcome></app-nx-welcome>
1+
22
<router-outlet></router-outlet>
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
11
import { TestBed } from '@angular/core/testing';
22
import { RouterModule } from '@angular/router';
33
import { AppComponent } from './app.component';
4-
import { NxWelcomeComponent } from './nx-welcome.component';
4+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
55

66
describe('AppComponent', () => {
77
beforeEach(async () => {
88
await TestBed.configureTestingModule({
9-
imports: [AppComponent, NxWelcomeComponent, RouterModule.forRoot([])],
9+
imports: [AppComponent, NoopAnimationsModule, RouterModule.forRoot([])],
1010
}).compileComponents();
1111
});
1212

13-
it('should render title', () => {
13+
it('should render app component', () => {
1414
const fixture = TestBed.createComponent(AppComponent);
15-
fixture.detectChanges();
16-
const compiled = fixture.nativeElement as HTMLElement;
17-
expect(compiled.querySelector('h1')?.textContent).toContain(
18-
'Welcome ngx-dev-toolbar-demo'
19-
);
20-
});
15+
const app = fixture.debugElement.componentInstance;
16+
expect(app).toBeTruthy();
17+
})
2118

22-
it(`should have as title 'ngx-dev-toolbar-demo'`, () => {
23-
const fixture = TestBed.createComponent(AppComponent);
24-
const app = fixture.componentInstance;
25-
expect(app.title).toEqual('ngx-dev-toolbar-demo');
26-
});
2719
});
Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,94 @@
1-
import { Component } from '@angular/core';
2-
import { RouterModule } from '@angular/router';
3-
import { NxWelcomeComponent } from './nx-welcome.component';
1+
import { CommonModule } from '@angular/common';
2+
import { Component, inject, OnInit } from '@angular/core';
3+
import { toSignal } from '@angular/core/rxjs-interop';
4+
import { RouterOutlet } from '@angular/router';
5+
import {
6+
DevToolbarComponent,
7+
DevToolbarFeatureFlagsService,
8+
Flag,
9+
} from 'ngx-dev-toolbar';
10+
import { firstValueFrom, map } from 'rxjs';
11+
import { NavBarComponent } from './components/nav-bar/nav-bar.component';
12+
import { FeatureFlagsService } from './services/feature-flags.service';
413

514
@Component({
6-
imports: [NxWelcomeComponent, RouterModule],
715
selector: 'app-root',
8-
templateUrl: './app.component.html',
9-
styleUrl: './app.component.scss',
16+
standalone: true,
17+
imports: [CommonModule, RouterOutlet, NavBarComponent, DevToolbarComponent],
18+
template: `
19+
@if (useNewLayout() ) {
20+
<!-- New Modern Layout -->
21+
<div class="modern-layout">
22+
<app-nav-bar />
23+
<main class="modern-content">
24+
<router-outlet />
25+
</main>
26+
</div>
27+
} @else {
28+
<!-- Original Layout -->
29+
<div class="original-layout">
30+
<app-nav-bar />
31+
<main class="content">
32+
<router-outlet />
33+
</main>
34+
</div>
35+
}
36+
<ndt-toolbar></ndt-toolbar>
37+
`,
38+
styles: [
39+
`
40+
.modern-layout {
41+
min-height: 100vh;
42+
background: #f8f9fa;
43+
44+
.modern-content {
45+
max-width: 1200px;
46+
margin: 0 auto;
47+
padding: 2rem;
48+
background: white;
49+
border-radius: 8px;
50+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
51+
margin-top: 2rem;
52+
}
53+
}
54+
55+
.original-layout {
56+
.content {
57+
padding: 1rem;
58+
}
59+
}
60+
`,
61+
],
1062
})
11-
export class AppComponent {
12-
title = 'ngx-dev-toolbar-demo';
63+
export class AppComponent implements OnInit {
64+
featureFlagsService = inject(FeatureFlagsService);
65+
devToolbarFeatureFlagsService = inject(DevToolbarFeatureFlagsService);
66+
67+
useNewLayout = toSignal(
68+
this.featureFlagsService.select('newDemoApplicationLayout')
69+
);
70+
71+
ngOnInit(): void {
72+
this.loadFlags();
73+
}
74+
75+
private async loadFlags(): Promise<void> {
76+
// Gets the flags from the application and sets them in the dev toolbar
77+
const flags: Flag[] = await firstValueFrom(
78+
this.featureFlagsService.flags$.pipe(
79+
map((flags) =>
80+
flags.map(
81+
(flag) =>
82+
({
83+
id: flag.name,
84+
name: flag.name,
85+
description: flag.description,
86+
isEnabled: flag.enabled,
87+
} as Flag)
88+
)
89+
)
90+
)
91+
);
92+
this.devToolbarFeatureFlagsService.set(flags);
93+
}
1394
}
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
1+
import { ApplicationConfig } from '@angular/core';
2+
import { provideAnimations } from '@angular/platform-browser/animations';
23
import { provideRouter } from '@angular/router';
3-
import { appRoutes } from './app.routes';
4+
import { routes } from './app.routes';
45

56
export const appConfig: ApplicationConfig = {
6-
providers: [
7-
provideZoneChangeDetection({ eventCoalescing: true }),
8-
provideRouter(appRoutes),
9-
],
7+
providers: [provideRouter(routes), provideAnimations()],
108
};
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1-
import { Route } from '@angular/router';
1+
import { Routes } from '@angular/router';
22

3-
export const appRoutes: Route[] = [];
3+
export const routes: Routes = [
4+
{
5+
path: '',
6+
loadComponent: () =>
7+
import('./features/home/home.component').then((m) => m.HomeComponent),
8+
},
9+
{
10+
path: 'feature-flags',
11+
loadComponent: () =>
12+
import('./features/feature-flags/feature-flags.component').then(
13+
(m) => m.FeatureFlagsComponent
14+
),
15+
},
16+
];
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { CommonModule } from '@angular/common';
2+
import { Component } from '@angular/core';
3+
import { RouterLink, RouterLinkActive } from '@angular/router';
4+
5+
@Component({
6+
selector: 'app-nav-bar',
7+
standalone: true,
8+
imports: [CommonModule, RouterLink, RouterLinkActive],
9+
template: `
10+
<nav class="nav-bar">
11+
<div class="nav-content">
12+
<a routerLink="/" class="nav-brand">Demo App</a>
13+
<div class="nav-links">
14+
<a
15+
routerLink="/"
16+
routerLinkActive="active"
17+
[routerLinkActiveOptions]="{ exact: true }"
18+
>Home</a
19+
>
20+
<a routerLink="/feature-flags" routerLinkActive="active"
21+
>Feature Flags</a
22+
>
23+
</div>
24+
</div>
25+
</nav>
26+
`,
27+
styles: [
28+
`
29+
.nav-bar {
30+
background: #ffffff;
31+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
32+
}
33+
34+
.nav-content {
35+
max-width: 1200px;
36+
margin: 0 auto;
37+
padding: 1rem 2rem;
38+
display: flex;
39+
justify-content: space-between;
40+
align-items: center;
41+
}
42+
43+
.nav-brand {
44+
font-size: 1.25rem;
45+
font-weight: bold;
46+
text-decoration: none;
47+
color: #333;
48+
}
49+
50+
.nav-links {
51+
display: flex;
52+
gap: 1.5rem;
53+
54+
a {
55+
text-decoration: none;
56+
color: #666;
57+
transition: color 0.2s;
58+
59+
&:hover,
60+
&.active {
61+
color: #2196f3;
62+
}
63+
}
64+
}
65+
`,
66+
],
67+
})
68+
export class NavBarComponent {}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { CommonModule } from '@angular/common';
2+
import { Component, inject } from '@angular/core';
3+
import { FeatureFlagsService } from '../../services/feature-flags.service';
4+
5+
@Component({
6+
selector: 'app-feature-flags',
7+
standalone: true,
8+
imports: [CommonModule],
9+
template: `
10+
<div class="feature-flags-container">
11+
<h1>Feature Flags</h1>
12+
<div class="flags-list">
13+
@for (flag of flags$ | async; track flag.name) {
14+
<div class="flag-item">
15+
<div class="flag-info">
16+
<h3>{{ flag.name }}</h3>
17+
<p>{{ flag.description }}</p>
18+
</div>
19+
<label class="switch">
20+
<input
21+
type="checkbox"
22+
[checked]="flag.enabled"
23+
(change)="toggleFlag(flag.name)"
24+
/>
25+
<span class="slider"></span>
26+
</label>
27+
</div>
28+
}
29+
</div>
30+
</div>
31+
`,
32+
styles: [
33+
`
34+
.feature-flags-container {
35+
padding: 2rem;
36+
max-width: 800px;
37+
margin: 0 auto;
38+
}
39+
40+
.flags-list {
41+
display: flex;
42+
flex-direction: column;
43+
gap: 1rem;
44+
}
45+
46+
.flag-item {
47+
display: flex;
48+
justify-content: space-between;
49+
align-items: center;
50+
padding: 1rem;
51+
background: #f5f5f5;
52+
border-radius: 8px;
53+
}
54+
55+
.flag-info h3 {
56+
margin: 0;
57+
font-size: 1.1rem;
58+
}
59+
60+
.flag-info p {
61+
margin: 0.5rem 0 0;
62+
color: #666;
63+
}
64+
65+
.switch {
66+
position: relative;
67+
display: inline-block;
68+
width: 60px;
69+
height: 34px;
70+
}
71+
72+
.switch input {
73+
opacity: 0;
74+
width: 0;
75+
height: 0;
76+
}
77+
78+
.slider {
79+
position: absolute;
80+
cursor: pointer;
81+
top: 0;
82+
left: 0;
83+
right: 0;
84+
bottom: 0;
85+
background-color: #ccc;
86+
transition: 0.4s;
87+
border-radius: 34px;
88+
}
89+
90+
.slider:before {
91+
position: absolute;
92+
content: '';
93+
height: 26px;
94+
width: 26px;
95+
left: 4px;
96+
bottom: 4px;
97+
background-color: white;
98+
transition: 0.4s;
99+
border-radius: 50%;
100+
}
101+
102+
input:checked + .slider {
103+
background-color: #2196f3;
104+
}
105+
106+
input:checked + .slider:before {
107+
transform: translateX(26px);
108+
}
109+
`,
110+
],
111+
})
112+
export class FeatureFlagsComponent {
113+
private readonly featureFlagsService = inject(FeatureFlagsService);
114+
flags$ = this.featureFlagsService.flags$;
115+
116+
toggleFlag(flagName: string): void {
117+
this.featureFlagsService.toggleFlag(flagName);
118+
}
119+
}

0 commit comments

Comments
 (0)