A partir da versão 7 do Angular, as versões principais do núcleo Angular e da CLI estão alinhadas. Isso significa que, para usar a CLI ao desenvolver um aplicativo Angular, a versão @angular/core e a CLI precisam ser as mesmas.
Orientações para acesso aos repositórios Git encontram-se na página inicial.
Tecnologia | Versão | Propósito |
---|---|---|
Ionic Framework | 8.0.0 | Framework UI para aplicações híbridas |
Angular | 20.0.0 | Framework JavaScript/TypeScript |
Capacitor | 7.4.3 | Runtime nativo para aplicações web |
TypeScript | 5.8.0 | Linguagem de programação tipada |
Node.js | 24.0.0 | Ambiente de execução |
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Presentation │ │ Business │ │ Data │
│ Layer │◄──►│ Logic │◄──►│ Layer │
│ (Components) │ │ (Services) │ │ (Repositories) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
App Component (Root)
├── Tabs Component (Shell)
│ ├── Tab1 Page (Lazy Loaded)
│ ├── Tab2 Page (Lazy Loaded)
│ └── Tab3 Page (Lazy Loaded)
└── Shared Components
└── Explore Container
coatic-angular-mobile/
├── 📱 android/ # Projeto Android nativo
├── 🍎 ios/ # Projeto iOS nativo
├── 🌐 www/ # Build de produção
├── 📦 src/ # Código fonte
│ ├── 🎯 app/ # Aplicação Angular
│ ├── 🎨 assets/ # Recursos estáticos
│ ├── 🌍 environments/ # Configurações de ambiente
│ └── 🎭 theme/ # Temas e estilos
├── ⚙️ capacitor.config.ts # Configuração Capacitor
├── 📋 package.json # Dependências npm
└── 🔧 angular.json # Configuração Angular
/src
src/
├── app/
│ ├── explore-container/ # Componente reutilizável
│ │ ├── explore-container.component.ts
│ │ ├── explore-container.component.html
│ │ ├── explore-container.component.scss
│ │ └── explore-container.component.spec.ts
│ ├── tab1/ # Página Tab 1
│ │ ├── tab1.page.ts
│ │ ├── tab1.page.html
│ │ ├── tab1.page.scss
│ │ └── tab1.page.spec.ts
│ ├── tab2/ # Página Tab 2
│ ├── tab3/ # Página Tab 3
│ ├── tabs/ # Container de abas
│ │ ├── tabs.page.ts
│ │ ├── tabs.page.html
│ │ ├── tabs.page.scss
│ │ ├── tabs.page.spec.ts
│ │ └── tabs.routes.ts # Rotas das abas
│ ├── app.component.ts # Componente raiz
│ ├── app.component.html
│ ├── app.component.scss
│ └── app.routes.ts # Configuração de rotas
├── assets/
│ ├── icon/
│ │ └── favicon.png
│ └── shapes.svg
├── environments/
│ ├── environment.ts # Desenvolvimento
│ └── environment.prod.ts # Produção
└── theme/
└── variables.scss # Variáveis CSS customizadas
Arquivo: src/app/app.component.ts
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
standalone: true,
imports: [IonApp, IonRouterOutlet]
})
export class AppComponent {
constructor() {}
}
Responsabilidades:
Arquivo: src/app/tabs/tabs.page.ts
@Component({
selector: 'app-tabs',
templateUrl: 'tabs.page.html',
styleUrls: ['tabs.page.scss'],
standalone: true,
imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel]
})
export class TabsPage {
constructor() {}
}
Responsabilidades:
Estrutura padrão:
@Component({
selector: 'app-tab1',
templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss'],
standalone: true,
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
})
export class Tab1Page {
constructor() {}
}
Responsabilidades:
Arquivo: src/app/explore-container/explore-container.component.ts
Responsabilidades:
Arquivo: src/app/app.routes.ts
export const routes: Routes = [
{
path: '',
loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
},
];
Arquivo: src/app/tabs/tabs.routes.ts
export const routes: Routes = [
{
path: 'tabs',
component: TabsPage,
children: [
{
path: 'tab1',
loadComponent: () => import('../tab1/tab1.page').then((m) => m.Tab1Page),
},
{
path: 'tab2',
loadComponent: () => import('../tab2/tab2.page').then((m) => m.Tab2Page),
},
{
path: 'tab3',
loadComponent: () => import('../tab3/tab3.page').then((m) => m.Tab3Page),
},
{
path: '',
redirectTo: '/tabs/tab1',
pathMatch: 'full',
},
],
},
{
path: '',
redirectTo: '/tabs/tab1',
pathMatch: 'full',
},
];
Arquivo: capacitor.config.ts
const config: CapacitorConfig = {
appId: 'io.ionic.starter',
appName: 'coatic-mobile',
webDir: 'www'
};
Configurações Importantes:
appId
: Identificador único da aplicaçãoappName
: Nome da aplicaçãowebDir
: Diretório de build webArquivo: angular.json
Principais configurações:
Arquivo: ionic.config.json
{
"name": "coatic-mobile",
"integrations": {
"capacitor": {}
},
"type": "angular"
}
Arquivos de configuração:
tsconfig.json
: Configuração basetsconfig.app.json
: Configuração da aplicaçãotsconfig.spec.json
: Configuração dos testesDesenvolvimento: src/environments/environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api'
};
Produção: src/environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.coatic.com'
};
# Desenvolvimento
npm start # Servidor de desenvolvimento
ionic serve # Servidor Ionic com live reload
ionic serve --lab # Servidor com preview iOS/Android
# Build
npm run build # Build para produção
ionic build # Build Ionic
ionic build --prod # Build otimizado
# Capacitor
ionic capacitor run ios # Executar no iOS
ionic capacitor run android # Executar no Android
ionic capacitor sync # Sincronizar código
Para desenvolvimento com live reload em dispositivos:
# iOS
ionic capacitor run ios --livereload --external
# Android
ionic capacitor run android --livereload --external
chrome://inspect
Configurado automaticamente com Angular CLI para desenvolvimento rápido.
src/
├── app/
│ ├── *.spec.ts # Testes unitários
│ └── e2e/ # Testes end-to-end (opcional)
└── test.ts # Configuração de testes
Framework: Jasmine + Karma
Executar testes:
npm test # Execução única
npm run test:watch # Modo watch
npm run test:coverage # Com coverage
Exemplo de teste:
describe('Tab1Page', () => {
let component: Tab1Page;
let fixture: ComponentFixture<Tab1Page>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [Tab1Page]
});
fixture = TestBed.createComponent(Tab1Page);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Framework: Cypress (recomendado) ou Protractor
Configuração:
npm install --save-dev cypress
npx cypress open
Para testes com dependências:
const mockService = jasmine.createSpyObj('DataService', ['getData']);
TestBed.configureTestingModule({
providers: [
{ provide: DataService, useValue: mockService }
]
});
# Desenvolvimento
ionic build
# Produção
ionic build --prod
Output: Diretório www/
# Preparar build
ionic build --prod
ionic capacitor copy ios
ionic capacitor sync ios
# Abrir Xcode
ionic capacitor open ios
Passos no Xcode:
# Preparar build
ionic build --prod
ionic capacitor copy android
ionic capacitor sync android
# Abrir Android Studio
ionic capacitor open android
Passos no Android Studio:
name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: ionic build --prod
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
Para habilitar PWA:
ng add @angular/pwa
Recursos PWA:
feature/
├── components/ # Componentes específicos
├── services/ # Serviços da feature
├── models/ # Interfaces e tipos
├── pages/ # Páginas da feature
└── feature.module.ts # Módulo da feature (se não standalone)
UserProfileComponent
)DataService
)user-profile.component.ts
)userName
)API_URL
)// Angular imports primeiro
import { Component, OnInit } from '@angular/core';
// Ionic imports
import { IonContent, IonHeader } from '@ionic/angular';
// Third-party imports
import { Observable } from 'rxjs';
// Local imports
import { DataService } from '../services/data.service';
{
path: 'feature',
loadChildren: () => import('./feature/feature.routes').then(m => m.routes)
}
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
trackByFn(index: number, item: any) {
return item.id;
}
<ion-virtual-scroll [items]="items" approxItemHeight="50px">
<ion-item *virtualItem="let item">
{{ item.name }}
</ion-item>
</ion-virtual-scroll>
import { DomSanitizer } from '@angular/platform-browser';
constructor(private sanitizer: DomSanitizer) {}
sanitizeHtml(html: string) {
return this.sanitizer.sanitize(SecurityContext.HTML, html);
}
// capacitor.config.ts
{
server: {
cleartext: false // Força HTTPS
}
}
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' https:; script-src 'self'">
<ion-button aria-label="Fechar modal">
<ion-icon name="close"></ion-icon>
</ion-button>
@ViewChild('firstInput') firstInput!: ElementRef;
ionViewDidEnter() {
this.firstInput.nativeElement.focus();
}
<ion-label>
<h2>Título Principal</h2>
<p>Descrição detalhada</p>
</ion-label>
Erro: Cannot find module '@ionic/angular'
# Solução
rm -rf node_modules package-lock.json
npm install
Erro: Module not found: Error: Can't resolve
# Verificar imports
# Verificar se o módulo está instalado
npm ls [package-name]
Erro: [capacitor] [error] Unable to launch iOS app
# Limpar e rebuildar
ionic capacitor clean ios
ionic build
ionic capacitor copy ios
ionic capacitor sync ios
Erro: Android build failures
# Verificar ANDROID_HOME
echo $ANDROID_HOME
# Limpar projeto Android
cd android
./gradlew clean
cd ..
Problema: App lento no dispositivo
Problema: Bundle size muito grande
# Analisar bundle
npm run build -- --stats-json
npx webpack-bundle-analyzer www/stats.json
Erro: Code signing issues
Erro: App rejected by App Store
Erro: APK não instala
Erro: Play Store rejection
Use live reload instead:
ionic serve --external
Para debug de performance:
npm install --save-dev flipper
ionic doctor # Verificar configuração
ionic info # Informações do sistema
ionic config list # Listar configurações
ng add @ngrx/store
ng add @ngrx/effects
ng add @ngrx/store-devtools
npm install @datorama/akita
@Injectable({
providedIn: 'root'
})
export class StateService {
private state = new BehaviorSubject(initialState);
getState() {
return this.state.asObservable();
}
updateState(newState: Partial<State>) {
this.state.next({...this.state.value, ...newState});
}
}
npm install @capacitor/camera
import { Camera, CameraResultType } from '@capacitor/camera';
async takePicture() {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri
});
}
npm install @capacitor/geolocation
npm install @capacitor/push-notifications
ng add @angular/localize
// app.config.ts
import { registerLocaleData } from '@angular/common';
import localePt from '@angular/common/locales/pt';
registerLocaleData(localePt);
// theme.service.ts
@Injectable({
providedIn: 'root'
})
export class ThemeService {
setTheme(theme: 'light' | 'dark') {
document.body.setAttribute('color-theme', theme);
}
}
// variables.scss
[color-theme="dark"] {
--ion-color-primary: #3880ff;
--ion-background-color: #121212;
}
// app.component.ts
constructor(private swUpdate: SwUpdate) {
if (swUpdate.isEnabled) {
swUpdate.available.subscribe(() => {
if (confirm('Nova versão disponível. Atualizar?')) {
window.location.reload();
}
});
}
}
npm install @ionic/storage-angular
import { Storage } from '@ionic/storage-angular';
constructor(private storage: Storage) {
this.init();
}
async init() {
await this.storage.create();
}
async set(key: string, value: any) {
await this.storage.set(key, value);
}
// hooks/use-network.ts
export function useNetwork() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
Para aplicações muito grandes, considere dividir em micro-frontends:
// Module Federation com Webpack 5
const ModuleFederationPlugin = require('@module-federation/webpack');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
feature1: 'feature1@http://localhost:4201/remoteEntry.js',
},
}),
],
};
Última atualização: $(date)
Versão da Wiki: 1.0.0