Coding guidelines
Use Angular CLI
This command line interface helps you scaffold your code in minutes. It references components into their own modules and complies with naming conventions as well.
Refer: Angular CLI
Folder structure
Split your app into core, shared and multiple feature modules.
- [ project root ]
- |--- src
- |--- app
- |--- core
- |--- [+] guard
- |--- [+] interceptor
- |--- [+] service
- |--- [+] model
- |--- shared
- |--- component
- |--- [+]spinner
- |--- services
- |--- validation.service.ts|spec.ts
- |--- [+] pipes
- |--- [+] directives
- |--- shared.module.ts
- |--- layout
- |--- [+] auth-layout
- |--- [+] content-layout
- |--- [+] footer
- |--- [+] header
- |-- user-profile
- |--- [+] edit-profile
- |--- [+] view-profile
- |--- user-profile.component.ts|html|scss|spec.ts
- |--- user-profile.module.ts
- |--- user-profile-routing.module.ts
- |--- app.component.ts|html|scss|spec.ts
- |--- app.module.ts
- |--- app-routing.module.ts
- |--- main.ts
- |--- [+] environments
Core module
This module is for classes used by app.module. Resources which are always loaded such as route guards, HTTP interceptors, and application level services, such as the AuthenticationService and logging belongs to this directory.
Shared module
Shared module can have components, directives and pipes that will be shared across multiple modules and components, but not the entire app necessarily.
We already have a Angular boilerplate that follows the aforementioned rules and can be used for new projects.
Naming Conventions
Type | Syntax | Example |
---|---|---|
File Name | Dashed Case/Kebab Case feature(.component/.pipe/.module/.directive).ts | user-list.component.ts, logger.service.ts |
Class Name | Upper Camel Case/Pascal Case | TrainingListComponent, ValidationDirective |
Component Selector Name | Dashed Case/Kebab Case | 'admin-users', 'fd-title-bar' (fd stands for FEED Docs) |
Single Responsibility
Redistribute the component and its supporting classes into their own, dedicated files. A class should have only one job.
Lazy load feature module
A feature module shouldn’t be loaded initially, but when only you decide to initiate it. Thereby making your Angular app load faster. Here is an example on how to initiate a lazy loaded feature module via app-routing.module.ts file.
const routes: Routes = [
{
path: 'dashboard',
loadChildren: 'app/dashboard/dashboard.module#DashboardModule',
component: CoreComponent,
},
];
Use tree-shakable providers
@Injectable({
providedIn: 'root'
})
Please note that root can be replaced with any module where you wish to provide your service with.
Create aliase for imports
Use aliases for imports that are several levels deep.
import {LoaderService} from '../../../loader/loader.service';
This can be configured in tsconfig.json file.
{
"compileOnSave": false,
"compilerOptions": {
removed for brevity,
"paths": {
"@app/*": ["app/*"],
"@env/*": ["environments/*"]
}
}
}
And resulting code will look like:
import { LoaderService } from '@app/loader/loader.service';
import { environment } from '@env/environment’;
Use interfaces
Use interface for data models. Interfaces are completely removed during compilation and so they will not add any unnecessary bloat to your final code.
Set up ENV variables
Use environment files for storing app keys/secrets. Create an example environment file with placeholders to provide information about fields required and push it to source control . Its also good practice to maintain separate environment files for develop, staging, and prod environments.
Do not check in the actual environment file to source control and create a security vulnerability!
Use directives
Use directives when same behavior is repeated for HTML elements. Example - hover animations.
Use trackby*
When using ngFor to loop over an array in templates, use it with a trackByfunction which will return an unique identifier for each item. When an array changes, Angular re-renders the whole DOM tree. But if you use trackBy, Angular will know which element has changed and will only make DOM changes for that particular element.
Prevent null exceptions
Use the safe navigation operator while accessing an object's property from a template. This helps in avoiding exceptions.
<div class="col-md-3">
{{user?.name}}
</div>
Preserve immutability to avoid suprises
The easiest way to avoid messing up objects and arrays unintentionally is by using the ES6 spread operator (...).
Original object
this.user = {
name: 'Dzon',
age: 25,
addr
Cloned object
let updatedUser = {
...this.user,
name: 'Peter',
};
Prevent memory leaks
- When subscribing to observables, always make sure you unsubscribe from them appropriately by using operators like take, takeUntil, etc.
- Make use of async pipe wherever applicable.
Avoid logic in template
Avoid complex logic in templates as it is harder to debug and less maintainable. Extract complex logic to functions in the component file instead.
Keep it tidy
- Limit files to 400 Lines of code.
- Try to be DRY.
- Define small functions (no more than 75 lines).
Member sequence
- Place properties up top followed by methods.
- Place private members after public members, alphabetized.
Cheetsheet
Description | Avoid | Recommended Usage |
---|---|---|
Component selectors | selector: 'fdTitleBar' | selector: 'fd-title-bar' |
Component custom prefix | selector: 'title' | selector: 'fd-title' |
Directive selectors | selector: '[validate]' | selector: '[fdValidate]' |
Pipe names (init-caps.pipe.ts) | @Pipe({ name: 'init-caps' }) | @Pipe({ name: 'initCaps' }) |
Components as elements | <div fdTitleBar></div> | <fd-title-bar></fd-title-bar> |
Decorate input and output properties | inputs: ['label'] | @Input() label: string; |
outputs: ['titleChange'] | @Output() titleChange = new EventEmitter<any>(); | |
Dont prefix output properties | @Output() onSavedTheDay = new EventEmitter<boolean>(); | @Output() savedTheDay = new EventEmitter<boolean>(); |
Put presentation logic in the component class | Average power: {{totalPowers / items.length}} | Average power: {{avgPower}} In Component, get avgPower() { return this.totalPowers / this.items.length;} |
Use the @Injectable() class decorator | @Inject(HttpClient) private http: HttpClient | @Injectable() export class HeroArena {constructor(private http: HttpClient) {}} |