Skill: basic-initial-core-project
Project Name: basic-initial-core-full Framework: Angular 20 + Ionic 8 + Capacitor 8 + TypeScript 5.9 Arquitetura: DDD + Clean Architecture + SOLID (Feature First)
🏗️ Estrutura do Projeto
src/app/
├── core/ # Core independente (NÃO importa Features)
│ ├── guards/ # Guards de rota (funções CanActivateFn)
│ ├── interceptors/ # Interceptors HTTP (8 ativos)
│ └── providers/ # Providers centrais
│
├── shared/ # Shared "burro" (NÃO importa Core nem Features)
│ ├── components/ # Componentes compartilhados
│ ├── constants/ # Constantes (routes, forms, address)
│ ├── enums/ # Enums por domínio
│ ├── interfaces/ # Interfaces por domínio (prefixo I)
│ ├── layouts/ # Layouts (BasePagesLayoutComponent)
│ ├── services/ # Services (DI com InjectionToken)
│ ├── stores/ # Stores (signals)
│ └── utils/ # Utilitários
│
├── features/ # Features lazy-loaded (domínios)
│ ├── auth/ # Autenticação
│ │ ├── pages/
│ │ │ ├── login/
│ │ │ ├── register/
│ │ │ └── forgot-password/
│ │ ├── auth.routes.ts
│ │ └── auth.routes.spec.ts
│ │
│ ├── home/ # Home
│ │ ├── pages/
│ │ │ └── home/
│ │ └── home.routes.ts
│ │
│ └── settings/ # Configurações
│ ├── pages/
│ │ └── settings/
│ └── settings.routes.ts
│
├── app.component.ts
├── app.config.ts
├── app.routes.ts
└── environments/
├── environment.ts
└── environment.prod.ts
📄 Estrutura Padrão de uma Page
src/app/features/[feature]/pages/[page]/
├── [page].page.ts # Componente principal
├── [page].page.html # Template
├── [page].page.scss # Estilos (SEMPRE SCSS regular, nunca CSS modules ou styled-components)
├── [page].page.spec.ts # Testes unitários
└── forms-configs/ # Opcional: config de formulário
└── [page]-form-config-fields.ts
📝 Template Padrão: Page Simples (Sem Layout)
// [page].page.ts
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Router } from '@angular/router';
import {
IonContent,
IonButton,
IonIcon
} from '@ionic/angular/standalone';
@Component({
selector: 'app-[page]',
standalone: true,
imports: [
CommonModule,
IonContent,
IonButton,
IonIcon,
],
templateUrl: './[page].page.html',
styleUrls: ['./[page].page.scss'],
})
export class [Page]Page {
isLoading = signal(false);
constructor(private router: Router) {}
async navigateTo(path: string): Promise<void> {
await this.router.navigate([path]);
}
}
<!-- [page].page.html -->
<ion-content class="ion-padding">
<h1>[Page Title]</h1>
<ion-button expand="block" (click)="navigateTo('/home')">
Go to Home
</ion-button>
</ion-content>
// [page].page.scss
:host {
display: block;
height: 100%;
}
📝 Template Padrão: Page com Layout (BasePagesLayoutComponent)
// [page].page.ts
import { Component, signal, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BasePagesLayoutComponent } from '@shared/layouts/base-pages-layout/base-pages-layout.component';
import {
IonContent,
IonList,
IonItem,
IonLabel,
IonIcon
} from '@ionic/angular/standalone';
@Component({
selector: 'app-[page]',
standalone: true,
imports: [
CommonModule,
BasePagesLayoutComponent,
IonContent,
IonList,
IonItem,
IonLabel,
IonIcon,
],
templateUrl: './[page].page.html',
styleUrls: ['./[page].page.scss'],
})
export class [Page]Page {
isLoading = signal(false);
// Dados para o menu (se necessário)
menuItems = [
{ title: 'Item 1', icon: 'home', route: '/home' },
{ title: 'Item 2', icon: 'settings', route: '/settings' },
];
}
<!-- [page].page.html -->
<app-base-pages-layout
[pageTitle]="'[Page Title]'"
[showBackButton]="true"
[showMenu]="true"
>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let item of menuItems">
<ion-icon [name]="item.icon" slot="start"></ion-icon>
<ion-label>{{ item.title }}</ion-label>
</ion-item>
</ion-list>
</ion-content>
</app-base-pages-layout>
🛣️ Configuração de Rotas
app.routes.ts (Principal)
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
redirectTo: 'auth',
pathMatch: 'full',
},
{
path: 'auth',
loadChildren: () =>
import('./features/auth/auth.routes').then(m => m.AUTH_ROUTES),
},
{
path: 'home',
loadChildren: () =>
import('./features/home/home.routes').then(m => m.HOME_ROUTES),
},
{
path: 'settings',
loadChildren: () =>
import('./features/settings/settings.routes').then(m => m.SETTINGS_ROUTES),
},
];
feature.routes.ts (Exemplo: auth.routes.ts)
import { Routes } from '@angular/router';
export const AUTH_ROUTES: Routes = [
{
path: '',
redirectTo: 'login',
pathMatch: 'full',
},
{
path: 'login',
loadComponent: () =>
import('./pages/login/login.page').then(m => m.LoginPage),
},
{
path: 'register',
loadComponent: () =>
import('./pages/register/register.page').then(m => m.RegisterPage),
},
{
path: 'forgot-password',
loadComponent: () =>
import('./pages/forgot-password/forgot-password.page').then(m => m.ForgotPasswordPage),
},
];
routes.constants.ts (Constantes)
export const APP_ROUTES = {
AUTH: {
LOGIN: '/auth/login',
REGISTER: '/auth/register',
FORGOT_PASSWORD: '/auth/forgot-password',
FULL_LOGIN: 'auth/login',
FULL_REGISTER: 'auth/register',
FULL_FORGOT_PASSWORD: 'auth/forgot-password',
},
HOME: {
MAIN: '/home',
FULL_MAIN: 'home',
},
SETTINGS: {
MAIN: '/settings',
FULL_MAIN: 'settings',
},
} as const;
🔧 Services (DI com InjectionToken)
Estrutura Padrão
src/app/shared/services/[domain]/
├── [domain].api.service.ts # Interface + InjectionToken
├── [domain].service.ts # Implementação real
├── [domain].mock.service.ts # Implementação mock
└── [domain]-config.service.ts # Flag individual
Exemplo: User Service
// user.api.service.ts
import { InjectionToken } from '@angular/core';
export interface IUserService {
getUser(id: string): Promise<any>;
updateUser(id: string, data: any): Promise<any>;
}
export const USER_SERVICE = new InjectionToken<IUserService>('UserService');
// user.service.ts
import { Injectable } from '@angular/core';
import { IUserService } from './user.api.service';
@Injectable()
export class UserService implements IUserService {
async getUser(id: string): Promise<any> {
// Implementação real (API)
}
async updateUser(id: string, data: any): Promise<any> {
// Implementação real (API)
}
}
// user.mock.service.ts
import { Injectable } from '@angular/core';
import { IUserService } from './user.api.service';
@Injectable()
export class UserMockService implements IUserService {
async getUser(id: string): Promise<any> {
return { id, name: 'Mock User' };
}
async updateUser(id: string, data: any): Promise<any> {
return { id, ...data };
}
}
// user-config.service.ts
import { InjectionToken } from '@angular/core';
export const USE_USER_CONFIG_MOCK = new InjectionToken<boolean>('UseUserConfigMock');
Registro no shared.provider.ts
import { Provider } from '@angular/core';
import { USER_SERVICE } from './services/user/user.api.service';
import { UserService } from './services/user/user.service';
import { UserMockService } from './services/user/user.mock.service';
import { USE_USER_CONFIG_MOCK } from './services/user/user-config.service';
import { USE_MOCK_GLOBAL } from './services/global-config.service';
export function provideMockService<T>(
token: InjectionToken<T>,
real: Type<T>,
mock: Type<T>,
individualFlag: InjectionToken<boolean>,
globalFlag: InjectionToken<boolean> = USE_MOCK_GLOBAL
): Provider[] {
return [
{ provide: token, useClass: UserService },
{ provide: USE_USER_CONFIG_MOCK, useValue: false },
];
}
// OU forma simplificada
export const APP_PROVIDERS: Provider[] = [
...provideMockService(USER_SERVICE, UserService, UserMockService, USE_USER_CONFIG_MOCK),
];
🧪 Testes Unitários (Vitest)
Estrutura Padrão
// [page].page.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { [Page]Page } from './[page].page';
describe('[Page]Page', () => {
let component: [Page]Page;
let fixture: ComponentFixture<[Page]Page>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [[Page]Page],
providers: [
// Mocks de services via InjectionToken
{ provide: USER_SERVICE, useValue: { getUser: vi.fn() } },
],
}).compileComponents();
fixture = TestBed.createComponent([Page]Page);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have isLoading false initially', () => {
expect(component.isLoading()).toBe(false);
});
});
Comando para Testar
# Testar arquivo específico
node "D:/2 - workspace/4 - basic inicial core angular ionic/basic-initial-core-full/node_modules/vitest/vitest.mjs" run "src/app/features/[feature]/pages/[page]/[page].page.spec.ts"
# Testar feature inteira
npx vitest run src/app/features/[feature] --coverage --silent
📦 Imports Ionic Standalone
// Sempre importar componentes individualmente
import {
IonContent,
IonHeader,
IonToolbar,
IonTitle,
IonButton,
IonIcon,
IonList,
IonItem,
IonLabel,
IonInput,
IonTextarea,
IonSelect,
IonSelectOption,
IonCheckbox,
IonRadio,
IonRadioGroup,
IonToggle,
IonDatetime,
IonImg,
IonAvatar,
IonBadge,
IonChip,
IonCard,
IonCardContent,
IonCardHeader,
IonCardTitle,
IonFab,
IonFabButton,
IonRefresher,
IonRefresherContent,
IonInfiniteScroll,
IonInfiniteScrollContent,
IonSkeletonText,
IonSpinner,
IonToast,
IonModal,
IonPopover,
IonAlert,
IonLoading,
IonActionSheet,
IonNote,
IonText,
IonBackButton,
IonButtons,
IonMenuButton,
IonSearchbar,
IonSegment,
IonSegmentButton,
IonTabBar,
IonTabButton,
IonTabs,
IonRippleEffect,
IonFooter,
IonThumbnail,
IonItemDivider,
IonItemGroup,
IonItemSliding,
IonItemOptions,
IonLabel,
} from '@ionic/angular/standalone';
⚠️ Regras de Arquitetura (ESLint)
| Regra | Descrição |
|---|---|
| Features não importam Features | Comunique via core/ ou shared/ |
| Core não importa Features | Core é independente |
| Shared não importa Core nem Features | Shared é "burro" |
| Fluxo | Component → Service (via Token) → HttpService → Interceptors → API |
🎯 Convenções de Nomenclatura
| Tipo | Padrão | Exemplo |
|---|---|---|
| Interfaces | prefixo I |
IUserLogged |
| Enums | sufixo Enum |
MenuTypeEnum |
| Booleanos | is/has/can/should |
isLoading |
| Signals | sem $ |
userSignal |
| Observables | sufixo $ |
user$ |
| Arquivos | kebab-case | user.service.ts |
| Suffix classes | Page ou Component |
LoginPage |
🚀 Checklist ao Criar Nova Page
- Criar pasta
src/app/features/[feature]/pages/[page]/ - Criar 4 arquivos:
.ts,.html,.scss,.spec.ts - Criar
forms-configs/se tiver formulário - Adicionar rota em
[feature].routes.ts - Adicionar rota em
app.routes.ts(lazy loading) - Adicionar constante em
routes.constants.ts - Usar
BasePagesLayoutComponentpara pages internas - Importar componentes Ionic standalone individualmente
- Usar InjectionToken para services (nunca classe diretamente)
- Criar testes unitários com Vitest
- Verificar imports (features não importam features)
📚 Referências
src/app/features/auth/- Exemplo completo de feature (login, register, forgot-password)src/app/shared/layouts/- Layouts compartilhadossrc/app/shared/services/- Services com DI patternsrc/app/core/interceptors/- 8 interceptors HTTP ativossrc/app/core/guards/- Guards de rota (funções CanActivateFn)
Última atualização: 2026-08-13 Projeto: basic-initial-core-full Stack: Angular 20 + Ionic 8 + Capacitor 8 + TypeScript 5.9