The purpose of this Guidelines is to offer a quick overview of the best practice when working on an Angular 2+ project.
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand. " [Martin Fowler]
Most of the following text cames from the official angular styleguide. In summing up the styleguide I removed the rules that are normally already reported by TsLint (the typescript linter).
- Limit logic in a component to only that required for the view. All other logic should be delegated to services.
- Put presentation logic in the component class, and not in the template.
- Extract templates and styles into a separate file, when more than 3 lines.
- Use the @Input() and @Output() class decorators instead of the inputs and outputs properties of the @Directive and @Component metadata.
- Avoid input and output aliases (ie @Input('labelAttribute') label: string;) except when it serves an important purpose.
- Learn and use the concept of stateful and statless components
- Try to use ChangeDetectionStrategy.OnPush strategy throughout your application, you will ideally only ever run the change detector on components that have actually changed (and their direct ancestors). This reduces the time complexity of the change detector from O(n) to O(log n) in the number of component instances in your application.
- Use attribute directives when you have presentation logic without a template.
- Prefer the @HostListener and @HostBinding to the host property of the @Directive and @Component decorators.
- Use services as singletons within the same injector. Use them for sharing data and functionality.
- Create services with a single responsibility that is encapsulated by its context. When a service has multiple responsibilities, every component or service that injects it now carries the weight of them all.
- Provide services to the Angular injector at the top-most component where they will be shared. When providing the service to a top level component, that instance is shared and available to all child components of that top level component.
- Format: feature.type.extension
- Example: user.component|service|model|component|d|pipe|module|directive.ts
- Add .spec for unit test files and .e2e for e2e tests, i.e. user.component.spec.ts
- Files and Folders name use kebab-case i.e user-manager/user-manager.service.ts
Follow Lift principles:
- Locate: Keeping related files near each other in an intuitive location saves time. A descriptive folder structure makes a world of difference to you and the people who come after you.
- Identify: Name the file such that you instantly know what it contains and represents.
- Flat: Keep a flat folder structure as long as possible. Creating sub-folders when a folder reaches seven or more files.
- Try to be DRY: Don't Repeat Yourself
- Do create folders named for the feature area they represent. Create an NgModule for all distinct features in an application.
- Create a folder named "core" and put there your services containing business logic (for example API calls). Only components can contain reference of core module, but not viceversa. This promotes re-usability. Also create and ngModule for it. Only the root AppModule should import the CoreModule. This approach offers many advantages:
- You can easily create a package sharable by different applications.
- Often you have different components (in different feature folders) that need to comunicate with the same service.
- Promote separation between UI and business logic layer.
- Create a folder named "shared" and put there 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. Also create and ngModule for it.
- Put the contents of lazy loaded features in a lazy loaded folder. A typical lazy loaded folder contains a routing component, its child components, and their related assets and modules.
- Use Tslint, is your friend. You can find a copy of a configured tslint.json in the root of this project. Copy and paste it in the root of your project. Remember to edit "directive-selector" and "component-selector" rules to match the initial names of your project.
- In Tslint use and follow codelyzer rules.
- Use GIT pre-commit hooks for run linting before commits, to maintain code style consistence across the team. To achieve this result use husky.
- SASS/SCSS Guidelines.
In the following section will be discussed only lint rules that normally Tslint could not catch. If you need info on specific rules that are not present here, give a look to the tslint documentation.
- Interfaces: Name an interface using upper camel case. Consider naming an interface without an I prefix. Consider using a class instead of an interface to avoid this kind of problem.
- Name events without the prefix on. Name event handler methods with the prefix on followed by the event name. I.E. <toh-hero (savedTheDay)="onSavedTheDay($event)">
Angular styleguide suggests to:
- Name constants in lowercase
- Avoid prefixing private methods with an underscare
I think that those are opinable choices. If you want in future re-use you code in an application in vanilla Javascript or viceversa use vanilla code in your app, you'll have to spend time to re-factor your app.
As the application grows, how do we know that a state change in one module will consistently and accurately reflected in other modules? And what if these modifications result in even more state changes? Eventually, it becomes extremely difficult to reason about what's actually happening in your application, and be a large source of bugs. 3 Ways to solve this problem:
- NGRX: RxJS powered state management for Angular applications, inspired by Redux. When you should consider NGRX:
- Complex application
- Large Team
- Changing requirements
- Mobx: MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP). When you should consider Mobx:
- Simpler application
- Rapid prototyping
- Small team
- Angular Services and RxJS
- Error logging: Sentry
- Database: PouchDB. Don't use localstorage as it can be deleted by OS to free memory.
- Time and Dates: Date-fns should be more performant than MomentJs
- Immutable-js - Immutable Data Collections including Sequence, Range, Repeat, Map, OrderedMap, Set and a sparse Vector.
- Lodash-es - A utility library delivering consistency, customization, performance, & extras.
- If working in a team, consider using git flow.
- Consider using Commitizen with cz-conventional-changelog to format properly every git commit and validate commit msg to ensure validation.
- Consider using standard version for automatic changelog based on your commits.
- Set code style for typescript:
- {import} -> { import }
- import * from "lodash" -> import * from 'lodash'
- Set typescript settings to be used with the version inside node_modules instead of the bundled one
- Don't activate typescript compiler.
- Enable tslint in settings
- Download scss lint plugin and enable it
- Official angular styleguide.
- How does angular 2 change detection really work
- Why changedetection.OnPush is so important
- Angular 2 best practices
- Pro tips for angular cli
- Avoid switch and lots of if else statements by using object literals
- Use flow instead of chain in lodash
- Avoid the use of for statements
Copyright (c) 2017 Marco Turi
Source code is open source and released under the MIT license.