Standards have a positive impact on any business – and software development is no exception. An organisation-wide internal style guide for writing code should be accessible to every development team before starting a project.
Coding standards or coding guidelines make sure that all developers working on a project use certain guidelines for coding. It is important to have those standards as developers read code more than they write. Coding guidelines help to make reading code more straightforward. They also make debugging easier as code based on coding guidelines is more homogeneous and consistent.
Writing coding guidelines for a new system is even more crucial as documentation often doesn’t include coding practices that are suitable for code development based on the new system. When guidelines are defined in a document, rules don’t need to be explained to each developer individually, which saves time and effort. It is also essential for onboarding new developers.
I experienced it first hand while working on a commercial project based on Spartacus. Spartacus is a lean, Angular-based JavaScript storefront for SAP Commerce Cloud. Spartacus talks to SAP Commerce Cloud exclusively through the Commerce REST API. Being an open source project, I managed to learn Spartacus and use it for a mid-large scale project with a team of 3 developers, mostly benefiting from Spartacus’ documentation and developer community on Slack and Stackoverflow.
After writing Angular apps since its early versions, I’ve developed my own style, which is strongly influenced by the Angular styleguide, best practices shared by leaders, and simply experimenting with small personal projects.
While working on the Spartacus-based project, I realised that even though our team did implement the Angular style guide, there was no overarching coding standard that covered both coding language (Angular) and the system (Spartacus). Therefore, I believe there is a need for a coding standard that covers both Angular and Spartacus to ensure consistency is maintained. The below Spartacus coding style guide shows best practice examples, common mistakes and tips and tricks to read and write Spartacus code successfully.
Document Vocabulary
Each coding guideline describes either good or poor coding practice. The wording of each guideline indicates how strong the recommendation is. Before starting the coding process, every developer must ensure they read and follow the below.
Do Guidelines should always be followed. These guidelines form the basis for Spartacus development and need to be implemented. It would take an unusual case to break a Do Guideline. In those cases, a senior consultant should be asked. Green boxes indicate DoGuidelines.
Consider Guidelines should generally be followed. If there is a good reason to deviate, then the guideline can be overlooked - as long as consistency is kept. Blue boxes indicate Consider Guidelines.
Don't Guidelines define code standards that should be avoided. Red boxes indicate Don't Guidelines.
Project Structure
Folder Structure
Do
Try keeping the same component structure as Spartacus’ codebase. However, if you are customising, use “custom” suffix before naming the component or service.
Why? Having two classes with the same name can cause confusion during run time. For example,
ActiveCartService
is a Spartacus service. If you need to customise it, create a service called
customcAtiveCartService
and extend the ActiveCard service in your newly created custom service.
export class CustomActiveCartService extends ActiveCartService {…}
Consider
Create the following folders in the codebase to make sure that our Spartacus projects are consistent. The folder structure for components should follow Spartacus’ folder structure to make sure files are created with the same hierarchy. This will help to find the relative Spartacus code more easily.
App
Components
Shared
Data-models
Enumerators
Interfaces
Models
Services
Shared
Guards
Custom-routes
Pipes
Assets
Config
Icons
Fonts
Translate
Styles
Shared
Components
Data Structure
Do
1. Create interfaces only if you are extending Spartacus’ existing interfaces. Add
ICustom
prefix to the original name of the interface. Below is an example of extending an existing interface.
2. All new properties should be optional to make sure it is flexible enough to be shared in different contexts or components.
The above example extends the ‘Adress’ interface and adds two optional properties.
Consider
1. Use classes instead of interface if you introduce a custom component specific data model. Classes are object factories and can be used to create objects and initialise properties, whereas interface defines the objects' type and properties.
2. Use strong types based on the custom interface for Application Programming Interface (API) purposes to ensure UI follows the new custom interface. Otherwise UI objects might only refer to the original properties in the interface and not to the newly added properties.
Recommended Development Environment
CLI Tools
Your Angular development environment should include the latest version of the following tools:
In order to make sure that all team members have the same experience, create
.vscode
folder at the start of your project. In your folder create a
settings.json
file. Settings added here are known as
workspace
settings. Here are some recommended workspace settings that also help to avoid collision with the recommended code-formatter:
{
// Prettier uses single quotes.
"prettier.singleQuote": true,
// Prettify on save
"editor.formatOnSave": true,
// prettier uses 2 spaces instead of 4
"editor.tabSize": 2,
// organize imports on save
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
// Uses the project specific typescript version.
"typescript.tsdk": "node_modules/typescript/lib"
}
Don’t accept support for any browser or device that is not supported in the browser compatibility matrix as it introduces the risk of unresolvable bugs.
Customising Routes
When a new component is added to Spartacus, routes should be defined in a separate module that contains routing specifications.
Do
Create a folder named custom-routes and create the module name
1. Use a separate variable for static routes (if it is necessary to use them at all). Refer to the STATIC_ROUTES variable example above.
2. Create an enumerated data type (enum) for custom route names to make sure you can address names properly in the code.
Don’t
Don’t use static routes when using routingService. Below you can see an example of using redirect poorly.
Lazy loading lets you divide your JavaScript code into multiple chunks. Only deliver related chunks. A chunk is a resource file that is downloaded by the browser when a user views the web page.
Do
Use lazy loading modules as much as possible as it increases the speed of render and makes app function better when it gets bigger. Below is an example of a good lazy loading practice.
Create a module according to Spartacus’ codebase. Usually, every component is associated with a module. However, in some cases there might be more than one component attached to a module.
Don’t
Don’t create a feature module and import all modules in it as it goes against lazy loading.
Don’t add a component directly to the app module.
Documentation
Use the compdoc package for dynamic documentation purposes. The documentation covers the structure of the application, data models and modular design.
Use the below command to create the document before every release.
compodoc -p tsconfig.json -s
Consider
Leaving comments in code whenever you customise a Spartacus component.
import { Component, OnInit } from '@angular/core';
import { CmsProductReferencesComponent, ProductReferenceService } from '@spartacus/core';
import { BREAKPOINT, BreakpointService, CmsComponentData, CurrentProductService, ProductReferencesComponent } from '@spartacus/storefront';
import {UserPermissionService} from '../../../services/user-permission/user-permission.service';
/**
* This is CustomProductReferencesComponent
*
* This component extends ProductReferencesComponent
*/
@Component({
selector: 'app-custom-product-references',
templateUrl: './custom-product-references.component.html',
styleUrls: ['./custom-product-references.component.scss'],
})
export class CustomProductReferencesComponent
extends ProductReferencesComponent
implements OnInit {
/**
* ProductReferencesComponent dependencies: cmsComponentData, currentProductService, productReferenceService
*/
constructor(
protected cmsComponentData: CmsComponentData,
protected currentProductService: CurrentProductService,
protected productReferenceService: ProductReferenceService,
private breakpointService: BreakpointService,
public userPermissionService: UserPermissionService
) {
/**
* injecting services
*/
super(cmsComponentData, currentProductService, productReferenceService);
}
/**
* Defines width of a carousel item
*/
public carouselWidth: string = '25%';
ngOnInit(): void {
/**
* Set number of carousel items in desktop, tablet and mobile view
*/
this.breakpointService.breakpoint$.pipe().subscribe((pageSize) => {
if (pageSize === BREAKPOINT.lg || pageSize === BREAKPOINT.xl) {
this.carouselWidth = '285px';
}
if (pageSize === BREAKPOINT.md) {
this.carouselWidth = '285px';
}
if (pageSize === BREAKPOINT.xs) {
this.carouselWidth = '300px';
}
});
}
}
Don’t
Avoid leaving comments that are not specific. Comments should clearly specify the intention of variables, methods, services etc.