1 / 23
S. O. L. I. D.

Princípios no
Angular

Não decore. Aprenda no Código.

by Walteann Costa
2 / 23

A armadilha da teoria

Muitos devs frontend leem sobre S.O.L.I.D, decoram os termos bonitos para as entrevistas... e no dia a dia continuam criando God Components (aqueles componentes gigantes que fazem absolutamente tudo).

Os princípios não são check-lists de regras engessadas. São a divisa exata entre um código onde você tem medo de alterar algo e um código escalável e seguro para o futuro.

Confira agora 15 cenários dissecados no código, com os "Erros", os "Acertos" e O Mais Importante: O Porquê de evitar aquele padrão!

3 / 23
S - Single Responsibility Principle

O Princípio da Responsabilidade Única

A teoria define: "Uma classe deve ter um e apenas um motivo para mudar".

No desenvolvimento Angular, a separação de responsabilidades significa garantir que seus componentes fiquem imunes às regras complexas da malha de dados. O Componente apenas desenha a View (HTML), enquanto os Services lidam com a regra de Negócios pura.

4 / 23

SRP - Exemplo 1 💻

Componentes "Mudos" (Dumb Components)

O que evitar? Injetar o HTTP direto na View.

// ❌ ERRADO: O Componente incha se a API mudar! @Component({...}) export class UsersComponent { users: IUser[] = []; constructor(private http: HttpClient) {} ngOnInit() { this.http.get('/api/users').subscribe(d => this.users = d); } }
// ✅ CERTO: O Componente apenas exibe. O Service Trabalha! @Component({...}) export class UsersComponent { users$ = this.userService.getUsers(); constructor(private userService: UserService) {} }
💡 O Porquê evitar:

Se a API sofre um breaking change, a atualização deve ocorrer em um único Service isolado. Sua view jamais deveria tratar erros nativos de infraestrutura do HttpClient ou ficar gastando RAM gerenciando cancelamento de Subscriptions manuais em Componentes Folha.

5 / 23

SRP - Exemplo 2 💻

O Fim dos "God Services"

O que evitar? Arquivos que acumulam ações sistêmicas gigantes.

// ❌ ERRADO: Tudo misturado em um único arquivo caótico injetado em tudo @Injectable() export class AppGlobalService { refreshToken() {...} checkoutCart(items) {...} getUserProfile() {...} }
// ✅ CERTO: Segregue em arquivos focados e modulares // 📦 auth.service.ts export class AuthService { refreshToken() {...} } // 📦 cart.service.ts export class CartService { checkout(items) {...} }
💡 O Porquê evitar:

Quando seu colega mexer no código do Carrinho, todos os componentes (até os da Home) que injetam e importam esse "GodService" revalidarão a injeção dele inutilmente. Isso gera conflitos desnecessários no Git e aumenta o uso de RAM na sessão sem nenhuma utilidade.

6 / 23

SRP - Exemplo 3 💻

A Lógica Pura pertence aos Custom Pipes

O que evitar? Popular o Componente View de calculadoras Strings brutas.

// ❌ ERRADO: O arquivo Typescript sujo cuidando de máscaras visuais! export class UsersComponent { formatDocumentCpf(val: string) { return val.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, ...); } }
// ✅ CERTO: Crie Angular Pipes focados unicamente nisto! @Pipe({ name: 'cpf' }) export class CpfPipe implements PipeTransform { transform(val: string) { return FormaterUtils.cpf(val); } }
💡 O Porquê evitar:

Anotar a chamada {{ formatarRegraPesada(user.id) }} direto na View força o Change Detector do Angular a executar o método novamente a cada ciclo de detecção de mudanças, travando a interface do usuário! Um Custom Pipe puro recalcula o valor estritamente só quando o argumento de entrada sofre mutação.

7 / 23
O - Open/Closed Principle

O Princípio Aberto/Fechado

A teoria define: "As entidades devem ser abertas para extensão, mas fechadas para modificação".

Trata-se de arquitetar módulos que permitam extensibilidade funcional sem termos que invadir o arquivo base do componente (quebrar o status de código fechado dele) apenas para colocar um caminhão de novos IFs lógicos perigosos a cada Variante na Sprint de Layout.

8 / 23

OCP - Exemplo 1 💻

Content Projection Genérico (ng-content)

O que evitar? Quebrar e sujar Cards Genéricos exigindo Inputs Infinitos.

<!-- ❌ ERRADO: O arquivo base incha a cada nova condicional interna --> <app-modal [showDeleteButton]="true" [showWarningMessage]="false" [contentType]="'admin_settings'"> </app-modal>
<!-- ✅ CERTO: OCP perfeito! O Modal base atua como Carcaça burrinha --> <app-modal> <app-modal-header>Configurações</app-modal-header> <app-modal-content> <!-- Use ng-content! Fatie as Views por fora via projeção pura --> <admin-settings-tab></admin-settings-tab> </app-modal-content> </app-modal>
💡 O Porquê evitar:

Atrelar múltiplos cenários baseados em Inputs IFs engessa o time inteiro. Ao usar ng-content, nós fechamos o Container Base com absoluta imunidade a quebras futuras. Amanhã para adicionar botões animados basta Você acoplar e estender via Tags Externas blindadas sem encostar no arquivo fonte do Modal!

9 / 23

OCP - Exemplo 2 💻

Diretivas vs Switches Engessados

O que evitar? Infestar o HTML com lógicas gigantes e fixas acopladas de IF Arrays.

<!-- ❌ ERRADO: Layout frágil sujo por amarras diretas em View --> <div *ngIf="user.role === 'Admin' || user.role === 'Master'"> <button>Painel Sigiloso</button> </div>
<!-- ✅ CERTO: Extenda novas Role Rules usando injeção de DIRETIVAS! --> <button *appHasRole="['Admin', 'Master']"> Painel Sigiloso </button>
💡 O Porquê evitar:

Manter lógica sensível de permissões colada na malha frágil do HTML em cada View resulta em dívidas técnicas irredutíveis. Se a regra de acesso mudar ou a matriz de permissões crescer, você ajusta em um único arquivo TypeScript central da Diretiva, eliminando a caça exaustiva em dezenas de templates espalhados.

10 / 23

OCP - Exemplo 3 💻

Os Poderes de Middleware do HttpInterceptor

O que evitar? Modificar à mão as instâncias Request Bases de cada Service.

// ❌ ERRADO: Poluindo todos os Services pra acoplar o Cabeçalho JWT getProfile() { const h = new HttpHeaders({ Authorization: `Bearer ${tk}` }); return this.http.get('/api', { headers: h }); }
// ✅ CERTO: A regra impera como Plugin passivo. Muro fechado base! @Injectable() export class TokenInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler) { const clone = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); return next.handle(clone); } }
💡 O Porquê evitar:

Numa squad com 120 Services corporativos distintos enviando arquivos com HttpClient base nativos, exigir e obrigar que a base seja furada num Breaking Change gigante de Refatoração em cada Service é um crime funcional! O Interceptor do Angular funciona perfeitamente como Cascata Middleware, acoplável e seguro globalmente sem você editar uma letra dos Services.

11 / 23
L - Liskov Substitution Principle

Substituição de Liskov

A teoria define: "Subtipos devem ser perfeitamente substituíveis pelas suas classes-base sem corromper a consistência do sistema.".

Se o Serviço Base TS determina nos contratos rigorosos que tem um Output String na linha final, mas seu Componente Substituto "customizado" da squad resolve falhar essa regra de silêncio e mentir o Type retornando um Number cru onde a regra original era uma String, os Async Pipes e Filtros dos Observables derretem em cascata.

12 / 23

LSP - Exemplo 1 💻

Os Mocks que Mentem nos Testes (Unit Testing)

O que evitar? Sub-versões de dublês de Mock trapaceando em types forçados.

// ❌ ERRADO: Retornos imprevisíveis mentindo na herança base class AuthMockService { // O Sistema Pai espera um Objeto, e você joga NULL pra pular? getProfile() { return null; } }
// ✅ CERTO: O Dublê cumpre o Polimorfismo sagrado prometido class AuthMockService implements IAuthAPI { getProfile(): Observable<IUser> { return of({ id: 1, name: 'Mocado' }); } }
💡 O Porquê evitar:

O TypeScript confia plenamente na assinatura do contrato da classe-base. Se o Mock retorna null onde o sistema espera um objeto, qualquer acesso a propriedades desse retorno — como user.name ou user.id — vai lançar um TypeError em runtime, quebrando componentes que dependem do dado corretamente tipado.

13 / 23

LSP - Exemplo 2 💻

Inputs Omitidos Silenciosamente nas Heranças

O que evitar? Perder ou jogar fora premissas em Componentes Extends.

// ❌ ERRADO: Desacordos Silenciosos corrompem todo o Form Base! export abstract class BaseButton { @Input() isLoading: boolean; // O pai prometeu Loading! } export class SuperButton extends BaseButton { // A view html dele não injeta ou liga pro isLoading.. // Ele força o Pai a mandar Loading Ativo à toa.. }
// ✅ CERTO: Subtipos NUNCA perdem as promessas Sagradas de input <!-- A Herança do HTML Subordinado prega uso do Dado Estrito --> <button> <spinner *ngIf="isLoading"></spinner> Enviar </button>
💡 O Porquê evitar:

Heranças hierárquicas em Componentes Angulares são baseadas de Promessas Absolutas. Se o UI Kit da Empresa decreta que *todo Button* semáforo aceitará ser disabled, o Desenvolvedor do SubComponent Customizado não pode simplesmente "Apagar" do código dele a funcionalidade bloqueadora de inputs. Isso fura segurança do Liskov nativo do app.

14 / 23

LSP - Exemplo 3 💻

Event Emitters Manipulados Sujos e Dinâmicos

O que evitar? A classe Derivada Emitir Formatos estranhos quebrando as Assinaturas Originais.

// ❌ ERRADO: Você muda os tipos corrompendo a estrutura Pai! // O Base Component Original emitia emitiu o valor Limpo! @Output() ClickEvt = new EventEmitter<string>(); // O SubComponent decide tacar o Object Browser Full sujo aqui! @Output() override ClickEvt = new EventEmitter<any>();
// ✅ CERTO: Mantenha a Consistência Sagrada da String Liskoviana @Output() override ClickEvt = new EventEmitter<string>(); // Emitir Types Estritos Garante o Container Pai Mestre Limpo.
💡 O Porquê evitar:

Causar crash no Runtime com uma bomba Type "any". Funções mestres conectadas ao seu Output ngClick engasgam completamente pois o programador original usava um método Substring/Replace nativo na recepção do Evento esperando o String Base Prometido. Lançar o payload falso (Ex: O Event pointer do click do DOM puro) detona o Javascript em Produção!

15 / 23
I - Interface Segregation Principle

A Segregação de Interfaces

A teoria define: "Clientes não devem depender de lógicas e interfaces gigantes das quais na prática não utilizam.".

Pense puramente em micro-contratos! Chega de trafegar e vazar chaves de uma Database User completa na RAM apenas porque uma View Burra exigia o simples acesso minúsculo de exibir o campo `AvatarPicUrl` preso lá dentro do buraco.

16 / 23

ISP - Exemplo 1 💻

Minimizando Inputs Vazados em Memória do DOM

O que evitar? Jogar Componentes inteiros no State sendo que você precisa de Uma String.

<!-- ❌ ERRADO: Passando Entidade Full com Senhas Inúteis --> <user-avatar [data]="fullUserObj"></user-avatar> export class AvatarComponent { @Input() data: IFullUser; // 50 propriedades à toa! VAZAMENTO! }
<!-- ✅ CERTO: A Segregação Clama pela Interface Menor Imutável --> <user-avatar [url]="user.avatarUrl"></user-avatar> export class AvatarComponent { @Input() url: string; // Extremamente Limpo e Testável à vontade }
💡 O Porquê evitar:

Conhecido também pelos arquitetos como ("Data-Leak" de Scope). Espalhar componentes de interface pesados como Database Rows para trafegar no DOM, causa cópias profundas violentas de megabytes inúteis de Memória RAM na vida do Client-Side e fere brutamente o Isolamento Componentizado Básico de Views.

17 / 23

ISP - Exemplo 2 💻

Fatiando View Models Usando Utility Pick do TS

O que evitar? Puxar o peso de Árvores de Diretório corporativas num Type solto.

// ❌ ERRADO: Acoplando Interface Raiz em módulo burro periférico export type CartPayload = IFullDatabaseSystemUser; function parsePayment(u: IFullDatabaseSystemUser) {...}
// ✅ CERTO: O Typescript Utility Rules (Pick, Omit) é Rei absoluto export type CartModel = Pick<IUser, 'id' | 'email'>; // A função garante blindagem com uso focado sem invocar Monstros. function buildPaymentRules(u: CartModel) { ... }
💡 O Porquê evitar:

Importações descontroladas acarretam problemas no Ciclo de Arquivos da sua Arquitetura Angular. O uso Mágico do Typescript Pick/Omit clona os contratos do BackEnd base em Miniaturas Blindadadas estritamente cirúrgicas eliminando a força de Acoplamento do iceberg da dependência Master!

18 / 23

ISP - Exemplo 3 💻

Sub-contratos para Impedir Quebras em Ações Enormes

O que evitar? Você pede para o Service Injetor limpar um Cache, mas Força na Injeção acesso raiz destruidor ao construtor inteiro copiando até os acessos Write/Save e Auth!

// ❌ ERRADO: Conectando Instâncias Globais pra tarefas minúsculas class MenuHeaderComponent { constructor(private s: FullAppWideStorageSystem) {} }
// ✅ CERTO: Interfaces Particulares Limitadoras nos Argumentos Injetados class StorageSys implements ICacheClearer, IFileSaver {...} class MenuHeaderComponent { constructor(@Inject(CACHE) private s: ICacheClearer){} }
💡 O Porquê evitar:

Assine barreiras de Segurança na camada do próprio Injector. Modelando as Classes de Injeção em Sub-Tópicos nós proibiremos eternamente o seu programador Júnior de ativar acidentalmente chamadas arriscadas profundas destrutivas baseadas no Storage Main porque o Token barrou seu Component.

19 / 23
D - Dependency Inversion Principle

Inversão das Dependências

A teoria define: "Dependa inteiramente de camadas limitadas por abstrações seguras, não importe pacotes fixos cegamente".

O DI System fantástico do Angular brilha muito aqui. A lógica raiz de um Painel limpo Checkout Front-End nunca importa do NPM a marca do seu Gateway Atual de Testes (ex: MercadoPago SDK, Stripe Lib Base). O Token delega na Abstração a inversão limpa completa!

20 / 23

DIP - Exemplo 1 💻

Blindagem Inversível: Provider Aliases (useClass)

O que evitar? O temível Vendor Lock-in! Importar referências estrábicas Npm nos construtores vitais Centrais.

// ❌ ERRADO: Acoplamento rígido cravado na parede import { DataDogSDK } from 'datadog-wrapper'; export class HomeComp { constructor(private logger: DataDogSDK) {} }
// ✅ CERTO: O Component Base Injeta Abstração Limpa de Domínio. export class HomeComp { constructor(private lgr: BaseLogger) {} } // 🌎 Modulo Root provê a Magia de inversão do Pacote Alvo em Silêncio: providers: [{ provide: BaseLogger, useClass: DataDogSDK }]
💡 O Porquê evitar:

O Dogma do Clean-Code impõe que Sua App é sagrada perante o Vendor (O Fornecedor das Libs Externas). Ao se trancar direto no Pacote "Datadog" o tempo todo, migrar sua Engine para Firebase futuramente demorará meses catando os cacos pra varrer cada string import! Delega O Provider central resolver e você será livre.

21 / 23

DIP - Exemplo 2 💻

Injection Tokens Contra Variáveis Fixas Environment

O que evitar? Atropelar as estruturas pedindo Imports literais de configurações soltas locais.

// ❌ ERRADO: Sujeira Hardcoded que barra e destrói o isolamento Virtual import { environment } from '../../../../envs/environment'; const urlServer = environment.API_PATH;
// ✅ CERTO: InjectionToken cria as Paredes Imutáveis Vitais do Framework export const API_URL = new InjectionToken<string>('API'); // 🌎 O Componente não esbarra importando Paths e Links Perigosos constructor(@Inject(API_URL) private apiUrl: string) {}
💡 O Porquê evitar:

Paths de Importação soltos geram dor física em Corporações com Repositórios Distribuídos Gigantes e Front-ends anabolizados pelo Micro-Frontends. Ao providenciar Inversão por Injection Tokens Genéricos o Engine permite virtualizar seus componentes base à quente sem encavalar caminhos de Build-Files.

22 / 23

DIP - Exemplo 3 💻

Ports & Adapters Architecture Base Providers

O que evitar? Subserviência direta entre Regra Custom Mestra do Cart e APIs SDK Pagadoras complexas.

// ❌ ERRADO: Dependência Caótica para Refatorar Módulos Mães import { PayPalLibSdk } from 'paypal-module'; constructor(private infra: PayPalLibSdk) {}
// ✅ CERTO: A Interface Gateway Limitante domina e inverte a Injeção Corrompida! export interface IPaymentPort { pay(a: number): Observable<boolean>; } // A Regra Exige somente acesso as funcoes limitadas, nada das propriedades do SDK Ext! constructor(@Inject(PAYMENT_GW) private py: IPaymentPort){}
💡 O Porquê evitar:

Garantia Base para Testes Unitários de alta velocidade em CIs Remotos Cloud! Ao puxar um `import` raiz de Lib de terceiros enorme no módulo, levantar o container Angular isolado Mockado pro Karma TestRunner irá engasgar fatalmente quebrando tempo vital tentando imitar a arquitetura do BackEnd Alheio!

23 / 23

Quando estamos começando, nosso foco é 100% em "fazer funcionar e subir logo para produção". E tudo bem!

Porém, conforme suas bases crescem, o diferencial de um Developer Pleno ou Senior não é "jogar o HTML pra Main". É implementar sólidas defesas limpas em que o seu "eu-do-futuro" sobreviva à manutenção sem reescrever pedaços inteiros de projeto dezenas de vezes.

O Segredo: Não saia enfiando regras arbitrárias e mágicas de forma cega. Aplique os fundamentos matemáticos e veja a raiz dos erros sumirem!

Gostou das 15 Lendas explicadas pra valer nos 💻 Códigos?

Qual Princípio de Arquitetura S.O.L.I.D te dá mais problemas no dia a dia da equipe onde atua?

Manda nos comentários! 👇