Skip to main content
Jet follows Angular best practices for component and service architecture. This guide shows you how to add new elements to your application.

Adding Components

1

Generate the component

Use Angular CLI to generate a new component:
ng g c components/<component-name>
For example:
ng g c components/user-card
2

Configure the component decorator

Update the @Component() decorator:
@Component({
  selector: 'jet-user-card',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [TranslocoModule],
  templateUrl: './user-card.component.html',
  styleUrl: './user-card.component.scss'
})
3

Add logging service

In the component class, inject and use the LoggerService:
export class UserCardComponent {
  readonly #loggerService = inject(LoggerService);

  constructor() {
    this.#loggerService.logComponentInitialization('UserCardComponent');
  }
}
4

Add translations

Add the component selector as a key in all translation files:In public/i18n/en.json:
{
  "jet-user-card": {
    "title": "User Card",
    "description": "User information"
  }
}
In public/i18n/ar.json and other language files as well.
5

Set up template with Transloco

Wrap your template content in a Transloco container:
<ng-container *transloco="let t">
  <h2>{{ t('jet-user-card.title') }}</h2>
  <p>{{ t('jet-user-card.description') }}</p>
</ng-container>
6

Update the spec file

Update the generated test file to match your component’s dependencies and structure.

Adding Page Components

Page components are routable components that represent full pages in your application.
1

Generate the page component

ng g c components/<page-name>-page
For example:
ng g c components/dashboard-page
2

Configure the component

Set up the component decorator with PageComponent:
@Component({
  selector: 'jet-dashboard-page',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [TranslocoModule, PageComponent],
  templateUrl: './dashboard-page.component.html',
  styleUrl: './dashboard-page.component.scss'
})
3

Add logging

export class DashboardPageComponent {
  readonly #loggerService = inject(LoggerService);

  constructor() {
    this.#loggerService.logComponentInitialization('DashboardPageComponent');
  }
}
4

Set up the page template

Use the PageComponent wrapper for SEO and layout:
<ng-container *transloco="let t">
  <jet-page
    [seoDescription]="t('jet-dashboard-page.seo.description')"
    [seoKeywords]="t('jet-dashboard-page.seo.keywords')"
    [seoTitle]="t('jet-dashboard-page.seo.title')"
    [toolbarTitle]="t('jet-dashboard-page.toolbar-title')"
  >
    <!-- Your page content here -->
  </jet-page>
</ng-container>
5

Add translations with SEO fields

In public/i18n/en.json:
{
  "jet-dashboard-page": {
    "seo": {
      "title": "Dashboard - Jet",
      "description": "View your dashboard analytics and insights",
      "keywords": "dashboard, analytics, metrics"
    },
    "toolbar-title": "Dashboard"
  }
}
6

Add route

Add the route in src/app/app.routes.ts:
{
  path: 'dashboard',
  loadComponent: () => 
    import('./components/dashboard-page/dashboard-page.component')
      .then((m) => m.DashboardPageComponent),
}
7

Update sitemap

Add the new page to public/sitemaps/sitemap-main.xml:
<url>
  <loc>https://your-domain.com/dashboard</loc>
  <changefreq>weekly</changefreq>
  <priority>0.8</priority>
</url>
8

Add navigation (optional)

If you want the page in the navigation menu, add it to src/app/constants/navigation-menu-items.constant.ts:
export const NAVIGATION_MENU_ITEMS: NavigationMenuItem[] = [
  // ... existing items
  {
    icon: 'dashboard',
    labelKey: marker('constants.dashboard'),
    routerLink: '/dashboard',
  },
];
And add the translation in public/i18n/en.json:
{
  "constants": {
    "dashboard": "Dashboard"
  }
}

Adding Services

1

Generate the service

ng g s services/<service-name>/<service-name>
For example:
ng g s services/notification/notification
2

Set up the service

import { inject, Injectable } from '@angular/core';
import { LoggerService } from '@jet/services/logger/logger.service';

@Injectable({
  providedIn: 'root'
})
export class NotificationService {
  readonly #loggerService = inject(LoggerService);

  constructor() {
    this.#loggerService.logServiceInitialization('NotificationService');
  }

  // Your service methods here
}
3

Create a mock

Create notification.service.mock.ts for testing:
import { NotificationService } from './notification.service';

export class NotificationServiceMock 
  implements Partial<NotificationService> {
  // Mock implementations
}
4

Update the spec file

Update notification.service.spec.ts to test your service properly.

Component Architecture Patterns

Using Signals

Jet uses Angular Signals for reactive state management:
export class UserListComponent {
  readonly users = signal<User[]>([]);
  readonly selectedUser = signal<User | null>(null);
  
  readonly userCount = computed(() => this.users().length);
  
  selectUser(user: User) {
    this.selectedUser.set(user);
  }
}

OnPush Change Detection

All components use ChangeDetectionStrategy.OnPush for optimal performance:
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  // ...
})
This means:
  • Change detection only runs when inputs change
  • Use signals or explicitly mark for check when needed
  • Better performance for large applications

Standalone Components

All components are standalone (no NgModules):
@Component({
  standalone: true,
  imports: [
    CommonModule,
    TranslocoModule,
    MatButtonModule,
    MatCardModule,
  ],
  // ...
})

Path Aliases

Jet uses the @jet path alias for imports:
import { LoggerService } from '@jet/services/logger/logger.service';
import { User } from '@jet/interfaces/user.interface';
import { AppRole } from '@jet/enums/app-role.enum';
This is configured in tsconfig.json:
{
  "compilerOptions": {
    "paths": {
      "@jet/*": ["src/app/*"]
    }
  }
}

Best Practices

Use TypeScript private fields for internal implementation:
readonly #loggerService = inject(LoggerService);
readonly #destroyRef = inject(DestroyRef);
Use the functional inject() API instead of constructor injection:
readonly #userService = inject(UserService);
readonly #router = inject(Router);
Mark injected services as readonly:
readonly #alertService = inject(AlertService);
Always log initialization in constructors:
constructor() {
  this.#loggerService.logComponentInitialization('MyComponent');
}
  • One component = one responsibility
  • Extract reusable logic into services
  • Use composition over inheritance

Build docs developers (and LLMs) love