Depois de um longo período iterando sobre como uso assistentes de IA no meu workflow Angular, cheguei a uma conclusão: a qualidade do código que você recebe de volta é quase inteiramente determinada pela qualidade do prompt que você escreve. Isso parece óbvio, mas a maioria dos desenvolvedores trata a IA como um colega sênior que já conhece seu codebase, suas convenções e suas restrições. Ela não conhece. Você precisa ensiná-la toda vez. E a forma como você ensina é através do prompt.
Nada do que vem a seguir é o conselho genérico de 'seja específico, dê contexto'. São os prompts que eu realmente uso quando gero componentes, services, testes e decisões de arquitetura em Angular, mostrados antes e depois, porque a diferença entre um prompt vago e um preciso é mais fácil de ver do que de descrever.
Por Que Angular Precisa de Prompts Diferentes
Angular é opinativo. Ele tem uma forma específica de fazer injeção de dependência, um sistema de módulos (ou standalone components, que entraram em developer preview na v14.0 e se tornaram estáveis na v15), convenções RxJS, expectativas fortes de TypeScript, e uma estrutura de arquivos muito particular. Modelos de IA genéricos conhecem Angular, mas não sabem qual Angular. Eles podem gerar código usando NgModules quando seu projeto é standalone, ou usar componentes baseados em classe com lifecycle hooks quando você adotou signals. Você precisa dizer a eles.
Além disso, projetos Angular enterprise têm suas próprias camadas de convenção: bibliotecas compartilhadas, componentes de design system, padrões específicos de gerenciamento de estado, convenções de nomenclatura para services vs facades vs resolvers. Nada disso está nos dados de treinamento do modelo. Então o ônus está em você de fornecer isso.
O System Prompt / Bloco de Contexto Com Que Eu Sempre Começo
Seja usando uma ferramenta baseada em chat ou uma integração com IDE, a primeira coisa que faço é estabelecer um bloco de contexto. Eu configuro isso como system prompt ou colo no início de cada conversa. Aqui está mais ou menos como ele se parece para um projeto standalone baseado em signals. A linha de versão e as restrições mudam de codebase para codebase:
You are an Angular expert assisting a senior developer on an enterprise Angular 17.3+ project.
Constraints:
- Use standalone components only. No NgModules.
- Use Angular Signals for local state. Avoid BehaviorSubject for component-level state.
- RxJS is acceptable for HTTP streams and complex async flows.
- All components must be typed. No use of 'any'.
- Use inject() function for dependency injection, not constructor injection.
- Follow Angular style guide naming: MyFeatureComponent, MyFeatureService, etc.
- Use OnPush change detection strategy on all components.
- Tests use Jest + Angular Testing Library. No TestBed unless strictly necessary.
- CSS is SCSS. No inline styles.
- All observables must be unsubscribed. Prefer async pipe or takeUntilDestroyed().
When generating code, always provide the full file, not snippets.Esse bloco sozinho muda o output dramaticamente. Antes de adicioná-lo, eu recebia services com injeção por construtor, boilerplate de NgModule e componentes sem OnPush. Depois, recebo algo muito mais próximo do que eu realmente escreveria.
Prompts para Componentes
Antes: O Pedido Vago
No início, eu escrevia algo assim:
Create an Angular component for a user profile card.O que eu recebia de volta era um componente baseado em classe com injeção por construtor, sem tipos nos inputs, sem OnPush, e às vezes uma declaração de NgModule. Tecnicamente correto para Angular 2018, não útil para meu projeto.
Depois: O Pedido Estruturado
Create a standalone Angular 17.3+ component called UserProfileCardComponent.
Inputs:
- user: input.required<User>() (interface: { id: string; name: string; email: string; avatarUrl: string | null })
- isLoading: input<boolean>(false)
Behavior:
- When isLoading is true, show a skeleton placeholder (use a div with class 'skeleton')
- When user.avatarUrl is null, show initials from user.name in a circle div
- Emit an output called profileClicked: OutputEmitterRef<string> emitting user.id on card click
Style: SCSS. Card has a shadow and rounded corners. Use BEM naming.
Change detection: OnPush.
Do not use @Input() decorator. Use the input() signal API.
Provide the full .ts and .html files.Isso produz um componente que posso colocar no projeto com talvez um ou dois ajustes. As diferenças principais: especifiquei a versão do Angular, o estilo de API (signal inputs), o formato exato da interface, a lógica de comportamento e o formato de output. A versão precisa ser exata. Signal inputs chegaram na 17.1 e output() com OutputEmitterRef na 17.3, então um modelo a quem você diz apenas 'Angular 17' tem todo o direito de te entregar @Input() e EventEmitter, e estar certo.
Prompts para Services
Services precisam de um template diferente porque as preocupações principais são diferentes: escopo de injeção, tratamento de HTTP, gerenciamento de erros e tipos de retorno.
Create an Angular service called UserService.
- Injectable at root level
- Uses inject(HttpClient) for HTTP calls
- Base URL comes from inject(APP_CONFIG) token, property: apiBaseUrl
- Methods:
1. getUser(id: string): Observable<User>
- GET /users/:id
- Pipe through catchError, rethrow as typed AppError
2. updateUser(id: string, payload: Partial<User>): Observable<User>
- PATCH /users/:id
- Same error handling
- AppError interface: { code: string; message: string; statusCode: number }
- No BehaviorSubject or state management inside this service. Pure data access layer.
- Use typed HttpClient (pass response type in generic).As coisas críticas que eu sempre incluo para services: o escopo de injeção, de onde vem a configuração, as assinaturas exatas dos métodos com tipos de retorno, e o que o service NÃO deve fazer. Esse último ponto é surpreendentemente importante. Se você não diz 'essa é uma camada pura de acesso a dados', o modelo frequentemente vai adicionar um cache com BehaviorSubject e de repente seu service está fazendo gerenciamento de estado.
Prompts para Testes
Geração de testes é onde prompt engineering mais compensa. A diferença entre um teste genérico e um teste útil é enorme, e é quase inteiramente controlável pelo prompt.
Prompt de Teste Ruim
Write tests for UserProfileCardComponent.Você vai receber testes de renderização superficial que verificam se o componente renderiza, talvez um ou dois bindings de propriedade. Nada que realmente pegaria uma regressão.
Prompt de Teste Bom
Write Jest tests for UserProfileCardComponent using Angular Testing Library (render from @testing-library/angular).
Test these scenarios:
1. When isLoading is true, the skeleton placeholder is visible and the user name is not
2. When isLoading is false and user.avatarUrl is null, initials are derived from user.name and shown
3. When isLoading is false and user.avatarUrl is set, an img tag is rendered with the correct src
4. Clicking the card emits the correct user.id through the profileClicked output
5. Component renders without errors with minimum required input (user with all fields, isLoading false)
Do not use TestBed directly. Use the render() function from Testing Library.
Mock no services. This component has no service dependencies.
Use userEvent from @testing-library/user-event for interaction tests.O resultado é um arquivo de teste com cobertura real de comportamento real. Eu defino os cenários explicitamente porque o modelo não sabe o que importa no meu contexto de negócio. Só eu sei.
Prompts para Decisões de Arquitetura
Além de geração de código, eu também uso IA para questões de arquitetura. Essas precisam de um tipo muito diferente de prompt, um que carregue contexto suficiente para o modelo raciocinar sobre tradeoffs em vez de escolher a resposta óbvia. O exemplo abaixo é um template. O domínio nele é deliberadamente banal, porque o que faz o trabalho é a lista de restrições e a última linha, não o assunto.
I'm building a self-contained feature module in Angular 17.3+ (standalone).
Context:
- The screen renders 6 independent panels, each with its own data source
- Panels can be shown or hidden per user preference (stored through a preferences API)
- Data refreshes every 30 seconds or on user request
- The app uses NgRx for global state but this feature should stay self-contained
Question: Should I use:
a) NgRx feature state for this module
b) A signal-based store (a plain service with signals)
c) NgRx ComponentStore
Explain the tradeoffs for each in this specific context. Consider DevTools debugging, boilerplate cost, testability, and the refresh pattern. Don't just list general pros/cons. Apply them to my situation.A última frase é crucial. Sem ela, você recebe uma lista estilo Wikipedia de tradeoffs que não te ajuda a decidir nada. Pedir ao modelo para aplicar o raciocínio à sua situação específica o força a tomar uma posição.
O Que Sempre Incluir no Contexto
- Versão do Angular e flags de features principais (standalone, signals, etc.)
- Qual padrão de gerenciamento de estado você está usando e em que escopo
- O stack de testes (Jest vs Karma, Testing Library vs TestBed)
- Sua abordagem de CSS (SCSS, Tailwind, CSS Modules, tokens de design system)
- APIs específicas que são proibidas ou obrigatórias (inject() vs constructor, input() vs @Input)
- Expectativas de formato de output de arquivo (arquivo completo vs snippet)
- Quaisquer interfaces ou tipos que o código gerado precisa seguir
Iterando em Prompts Como Código
Eu mantenho um arquivo pessoal de templates de prompt que refinei ao longo do tempo. Basicamente um prompts.md nas minhas notas. Quando um prompt produz ótimo output, eu salvo. Quando falha, eu anoto o porquê e ajusto. É o mesmo ciclo de feedback que uso para refinar código. Prompt engineering é um ofício, e você melhora nele através de iteração deliberada.
Um padrão que achei consistentemente útil: quando um output gerado está errado, não apenas tente novamente com um prompt levemente diferente. Em vez disso, analise exatamente o que estava errado. Era a versão do Angular assumida? O padrão de injeção? A interface faltando? Então corrija essa lacuna específica no prompt. Variar o prompt aleatoriamente é perda de tempo.
Os Limites do Prompt Engineering
Quero ser honesto sobre o que essa abordagem não resolve. Nenhuma quantidade de prompt engineering substitui entender o que você está pedindo. Se eu não sei o que change detection OnPush significa, não consigo especificá-lo corretamente e não vou perceber quando o código gerado usar Default. O prompt amplifica seu conhecimento. Não substitui.
O outro limite é tudo que abrange vários arquivos de uma vez. Uma feature feita de um componente, um service, um resolver e uma store ainda é difícil de gerar de uma vez. Eu divido em pedaços menores com bons prompts e monto manualmente. Isso não é uma falha da ferramenta; é apenas um entendimento preciso do que ela faz bem.
Precisão Vence Esperteza
Os desenvolvedores que eu vejo tirando mais proveito de ferramentas de IA não são os com os prompts mais criativos. São os mais precisos. Eles conhecem seu stack bem o suficiente para especificá-lo com precisão. Definem interfaces antes de pedir implementações. Dizem ao modelo o que NÃO fazer com a mesma clareza com que dizem o que fazer.
Se você tirar uma coisa deste post: trate seu bloco de contexto como infraestrutura. Mantenha-o versionado, mantenha-o atualizado conforme seu stack evolui, e compartilhe com seu time. Um bom system prompt é um ativo do time.
Referências
- Angular Signals. https://angular.dev/guide/signals
- Angular dependency injection. https://angular.dev/guide/di/dependency-injection
- About GitHub Copilot. https://docs.github.com/en/copilot/get-started/about-github-copilot