Styleguide
Use @benjavicente/lint-angular rules to lint your code following this styleguide.
Principles
- Prefer statically analysable code.
- In the component tree, derive state downward with signals and propagate changes upward with events.
- Use framework-agnostic best practices and tools.
Naming
- Component names:
{scope}-{component-name}(kebab-case) - Directive names:
{scope}{DirectiveName}(camelCase) - If a file exports a single component, directive, or service:
{name}.{component|directive|service}.ts - Template:
{name}.{component}.html - Allow only a single style file:
{name}.{component}.css - Class name:
Name{Component|Directive|Service} - Tests:
{name}.{component|directive|service}.spec.ts
Classes
- Do not use inheritance for anything UI-related.
- Prefer
#for attributes and methods,protectedwhen template access is needed, andpublicin the rare cases that need external access. - Use this order:
inject, inputs, outputs, and template queries before other attributes and methods. - Place Angular lifecycle hooks (
ngOnInit,ngOnDestroy, etc.) before other methods, in lifecycle order. - Use
readonlywhen possible. - Avoid decorators when possible. Only use class decorators.
- Do not pass whole component, directive, or service instances to helpers.
Reactivity
- Avoid
effectwhen possible. - Do not use effects when the logic can be a
computed. - Do not use effects to handle event logic; handle it in the event handler itself.
- Do not set signals in an effect. Use linked signals when needed.
- Each effect should sync out a single concern.
- Represent state as synchronous values with signals.
- Use observables only for asynchronous sequences.
- Convert one-off async observables with
firstValueFrom, such as HTTP client calls. - Group and compose state in utility functions when it helps clarity.
- Side effects that depend on the DOM should use
afterRenderEffect. - Use a dedicated server state management library, such as TanStack Query.
- Avoid Angular’s resource API.
inject and injection context (rules of inject)
- Functions that require the injection context should be prefixed with
inject. - Functions prefixed with
injectmust not be called outside an injection context. - A function
injectFnprefixed withinjectcan callassertInInjectionContext(injectFn)to verify the injection context. - Avoid manually passing the injection context at all costs, either as an argument or through
runInInjectionContext.
Components and directives
- Components should be dumb. Use services for logic and global state, and
injectXfunctions to compose behavior. - Prefer
input.required(),input.required({ transform: (value) => value }), andinput(default)when applicable. - Make it clear which component initializes and loads data to avoid unnecessary request cascades.
- Pass data down with inputs. When data comes from far above, inject the service or component that holds that state to avoid prop drilling.
- Avoid the
constructorwhen possible; use@angular/core/rxjs-interopfor external observables and lifecycle hooks instead.
Services
- Services should avoid holding state.
- Avoid non-global services. Prefer
providedIn: 'root'when possible. - Do not manage lifecycle state in services. Services should avoid lifecycle methods such as
initand must not depend on component inputs. - Services can be “connected” to the component lifecycle with
injectXmethods that read the component’s injection context. - Use the
provideXpattern to provide services and values. - Use
provideEnvironmentInitializer(() => inject(Service))to eagerly construct global services. - If service initialization depends on async values, do not leak that requirement to callers.
- When needed, use a private service that hides initialization details and a public one that injects the private service.
- Do not use
Module.forRootorModule.forFeature.
Templates
- Avoid inline templates when the template is longer than 10 lines.
- Avoid the
asyncpipe; prefer mapping values to signals. - Use modern syntax:
@if,@for,[class], and[style]. - Use
[style.{property}]and[class.{className}]for dynamic styles and classes. - Use
@letto declare auxiliary variables inside@ifand@for. - Attach attributes to the host element with
@Component({ host: { "value": 'value()', "(click)": 'onClick()' } }). Do not use@HostBindingor@HostListener. - Remember that host elements affect layout. Use
display: contentsto opt out, especially for non-standard HTML elements.
Routing
- Prefer loading all routes up front and lazy-loading only components.
- Prefer
relativeToover absolute paths. - Avoid Angular Router route resolvers for data loading; they do not offer fine-grained control.
Testing for components and directives
- Test user interactions in components, not internal logic.
- Never test private methods.
- Avoid test IDs. Use accessibility attributes instead.
- Use Testing Library.
Testing for services and utilities
- If a function takes a signal as an argument, test it with an input signal.
- If a function does not depend on a signal, do not test it through a component.
- Functions prefixed with
injectshould run insideTestBed.runInInjectionContext.