Stack Angular β InfoWhere
Template de stack para projetos Angular
Γltima atualizaΓ§Γ£o: 25/01/2026
Uso: SecundΓ‘rio (projetos de clientes enterprise)
1. VisΓ£o Geral
Stack frontend para projetos de clientes enterprise que preferem/exigem Angular.
Filosofia
- Bootstrap como UI framework (ou Material se cliente exigir)
- Componentes pragmΓ‘ticos β dividir quando faz sentido, nΓ£o por dogma
- Signals para estado local e UI simples
- RxJS sΓ³ para sistemas grandes (real-time, streams complexos)
- Standalone components como padrΓ£o (sem NgModules)
- Diretivas quando simplificam o cΓ³digo
2. VersΓ΅es
| Componente | VersΓ£o | Notas |
|---|---|---|
| Angular | 19.x | Standalone components |
| TypeScript | 5.x | Vem com Angular |
| Node | 22.x | LTS |
| pnpm | Γltima | Gerenciador de pacotes |
3. DependΓͺncias Core
3.1 Framework Base
{
"dependencies": {
"@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
"@angular/core": "^19.0.0",
"@angular/forms": "^19.0.0",
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.6.0",
"zone.js": "~0.15.0"
}
}
3.2 UI Framework
PadrΓ£o: Bootstrap
{
"dependencies": {
"bootstrap": "^5.3.0",
"@popperjs/core": "^2.11.0"
}
}
Alternativa (se cliente exigir): Angular Material
{
"dependencies": {
"@angular/material": "^19.0.0",
"@angular/cdk": "^19.0.0"
}
}
### 3.3 HTTP & State
```json
{
"dependencies": {
"@angular/common": "^19.0.0",
"@ngrx/signals": "^19.0.0"
}
}
3.4 AutenticaΓ§Γ£o
{
"dependencies": {
"keycloak-angular": "^16.0.0",
"keycloak-js": "^26.0.0"
}
}
4. Testes
{
"devDependencies": {
"@angular/cli": "^19.0.0",
"@angular-devkit/build-angular": "^19.0.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"jasmine-core": "~5.4.0"
}
}
Cobertura mΓnima: 70%
5. AutenticaΓ§Γ£o
| Componente | Escolha |
|---|---|
| Identity Provider | Keycloak |
| Lib | keycloak-angular |
ConfiguraΓ§Γ£o tΓpica
// src/app/config/keycloak.config.ts
import { KeycloakService } from 'keycloak-angular';
export function initializeKeycloak(keycloak: KeycloakService) {
return () =>
keycloak.init({
config: {
url: environment.keycloak.url,
realm: environment.keycloak.realm,
clientId: environment.keycloak.clientId,
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html',
pkceMethod: 'S256',
},
});
}
App Config
// src/app/app.config.ts
import { ApplicationConfig, APP_INITIALIZER } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { KeycloakService, KeycloakBearerInterceptor } from 'keycloak-angular';
import { routes } from './app.routes';
import { initializeKeycloak } from './config/keycloak.config';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(withInterceptors([keycloakBearerInterceptor])),
KeycloakService,
{
provide: APP_INITIALIZER,
useFactory: initializeKeycloak,
multi: true,
deps: [KeycloakService],
},
],
};
6. Estrutura do Projeto
{projeto}/
βββ public/
β βββ assets/
β βββ silent-check-sso.html
βββ src/
β βββ main.ts
β βββ index.html
β βββ styles.scss
β βββ app/
β β βββ app.component.ts
β β βββ app.config.ts
β β βββ app.routes.ts
β β βββ core/ # Singleton services
β β β βββ guards/
β β β β βββ auth.guard.ts
β β β βββ interceptors/
β β β β βββ error.interceptor.ts
β β β βββ services/
β β β βββ auth.service.ts
β β βββ shared/ # Shared components/pipes
β β β βββ components/
β β β β βββ header/
β β β β βββ footer/
β β β βββ directives/
β β β βββ pipes/
β β βββ features/ # Feature modules
β β β βββ home/
β β β β βββ home.component.ts
β β β β βββ home.routes.ts
β β β βββ users/
β β β βββ components/
β β β β βββ user-card/
β β β βββ services/
β β β β βββ user.service.ts
β β β βββ models/
β β β β βββ user.model.ts
β β β βββ users.component.ts
β β β βββ users.routes.ts
β β βββ config/
β β βββ keycloak.config.ts
β βββ environments/
β βββ environment.ts
β βββ environment.prod.ts
βββ angular.json
βββ package.json
βββ tsconfig.json
βββ tsconfig.app.json
βββ tsconfig.spec.json
βββ README.md
7. PadrΓ΅es e ConvenΓ§Γ΅es
7.1 Componentes β Abordagem PragmΓ‘tica
PrincΓpio: Dividir quando faz sentido, nΓ£o por dogma.
| Quando criar componente separado | Quando NΓO separar |
|---|---|
| Reutilizado em 2+ lugares | Usado sΓ³ uma vez e simples |
| LΓ³gica complexa isolada | Poucos elementos HTML |
| TestΓ‘vel independentemente | Acoplado demais ao pai |
7.2 Signals vs RxJS β Quando usar cada um
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SIGNALS (padrΓ£o para maioria dos casos) β
β β
β β
Estado local do componente β
β β
Valores derivados (computed) β
β β
FormulΓ‘rios simples β
β β
UI state (loading, error, etc.) β
β β
HTTP requests simples (com toSignal) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RXJS (sistemas grandes, casos complexos) β
β β
β β
WebSockets / real-time β
β β
MΓΊltiplos HTTP combinados β
β β
Debounce/throttle de inputs β
β β
Streams contΓnuos de eventos β
β β
OperaΓ§Γ΅es complexas (switchMap, mergeMap, etc.) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
7.3 Exemplo com Signals (maioria dos casos)
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Contagem: {{ count() }}</p>
<p>Dobro: {{ double() }}</p>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
// signal() β valor reativo
count = signal(0);
// computed() β valor derivado (recalcula automaticamente)
double = computed(() => this.count() * 2);
increment() {
this.count.update(c => c + 1);
}
}
7.4 Exemplo com RxJS (sistemas grandes)
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs';
@Component({
selector: 'app-search',
standalone: true,
imports: [AsyncPipe, ReactiveFormsModule],
template: `
<input [formControl]="searchControl" placeholder="Buscar...">
@for (item of results$ | async; track item.id) {
<div>{{ item.name }}</div>
}
`
})
export class SearchComponent {
private api = inject(ApiService);
searchControl = new FormControl('');
// RxJS para debounce + HTTP
results$ = this.searchControl.valueChanges.pipe(
debounceTime(300), // Espera 300ms
distinctUntilChanged(), // SΓ³ se mudou
switchMap(term => this.api.search(term)) // Cancela request anterior
);
}
7.5 Componentes (Standalone)
// src/app/features/users/components/user-card/user-card.component.ts
import { Component, input, output } from '@angular/core';
import { User } from '../../models/user.model';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div class="user-card">
<h3>{{ user().name }}</h3>
<p>{{ user().email }}</p>
<button (click)="onSelect()">Select</button>
</div>
`,
styles: [`
.user-card {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
`]
})
export class UserCardComponent {
// Novos inputs baseados em Signals (Angular 17+)
user = input.required<User>();
// Novo output
select = output<User>();
onSelect(): void {
this.select.emit(this.user());
}
}
7.2 Services
// src/app/features/users/services/user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../models/user.model';
import { environment } from '../../../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class UserService {
private http = inject(HttpClient);
private apiUrl = `${environment.apiUrl}/users`;
getAll(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getById(id: string): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
create(user: Partial<User>): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
update(id: string, user: Partial<User>): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user);
}
delete(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
7.3 Signals (State Management)
// src/app/features/users/users.store.ts
import { computed, inject } from '@angular/core';
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
import { UserService } from './services/user.service';
import { User } from './models/user.model';
interface UsersState {
users: User[];
loading: boolean;
error: string | null;
}
const initialState: UsersState = {
users: [],
loading: false,
error: null,
};
export const UsersStore = signalStore(
withState(initialState),
withComputed((state) => ({
activeUsers: computed(() => state.users().filter(u => u.active)),
userCount: computed(() => state.users().length),
})),
withMethods((store, userService = inject(UserService)) => ({
async loadUsers() {
patchState(store, { loading: true, error: null });
try {
const users = await userService.getAll().toPromise();
patchState(store, { users: users ?? [], loading: false });
} catch (error) {
patchState(store, { error: 'Failed to load users', loading: false });
}
},
}))
);
7.4 Guards
// src/app/core/guards/auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { KeycloakService } from 'keycloak-angular';
export const authGuard: CanActivateFn = async (route, state) => {
const keycloak = inject(KeycloakService);
const router = inject(Router);
const isAuthenticated = await keycloak.isLoggedIn();
if (!isAuthenticated) {
await keycloak.login({
redirectUri: window.location.origin + state.url,
});
return false;
}
return true;
};
7.5 Routes
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './core/guards/auth.guard';
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./features/home/home.component')
.then(m => m.HomeComponent),
},
{
path: 'users',
canActivate: [authGuard],
loadChildren: () => import('./features/users/users.routes')
.then(m => m.USERS_ROUTES),
},
{
path: '**',
redirectTo: '',
},
];
8. Infraestrutura
| Componente | Escolha |
|---|---|
| Build | Angular CLI |
| Deploy | Nginx / Azure Static Web Apps |
| CI/CD | GitHub Actions / Azure DevOps |
Dockerfile tΓpico
# Build stage
FROM node:22-alpine AS builder
WORKDIR /app
RUN npm install -g pnpm @angular/cli
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build --configuration=production
# Production stage
FROM nginx:alpine
COPY --from=builder /app/dist/{projeto}/browser /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
9. O que NΓO usar
| Tecnologia | Motivo |
|---|---|
| MΓ³dulos (NgModules) | Preferir Standalone Components |
| RxJS para tudo | Preferir Signals quando possΓvel |
| NgRx Store completo | Preferir @ngrx/signals (mais simples) |
| Angular < 19 | Sempre versΓ£o recente |
| npm | Preferir pnpm |
10. Checklist de Novo Projeto
- Criar projeto com
ng new --standalone - Instalar Angular Material ou PrimeNG
- Configurar Keycloak
- Criar estrutura de pastas (core, shared, features)
- Configurar environments
- Configurar guards
- Criar interceptors (error, loading)
- Criar Dockerfile + nginx.conf
- Configurar Azure DevOps / GitHub Actions
- Criar README.md com instruΓ§Γ΅es
11. Links de ReferΓͺncia
Nota: Este template Γ© a base. Cada projeto de cliente pode ter ajustes especΓficos documentados no
technical_context.mddo projeto.