From e96a0c1fc7bc594fa714ef8f43c65f7dea6a421d Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Wed, 9 Apr 2025 11:44:21 -0700 Subject: [PATCH] docs: replace style guide with 2025 revision (#60809) See RFC https://github.com/angular/angular/discussions/58412 for background. PR Close #60809 --- .../src/content/best-practices/style-guide.md | 1078 ++++------------- 1 file changed, 250 insertions(+), 828 deletions(-) diff --git a/adev/src/content/best-practices/style-guide.md b/adev/src/content/best-practices/style-guide.md index 18489945799..1add59bdd6f 100644 --- a/adev/src/content/best-practices/style-guide.md +++ b/adev/src/content/best-practices/style-guide.md @@ -1,893 +1,315 @@ # Angular coding style guide -Looking for an opinionated guide to Angular syntax, conventions, and application structure? -Step right in. -This style guide presents preferred conventions and, as importantly, explains why. +## Introduction -## Style vocabulary +This guide covers a range of style conventions for Angular application code. These recommendations +are not required for Angular to work, but instead establish a set of coding practices that promote +consistency across the Angular ecosystem. A consistent set of practices makes it easier to share +code and move between projects. -Each guideline describes either a good or bad practice, and all have a consistent presentation. +This guide does _not_ cover TypeScript or general coding practices unrelated to Angular. For +TypeScript, check +out [Google's TypeScript style guide](https://google.github.io/styleguide/tsguide.html). -The wording of each guideline indicates how strong the recommendation is. +### When in doubt, prefer consistency -**Do** is one that should always be followed. -*Always* might be a bit too strong of a word. -Guidelines that literally should always be followed are extremely rare. -On the other hand, you need a really unusual case for breaking a *Do* guideline. - -**Consider** guidelines should generally be followed. -If you fully understand the meaning behind the guideline and have a good reason to deviate, then do so. -Aim to be consistent. - -**Avoid** indicates something you should almost never do. -Code examples to *avoid* have an unmistakable red header. - -**Why**?
-Gives reasons for following the previous recommendations. - -## File structure conventions - -Some code examples display a file that has one or more similarly named companion files. -For example, `hero.component.ts` and `hero.component.html`. - -The guideline uses the shortcut `hero.component.ts|html|css|spec` to represent those various files. -Using this shortcut makes this guide's file structures easier to read and more terse. - -## Single responsibility - -Apply the [*single responsibility principle (SRP)*](https://wikipedia.org/wiki/Single_responsibility_principle) to all components, services, and other symbols. -This helps make the application cleaner, easier to read and maintain, and more testable. - -### Rule of One - -#### Style 01-01 - -**Do** define one thing, such as a service or component, per file. - -**Consider** limiting files to 400 lines of code. - -**Why**?
-One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control. - -**Why**?
-One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies. - -**Why**?
-A single component can be the default export for its file which facilitates lazy loading with the router. - -The key is to make the code more reusable, easier to read, and less mistake-prone. - -The following *negative* example defines the `AppComponent`, bootstraps the app, -defines the `Hero` model object, and loads heroes from the server all in the same file. -*Don't do this*. - - - -It is a better practice to redistribute the component and its -supporting classes into their own, dedicated files. - - - - - - - - - - - -As the application grows, this rule becomes even more important. +Whenever you encounter a situation in which these rules contradict the style of a particular file, +prioritize maintaining consistency within a file. Mixing different style conventions in a single +file creates more confusion than diverging from the recommendations in this guide. ## Naming -Naming conventions are hugely important to maintainability and readability. -This guide recommends naming conventions for the file name and the symbol name. +### Separate words in file names with hyphens -### General Naming Guidelines +Separate words within a file name with hyphens (`-`). For example, a component named `UserProfile` +has a file name `user-profile.ts`. -#### Style 02-01 +### Use the same name for a file's tests with `.spec` at the end -**Do** use consistent names for all symbols. +For unit tests, end file names with `.spec.ts`. For example, the unit test file for +the `UserProfile` component has the file name `user-profile.spec.ts`. -**Do** follow a pattern that describes the symbol's feature then its type. -The recommended pattern is `feature.type.ts`. +### Match file names to the TypeScript identifier within -**Why**?
-Naming conventions help provide a consistent way to find content at a glance. -Consistency within the project is vital. -Consistency with a team is important. -Consistency across a company provides tremendous efficiency. +File names should generally describe the contents of the code in the file. When the file contains a +TypeScript class, the file name should reflect that class name. For example, a file containing a +component named `UserProfile` has the name `user-profile.ts`. -**Why**?
-The naming conventions should help find desired code faster and make it easier to understand. +If the file contains more than one primary namable identifier, choose a name that describes the +common theme to the code within. If the code in a file does not fit within a common theme or feature +area, consider breaking the code up into different files. Avoid overly generic file names +like `helpers.ts`, `utils.ts`, or `common.ts`. -**Why**?
-Names of folders and files should clearly convey their intent. -For example, `app/heroes/hero-list.component.ts` may contain a component that manages a list of heroes. +### Use the same file name for a component's TypeScript, template, and styles -### Separate file names with dots and dashes +Components typically consist of one TypeScript file, one template file, and one style file. These +files should share the same name with different file extensions. For example, a `UserProfile` +component can have the files `user-profile.ts`, `user-profile.html`, and `user-profile.css`. -#### Style 02-02 +If a component has more than one style file, append the name with additional words that describe the +styles specific to that file. For example, `UserProfile` might have style +files `user-profile-settings.css` and `user-profile-subscription.css`. -**Do** use dashes to separate words in the descriptive name. +## Project structure -**Do** use dots to separate the descriptive name from the type. +### All the application's code goes in a directory named `src` -**Do** use consistent type names for all components following a pattern that describes the component's feature then its type. -A recommended pattern is `feature.type.ts`. +All of your Angular UI code (TypeScript, HTML, and styles) should live inside a directory +named `src`. Code that's not related to UI, such as configuration files or scripts, should live +outside the `src` directory. -**Do** use conventional type names including `.service`, `.component`, `.pipe`, `.module`, and `.directive`. -Invent additional type names if you must but take care not to create too many. +This keeps the root application directory consistent between different Angular projects and creates +a clear separation between UI code and other code in your project. -**Why**?
-Type names provide a consistent way to quickly identify what is in the file. +### Bootstrap your application in a file named `main.ts` directly inside `src` -**Why**?
-Type names make it easy to find a specific file type using an editor or IDE's fuzzy search techniques. +The code to start up, or **bootstrap**, an Angular application should always live in a file +named `main.ts`. This represents the primary entry point to the application. -**Why**?
-Unabbreviated type names such as `.service` are descriptive and unambiguous. -Abbreviations such as `.srv`, `.svc`, and `.serv` can be confusing. +### Group closely related files together in the same directory -**Why**?
-Type names provide pattern matching for any automated tasks. +Angular components consist of a TypeScript file and, optionally, a template and one or more style +files. You should group these together in the same directory. -### Symbols and file names +Unit tests should live in the same directory as the code-under-test. Avoid collecting unrelated +tests into a single `tests` directory. -#### Style 02-03 +### Organize your project by feature areas -**Do** use consistent names for all assets named after what they represent. +Organize your project into subdirectories based on the features or your application or common themes +to the code in those directories. For example, the project structure for a movie theater site, +MovieReel, might look like this: -**Do** use upper camel case for class names. - -**Do** match the name of the symbol to the name of the file. - -**Do** append the symbol name with the conventional suffix \(such as `Component`, `Directive`, `Module`, `Pipe`, or `Service`\) for a thing of that type. - -**Do** give the filename the conventional suffix \(such as `.component.ts`, `.directive.ts`, `.module.ts`, `.pipe.ts`, or `.service.ts`\) for a file of that type. - -**Why**?
-Consistent conventions make it easy to quickly identify and reference assets of different types. - -| Symbol name | File name | -|:--- |:--- | -| @Component({ … })
export class AppComponent { }
| app.component.ts | -| @Component({ … })
export class HeroesComponent { }
| heroes.component.ts | -| @Component({ … })
export class HeroListComponent { }
| hero-list.component.ts | -| @Component({ … })
export class HeroDetailComponent { }
| hero-detail.component.ts | -| @Directive({ … })
export class ValidationDirective { }
| validation.directive.ts | -| @NgModule({ … })
export class AppModule
| app.module.ts | -| @Pipe({ name: 'initCaps' })
export class InitCapsPipe implements PipeTransform { }
| init-caps.pipe.ts | -| @Injectable()
export class UserProfileService { }
| user-profile.service.ts | - -### Service names - -#### Style 02-04 - -**Do** use consistent names for all services named after their feature. - -**Do** suffix a service class name with `Service`. -For example, something that gets data or heroes should be called a `DataService` or a `HeroService`. - -A few terms are unambiguously services. -They typically indicate agency by ending in "-er". -You may prefer to name a service that logs messages `Logger` rather than `LoggerService`. -Decide if this exception is agreeable in your project. -As always, strive for consistency. - -**Why**?
-Provides a consistent way to quickly identify and reference services. - -**Why**?
-Clear service names such as `Logger` do not require a suffix. - -**Why**?
-Service names such as `Credit` are nouns and require a suffix and should be named with a suffix when it is not obvious if it is a service or something else. - -| Symbol name | File name | -|:--- |:--- | -| @Injectable()
export class HeroDataService { }
| hero-data.service.ts | -| @Injectable()
export class CreditService { }
| credit.service.ts | -| @Injectable()
export class Logger { }
| logger.service.ts | - -### Bootstrapping - -#### Style 02-05 - -**Do** put bootstrapping and platform logic for the application in a file named `main.ts`. - -**Do** include error handling in the bootstrapping logic. - -**Avoid** putting application logic in `main.ts`. -Instead, consider placing it in a component or service. - -**Why**?
-Follows a consistent convention for the startup logic of an app. - -**Why**?
-Follows a familiar convention from other technology platforms. - - - -### Component selectors - -#### Style 05-02 - -**Do** use *dashed-case* or *kebab-case* for naming the element selectors of components. - -**Why**?
-Keeps the element names consistent with the specification for [Custom Elements](https://www.w3.org/TR/custom-elements). - - - - - - - - -### Component custom prefix - -#### Style 02-07 - -**Do** use a hyphenated, lowercase element selector value; for example, `admin-users`. - -**Do** use a prefix that identifies the feature area or the application itself. - -**Why**?
-Prevents element name collisions with components in other applications and with native HTML elements. - -**Why**?
-Makes it easier to promote and share the component in other applications. - -**Why**?
-Components are easy to identify in the DOM. - - - - - - - - - -### Directive selectors - -#### Style 02-06 - -**Do** Use lower camel case for naming the selectors of directives. - -**Why**?
-Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names. - -**Why**?
-The Angular HTML parser is case-sensitive and recognizes lower camel case. - -### Directive custom prefix - -#### Style 02-08 - -**Do** spell non-element selectors in lower camel case unless the selector is meant to match a native HTML attribute. - -**Don't** prefix a directive name with `ng` because that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose. - -**Why**?
-Prevents name collisions. - -**Why**?
-Directives are easily identified. - - - - - -### Pipe names - -#### Style 02-09 - -**Do** use consistent names for all pipes, named after their feature. -The pipe class name should use `UpperCamelCase` \(the general convention for class names\), and the corresponding `name` string should use *lowerCamelCase*. -The `name` string cannot use hyphens \("dash-case" or "kebab-case"\). - -**Why**?
-Provides a consistent way to quickly identify and reference pipes. - -| Symbol name | File name | -|:--- |:--- | -| @Pipe({ name: 'ellipsis' })
export class EllipsisPipe implements PipeTransform { }
| ellipsis.pipe.ts | -| @Pipe({ name: 'initCaps' })
export class InitCapsPipe implements PipeTransform { }
| init-caps.pipe.ts | - -### Unit test file names - -#### Style 02-10 - -**Do** name test specification files the same as the component they test. - -**Do** name test specification files with a suffix of `.spec`. - -**Why**?
-Provides a consistent way to quickly identify tests. - -**Why**?
-Provides pattern matching for [karma](https://karma-runner.github.io) or other test runners. - -| Test type | File names | -|:--- |:--- | -| Components | heroes.component.spec.ts
hero-list.component.spec.ts
hero-detail.component.spec.ts | -| Services | logger.service.spec.ts
hero.service.spec.ts
filter-text.service.spec.ts | -| Pipes | ellipsis.pipe.spec.ts
init-caps.pipe.spec.ts | - -## Application structure and NgModules - -Have a near-term view of implementation and a long-term vision. -Start small but keep in mind where the application is heading. - -All of the application's code goes in a folder named `src`. -All feature areas are in their own folder. - -All content is one asset per file. -Each component, service, and pipe is in its own file. -All third party vendor scripts are stored in another folder and not in the `src` folder. -Use the naming conventions for files in this guide. - -### Overall structural guidelines - -#### Style 04-06 - -**Do** start small but keep in mind where the application is heading down the road. - -**Do** have a near term view of implementation and a long term vision. - -**Do** put all of the application's code in a folder named `src`. - -**Consider** creating a folder for a component when it has multiple accompanying files \(`.ts`, `.html`, `.css`, and `.spec`\). - -**Why**?
-Helps keep the application structure small and easy to maintain in the early stages, while being easy to evolve as the application grows. - -**Why**?
-Components often have four files \(for example, `*.html`, `*.css`, `*.ts`, and `*.spec.ts`\) and can clutter a folder quickly. - -Here is a compliant folder and file structure: - -```markdown -project root -├── src -│ ├── app -│ │ ├── core -│ │ │ └── exception.service.ts|spec.ts -│ │ │ └── user-profile.service.ts|spec.ts -│ │ ├── heroes -│ │ │ ├── hero -│ │ │ │ └── hero.component.ts|html|css|spec.ts -│ │ │ ├── hero-list -│ │ │ │ └── hero-list.component.ts|html|css|spec.ts -│ │ │ ├── shared -│ │ │ │ └── hero-button.component.ts|html|css|spec.ts -│ │ │ │ └── hero.model.ts -│ │ │ │ └── hero.service.ts|spec.ts -│ │ │ └── heroes.component.ts|html|css|spec.ts -│ │ │ └── heroes.routes.ts -│ │ ├── shared -│ │ │ └── init-caps.pipe.ts|spec.ts -│ │ │ └── filter-text.component.ts|spec.ts -│ │ │ └── filter-text.service.ts|spec.ts -│ │ ├── villains -│ │ │ ├── villain -│ │ │ │ └── … -│ │ │ ├── villain-list -│ │ │ │ └── … -│ │ │ ├── shared -│ │ │ │ └── … -│ │ │ └── villains.component.ts|html|css|spec.ts -│ │ │ └── villains.module.ts -│ │ │ └── villains-routing.module.ts -│ │ └── app.component.ts|html|css|spec.ts -│ │ └── app.routes.ts -│ └── main.ts -│ └── index.html -│ └── … -└── node_modules/… -└── … +``` +src/ +├─ movie-reel/ +│ ├─ show-times/ +│ │ ├─ film-calendar/ +│ │ ├─ film-details/ +│ ├─ reserve-tickets/ +│ │ ├─ payment-info/ +│ │ ├─ purchase-confirmation/ ``` -HELPFUL: While components in dedicated folders are widely preferred, another option for small applications is to keep components flat \(not in a dedicated folder\). -This adds up to four files to the existing folder, but also reduces the folder nesting. -Whatever you choose, be consistent. +Avoid creating subdirectories based on the type of code that lives in those directories. For +example, avoid creating directories like `components`, `directives`, and `services`. -### *Folders-by-feature* structure +Avoid putting so many files into one directory that it becomes hard to read or navigate. As the +number files in a directory grows, consider splitting further into additional sub-directories. -#### Style 04-07 +### One concept per file -**Do** create folders named for the feature area they represent. +Prefer focusing source files on a single _concept_. For Angular classes specifically, this usually +means one component, directive, or service per file. However, it's okay if a file contains more than +one component or directive if your classes are relatively small and they tie together as part of a +single concept. -**Why**?
-A developer can locate the code and identify what each file represents at a glance. -The structure is as flat as it can be and there are no repetitive or redundant names. +When in doubt, go with the approach that leads to smaller files. -**Why**?
-Helps reduce the application from becoming cluttered through organizing the content. +## Dependency injection -**Why**?
-When there are a lot of files, for example 10+, locating them is easier with a consistent folder structure and more difficult in a flat structure. +### Prefer the `inject` function over constructor parameter injection -For more information, refer to [this folder and file structure example](#overall-structural-guidelines). +Prefer using the `inject` function over injecting constructor parameters. The `inject` function works the same way as constructor parameter injection, but offers several style advantages: -### App *root module* +* `inject` is generally more readable, especially when a class injects many dependencies. +* It's more syntactically straightforward to add comments to injected dependencies +* `inject` offers better type inference. +* When targeting ES2022+ with [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields), you can avoid separating field declaration and initialization when fields read on injected dependencies. -IMPORTANT: The following style guide recommendations are for applications based on `NgModule`. New applications should use standalone components, directives, and pipes instead. +[You can refactor existing code to `inject` with an automatic tool](reference/migrations/inject-function). -#### Style 04-08 +## Components and directives -**Do** create an NgModule in the application's root folder, for example, in `/src/app` if creating a `NgModule` based app. +### Choosing component selectors -**Why**?
-Every `NgModule` based application requires at least one root NgModule. +See +the [Components guide for details on choosing component selectors](guide/components/selectors#choosing-a-selector). -**Consider** naming the root module `app.module.ts`. +### Naming component and directive members -**Why**?
-Makes it easier to locate and identify the root module. +See the Components guide for details +on [naming input properties](guide/components/inputs#choosing-input-names) +and [naming output properties](guide/components/outputs#choosing-event-names). - +### Choosing directive selectors -### Feature modules +Directives should use the +same [application-specific prefix](guide/components/selectors#selector-prefixes) +as your components. -#### Style 04-09 +When using an attribute selector for a directive, use a camelCase attribute name. For example, if +your application is named "MovieReel" and you build a directive that adds a tooltip to an element, +you might use the selector `[mrTooltip]`. -**Do** create an NgModule for all distinct features in an application; for example, a `Heroes` feature. +### Group Angular-specific properties before methods -**Do** place the feature module in the same named folder as the feature area; for example, in `app/heroes`. +Components and directives should group Angular-specific properties together, typically near the top +of the class declaration. This includes injected dependencies, inputs, outputs, and queries. Define +these and other properties before the class's methods. -**Do** name the feature module file reflecting the name of the feature area and folder; for example, `app/heroes/heroes.module.ts`. +This practice makes it easier to find the class's template APIs and dependencies. -**Do** name the feature module symbol reflecting the name of the feature area, folder, and file; for example, `app/heroes/heroes.module.ts` defines `HeroesModule`. +### Keep components and directives focused on presentation -**Why**?
-A feature module can expose or hide its implementation from other modules. +Code inside your components and directives should generally relate to the UI shown on the page. For +code that makes sense on its own, decoupled from the UI, prefer refactoring to other files. For +example, you can factor form validation rules or data transformations into separate functions or +classes. -**Why**?
-A feature module identifies distinct sets of related components that comprise the feature area. +### Avoid overly complex logic in templates -**Why**?
-A feature module can easily be routed to both eagerly and lazily. +Angular templates are designed to +accommodate [JavaScript-like expressions](guide/templates/expression-syntax). +You should take advantage of these expressions to capture relatively straightforward logic directly +in template expressions. -**Why**?
-A feature module defines clear boundaries between specific functionality and other application features. +When the code in a template gets too complex, though, refactor logic into the TypeScript code ( +typically with a [computed](guide/signals#computed-signals)). -**Why**?
-A feature module helps clarify and make it easier to assign development responsibilities to different teams. +There's no one hard-and-fast rule that determines what constitutes "complex". Use your best +judgement. -**Why**?
-A feature module can easily be isolated for testing. +### Use `protected` on class members that are only used by a component's template -### Shared feature module +A component class's public members intrinsically define a public API that's accessible via +dependency injection and [queries](guide/components/queries). Prefer `protected` +access for any members that are meant to be read from the component's template. -#### Style 04-10 +```ts +@Component({ + ..., + template: `

{{ fullName() }}

`, +}) +export class UserProfile { + firstName = input(); + lastName = input(); -**Do** create a feature module named `SharedModule` in a `shared` folder; for example, `app/shared/shared.module.ts` defines `SharedModule`. - -**Do** declare components, directives, and pipes in a shared module when those items will be re-used and referenced by the components declared in other feature modules. - -**Consider** using the name SharedModule when the contents of a shared -module are referenced across the entire application. - -**Consider** *not* providing services in shared modules. -Services are usually singletons that are provided once for the entire application or in a particular feature module. -There are exceptions, however. -For example, in the sample code that follows, notice that the `SharedModule` provides `FilterTextService`. -This is acceptable here because the service is stateless;that is, the consumers of the service aren't impacted by new instances. - -**Do** import all modules required by the assets in the `SharedModule`; for example, `CommonModule` and `FormsModule`. - -**Why**?
-`SharedModule` will contain components, directives, and pipes that may need features from another common module; for example, `ngFor` in `CommonModule`. - -**Do** declare all components, directives, and pipes in the `SharedModule`. - -**Do** export all symbols from the `SharedModule` that other feature modules need to use. - -**Why**?
-`SharedModule` exists to make commonly used components, directives, and pipes available for use in the templates of components in many other modules. - -**Avoid** specifying app-wide singleton providers in a `SharedModule`. -Intentional singletons are OK. -Take care. - -**Why**?
-A lazy loaded feature module that imports that shared module will make its own copy of the service and likely have undesirable results. - -**Why**?
-You don't want each module to have its own separate instance of singleton services. -Yet there is a real danger of that happening if the `SharedModule` provides a service. - -```markdown -project root -├──src -├──├──app -├──├──├── shared -├──├──├──└── shared.module.ts -├──├──├──└── init-caps.pipe.ts|spec.ts -├──├──├──└── filter-text.component.ts|spec.ts -├──├──├──└── filter-text.service.ts|spec.ts -├──├──└── app.component.ts|html|css|spec.ts -├──├──└── app.module.ts -├──├──└── app-routing.module.ts -├──└── main.ts -├──└── index.html -└── … +// `fullName` is not part of the component's public API, but is used in the template. + protected fullName = computed(() => `${this.firstName()} ${this.lastName()}`); +} ``` - - - - - - - - +### Use `readonly` on properties that are initialized by Angular + +Mark component and directive properties initialized by Angular as `readonly`. This includes +properties initialized by `input`, `model`, `output`, and queries. The readonly access modifier +ensures that the value set by Angular is not overwritten. + +```ts +@Component({/* ... */}) +export class UserProfile { + readonly userId = input(); + readonly userSaved = output(); +} +``` + +For components and directives that use the decorator-based `@Input`, `@Output`, and query APIs, this +advice applies to output properties and queries, but not input properties. + +```ts +@Component({/* ... */}) +export class UserProfile { + @Output() readonly userSaved = new EventEmitter(); + @ViewChildren(PaymentMethod) readonly paymentMethods?: QueryList; +} +``` + +### Prefer `class` and `style` over `ngClass` and `ngStyle` + +Prefer `class` and `style` bindings over using the `NgClass` and `NgStyle` directives. + +```html + +
+ + +
+``` + +Both `class` and `style` bindings use a more straightforward syntax that aligns closely with +standard HTML attributes. This makes your templates easier to read and understand, especially for +developers familiar with basic HTML. + +Additionally, the `NgClass` and `NgStyle` directives incur an additional performance cost compared +to the built-in `class` and `style` binding syntax. + +### Name event handlers for what they _do_, not for the triggering event + +Prefer naming event handlers for the action they perform rather than for the triggering event: + +```html + + + + + +``` + +Using meaningful names like this makes it easier to tell what an event does from reading the +template. + +For keyboard events, you can use Angular's key event modifiers with specific handler names: + +```html +