Angular shortens development time for applications, reducing the time it takes to deliver custom software. Improving your app’s performance is vital to staying competitive and profitable. Here are 30 Angular tips.
Time and time again companies have trusted us as a software development provider. Read more about some of our projects and find out why.
- Define form parameters before Component definition. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
export const FORM_PARAMS = { status: 'status', classification: 'classification', note: 'note' }; @Component({ selector: 'app-preview', templateUrl: './preview.component.html', styleUrls: ['./preview.component.scss'] }) this.form = this.formBuilder.group({ [FORM_PARAMS.status]: [false], [FORM_PARAMS.classification]: [null], [FORM_PARAMS.note]: [null, Validators.required] }); this.form.patchValue({ [FORM_PARAMS.status]: 'status' });
[/dm_code_snippet]
- Use renderer addClass, removeClass where possible. It will prevent too many change detection checks. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
// SLOWER
collapseRow(rowIndex: number) { this.setRowCollapsed(rowIndex); } // FASTER
collapseRow(event, rowIndex: number) { this.setRowCollapsed(rowIndex); this.render.removeClass(event.target, 'arrowDown'); this.render.addClass(event.target, 'arrowRight'); }
[/dm_code_snippet]
- Try to use get, set on Input() instead ngOnChanges. When you have many Inputs in the ngOnChanges, each “if” has to be checked. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
// SLOWER when many ifs ngOnChanges(changes: SimpleChanges) { const data: SimpleChange = changes.data; if (data && data.currentValue) { this.data = [...data.currentValue]; } if (configuration && configuration.currentValue) { this.config = configuration.currentValue; } } // FASTER public _config: Config; @Input('configuration') set configuration(value: Config) { this._config = value; } get configuration(): Config { return this._config; } _data: Data; @Input('data') set data(value: Data) { this._data = [...value]; } get data(): any[] { return this._data; }
[/dm_code_snippet]
- Use pipe when rendering content in ngFor. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
// Slower {{ render(row, column) }} render(row: any, column: string) { return YourService.render(row, column); } // Faster {{ row | render:column }} @Pipe({ name: 'render', }) export class RenderPipe implements PipeTransform { transform(row: any, column: string) { return YourService.render(row, column); } }
[/dm_code_snippet]
- Specify baseUrl and module aliases (paths) to your compilerOptions to avoid any inconsistency when importing other files. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
{ "compilerOptions": { "baseUrl": "src", "paths": { "@core/*": ["app/*"], "@assets/*": ["assets/*"] } } } import { Recruitment } from '@core/domain/recruitment'; instead import { Recruitment } from '../../../domain/recruitment';
[/dm_code_snippet]
- Add stylePreprocessorOptions to your angular.json file to avoid inconsistency while importing other files. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
"projects": { "project-frontend": { "root": "", "sourceRoot": "src", "projectType": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "stylePreprocessorOptions": { // <--- add this "includePaths": [ "./src/assets/style/scss" ] } } } } } @import "variables"; instead @import "../../assets/style/scss/variables";
[/dm_code_snippet]
- Run npm outdated command or add npm-check once a month to keep your dependencies updated. It will definitely help you keep track of changes. It’s much easier to update Angular 5 to 6 than 4 to 6.
- Run npm audit command once a month to check if any of the libraries has any vulnerabilities. It will help you keep your app secure. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
if [[ $(npm audit | grep Critical -B3 -A10) != '' ]]; then exit 1; fi"
[/dm_code_snippet]
- Use parent in form validation instead of this.form which may not be initialised while doing/performing custom validation check. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
// Correct static validateEndDate(fc: FormControl) { const startDate = fc.parent.get(FORM_PARAMS.startDate); if (startDate.value) { const diff = fc.value - startDate.value; return (diff < 86400) ? { endDateInvalid: true } : null; } return null; } // Incorrect static validateEndDate(fc: FormControl) { const startDate = this.form.get(FORM_PARAMS.startDate); if (startDate.value) { const diff = fc.value - startDate.value; return (diff < 86400) ? { endDateInvalid: true } : null; } return null; }
[/dm_code_snippet]
- Keep route names as const. It will prevent accidental typos. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
export class ROUTE { public static readonly LOGIN = '/login'; public static readonly RECRUITMENTS = '/recruitments'; public static readonly RECRUITMENT_EDIT = '/recruitments/:id'; } goToRecruitment($event, id: number) { $event.preventDefault(); this.router.navigate([ROUTE.RECRUITMENT_EDIT.replace(':id', id)]); }
[/dm_code_snippet]
- Start using webpack-bundle-analyzer. It will help you detect any fast-growing modules.In our case by mistake main.scss has been included in another file instead variable.scss. It has doubled the size of the bundle! [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
"scripts": { "bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json" }, 93f72404-b338-11e6-92d4-9a365550a701.gif Gif Source: https://github.com/webpack-contrib/webpack-bundle-analyzer
[/dm_code_snippet]
- Use browser Performance tests. 17ms rendering time means you use 60fps. Actions with fewer than 60fps and more than 30fps are ok. Any speed below 30fps makes the user notice the visualized slowdown of the application.
- Use the Augury chrome extension to track the current state of components.
- Prettier as code formaterTo prevent conflicts between tslint and prettier use the npm tool. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
// .prettierrc { "printWidth": 140, "parser": "typescript", "tabWidth": 2, "singleQuote": true, "trailingComma": "all" }
[/dm_code_snippet]
- Use trackBy in ngFor loops to optimize the re-rendering of iterable.trackByFn is a function that defines how to track changes for items in the iterable. When items are added, moved or removed in the iterable, only those nodes that have changed are re-rendered.
Example: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]@Component({ template: `
- {{item.id}}
`, }) export class SampleComponent { constructor() { this.items = [ {id: 1}, {id: 2}, {id: 3} ];
[/dm_code_snippet]
- Use a virtualized scroll list like e.g. CDK Virtual Scroll when you need to display a very large records collection. It will render just items that fit within ViewPort in the current scroll position, in comparison to rendering all items at once without virtualization.
[dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]import { ScrollingModule } from '@angular/cdk/scrolling'; @NgModule({ ..., imports: [ ..., ScrollingModule ], ..., }) export class AppModule { } @Component({ template: `
- {{item.id}}
`, }) export class SampleComponent { constructor() { for (let index = 0; index < 10000; index++) { this.items.push({ id: index }); } } trackByFn(index, item) { return item.id; } }
[/dm_code_snippet]
- From Angular v9+, use ngZoneEventCoalescing flag to reduce the amount of change detection cycles while Event Bubbling.
[dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]platformBrowserDynamic() .bootstrapModule(AppModule, { ngZoneEventCoalescing: true }) .catch(err => console.error(err));
[/dm_code_snippet]
Example: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]platformBrowserDynamic() .bootstrapModule(AppModule, { ngZoneEventCoalescing: true }) .catch(err => console.error(err));
[/dm_code_snippet]
Normally when we click on the button element, both “childrenFn” and “parentFn” are invoked, causing two change detection cycles. With ngZoneEventCoalescing flag set to true, there is only one change detection cycle to run.
- Debugging Angular component in console
- Open browser developer tools.
- Select element in “Elements” tab
- Debug this element in console: ($0 – in this variable is selected element)
Before Angular v9:[dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
const selectedComponent = ng.probe($0).componentInstance; selectedComponent.value = "New value"; ng.probe($0).injector.get(ng.coreTokens.ApplicationRef).tick();
[/dm_code_snippet]
From Angular v9+:
[dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]const selectedComponent = ng.getComponent($0); selectedComponent.value = "New value"; ng.applyChanges(myComponent);
[/dm_code_snippet]
From Angular v9+ it is even better to use this SB Angular Inspector.
- Lazy loading in Angular – instead of loading a complete app, Angular loads only the modules which are required at the moment. It reduces the initial load time.
- Always keep an eye on your Bundle Size. Depends on the scale of your project, in enterprise apps if your main.*.js file exceeds more than 0.5 MB, you may need to be aware. After your Angular app is deployed check if your cloud platform or CDN, hosts your bundles gzipped. In the network tab in the developer console in Response Headers, you should see the header: “Content-Encoding: gzip.” If you have your own server serving your app and it doesn’t implement gzip compression, you should definitely add those.
- While you don’t need to validate or react on every single change of your FormControl, use updateOn: ‘blur’. Event callbacks will be executed on blur events. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
this.email = new FormControl(null, { updateOn: 'blur' });
[/dm_code_snippet]
- Use Services for any Side Effects.It is a good place for any HTTP requests, event handlers, time-based events. It reduces the complexity of the component and provides reusability to our codebase. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
@Injectable({ providedIn: 'root' }) export class UsersService { constructor (private http: HttpClient) {} getUsers() { return this.http.get(API_ENDPOINT); } } @Component({ selector: 'app-component', template: '
- {{item}}
', }) export class AppComponent implements OnInit{ constructor(private usersService: UsersService){ } items = []; ngOnInit(){ this.usersService.getUsers().subscribe(items => this.items = items); } }
[/dm_code_snippet]
- Use the Angular CLI tool that makes it easy to create your application. With just one command you can generate whatever you want, use analytics, sets project configuration, and many more. Here are some useful commands.
To generate component under desired path with sass style files: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]ng generate component components/yourComponentName --style=sass Abbreviation: ng g c components/yourComponentName --style=sass
[/dm_code_snippet]
To see what files will be generated without creating them use flag: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
ng g c components/yourComponentName --dryRun=true
[/dm_code_snippet]
To generate without tests files: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
ng g c components/yourComponentName --skipTests=true
[/dm_code_snippet]
- From Angular 6 on, with the help of @angular/elements package you can create custom native components (Web Components) that can be used outside of the Angular. This package provides polyfills to support browsers that don’t support Web Components.To get started, install the package in an Angular project with the following command: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
ng add @angular/elements --name=
[/dm_code_snippet]
- Use an ErrorHandler from @angular/core to handle errors in your app. With this dedicated service, you can easily manage errors in your entire application. As a default, the service catches the errors and logs them to the console, but it can be extended by additional side effects, whatever you need. It is pretty easy to implement such a service: Simply create a file called e.g. error-handler. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
import { ErrorHandler } from '@angular/core'; class MyErrorHandler implements ErrorHandler { handleError(error: any) { // do something with the exception super.handleError(error); } } @NgModule({ providers: [{provide: ErrorHandler, useClass: MyErrorHandler}] }) class MyModule {}
[/dm_code_snippet]
- Add Aliases support to your project. If you think importing your components is ugly, try this. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
import { MyComponent } from '../../../../components/parent-component/my-component'
[/dm_code_snippet]
Specify aliases to your project folders in tsconfig.json file: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”markup” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
{ "compilerOptions": { "baseUrl": "src", "paths": { "@components": "app/components", "@services": "app/services" } } }
[/dm_code_snippet]
And import your code in this way: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
import { MyComponent } from '@components/parent-component/my-component';
[/dm_code_snippet]
-
Preload your resources and routes to speed up your application.
Angular has a preloading strategy implemented already in @anuglar/router module. It allows us to preload routes/links but also resources, modules etc. This process speeds uploading and rendering time of the resources, by preloading them to a browser cache. [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
class CustomPreloadingStrategy implements PreloadingStrategy { preload(route: Route, fn: ()=> Observable) { // ... } } RouterModule.forRoot([ ... ], { preloadingStrategy: OurPreloadingStrategy })
[/dm_code_snippet]
- Do you have a heavy computational function that slows your application down? From default JavaScript is a single-threaded language, so your heavy computations runs along with UI updates, but there is a remedy for it. With help of the Web Workers you can move your heavy non-UI algorithm to run on separate non-UI-blocking thread.Create file with your custom Web Worker (‘./myWebWorker.js’): [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
onmessage = function(e) { console.log('Worker: Message received from main script'); const result = e.data[0] * e.data[1]; if (isNaN(result)) { postMessage('Please write two numbers'); } else { const workerResult = 'Result: ' + result; console.log('Worker: Posting message back to main script'); postMessage(workerResult); } }
[/dm_code_snippet]
Then in your component subscribe on it: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
@Component({ selector: 'app', }) export class App implements OnInit{ private output private webworker: Worker ngOnInit() { if(typeof Worker !== 'undefined') { this.webWorker = new Worker('./myWebWorker') this.webWorker.onmessage = function(data) { this.output = data console.log('Message received from worker'); } } } runHeavyAlgorithm(firstValue: number, secondValue: number) { this.webWorker.postMessage([firstValue, secondValue]) } }
[/dm_code_snippet]
- Use Pure PipesTo specify that your Pipe as “Pure” simply just set the “pure” flag in its configuration: [dm_code_snippet background=”yes” background-mobile=”yes” bg-color=”#eeeeee” theme=”dark” language=”javascript” wrapped=”no” copy-text=”Copy Code” copy-confirmed=”Copied”]
@Pipe({ name: "units", pure: true }) class UnitsPipe implements PipeTransform { ... }
[/dm_code_snippet]
In this way any inputs passing through this Pipe is computed only once and cached, next calls with the same inputs will be skipped and returned the cached data.
- Remove all caret “^” and tilde “~” from your package.json. If you want your project to be stable and not to fail in the future you should stick to specific versions of packages you use.