{"id":18623613,"url":"https://github.com/codica2/angular-best-practices","last_synced_at":"2025-04-11T03:31:52.948Z","repository":{"id":53933926,"uuid":"177753110","full_name":"codica2/angular-best-practices","owner":"codica2","description":"Best Practices for Writing Angular Apps","archived":false,"fork":false,"pushed_at":"2019-03-26T10:23:22.000Z","size":4,"stargazers_count":60,"open_issues_count":0,"forks_count":19,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-25T09:01:38.408Z","etag":null,"topics":["angular","angular-universal","best-practices","ngrx","ngrx-store"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codica2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-26T09:14:26.000Z","updated_at":"2024-10-07T09:20:36.000Z","dependencies_parsed_at":"2022-08-13T04:50:50.999Z","dependency_job_id":null,"html_url":"https://github.com/codica2/angular-best-practices","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Fangular-best-practices","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Fangular-best-practices/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Fangular-best-practices/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Fangular-best-practices/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codica2","download_url":"https://codeload.github.com/codica2/angular-best-practices/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248335494,"owners_count":21086602,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","angular-universal","best-practices","ngrx","ngrx-store"],"created_at":"2024-11-07T04:25:19.792Z","updated_at":"2025-04-11T03:31:47.931Z","avatar_url":"https://github.com/codica2.png","language":null,"readme":"\u003ch1 align=\"center\"\u003eAngular best practices\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://angular.io\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/badge/-Angular-red.svg?logo=Angular\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://ngrx.io/\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/badge/Angular-NgRX-purple.svg?logo=Angular\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://webpack.js.org/\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/badge/Bundler-Webpack-%238DD6F9.svg?logo=Webpack\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://material.angular.io/\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/badge/Angular-Material-blue.svg?logo=Angular\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://angular.io/guide/universal\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/badge/Angular-Universal-red.svg?logo=Angular\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://angular.io/guide/router\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/badge/Angular-Router-orange.svg?logo=Angular\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://angular.io/\" target=\"_blank\"\u003e\u003cimg src=\"https://toddmotto.com/assets/img/categories/angular.svg\" width=\"150\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n### Description\n\nAngular is a comprehensive JavaScript framework that is frequently used by developers for building cross-platform applications. Angular applications are quick, light, and simple-to-use. Therefore Angular is one of our favourite frameworks for web applications development.\n\nBut we should take into account the many subtleties due to which the application can turn into a continuous mess.\n\nSo let's begin!\n\n### File structure\n\n```\napp/\n├── animations/\n├── components/\n|   ├── main/\n|   ├── profile/\n|   ├── shop/\n|   ├── ...\n├── modules/\n|   ├── admin/\n|   ├── sections/\n|   |   ├── section1/\n|   |   ├── section2/\n|   |   ├── section3/\n|   |   ├── shared/           =\u003e shared only between these sections\n|   |   ├── section.module.ts\n├── directives/\n├── pipes/\n├── models/\n├── resolvers/\n├── services/\n├── guards/\n├── store/\n|   ├── actions/\n|   ├── reducers/\n├── assets/\n|   ├── images/\n|   ├── svg/\n|   ├── sprites/\n|   ├── SCSS/\n|   |   ├── media_queries/\n|   |   ├── _components.scss\n|   |   ├── _base.scss\n|   |   ├── _buttons.scss\n|   |\t├── _cards.scss\n|   |\t├── ...\n|   |   └── main.scss\n├── shared/                  =\u003e shared through all app\n|   ├── components/\n|   ├── pipes/\n|   ├── directives/\n|   ├── shared.module.ts\napp.component.html\napp.component.ts\napp.module.ts\n```\n\n\u003e NOTE: such structure may not be suitable for all projects.\n\n#### trackBy\nAngular will re-render only those elements that have changed rather than whole DOM.\n```js\n// Template\n\u003cli *ngFor=\"let product of products; trackBy: trackByFn\"\u003e{{ product.title }}\u003c/li\u003e\n// Component\ntrackByFn(index, product) {\n   return product.id; // must be unique\n}\n```\n#### Use async pipe\n`async` pipes unsubscribe themselves automatically. It makes component more clean and prevents memory leaks if you forget to unsubscribe manually.\n```javascript\n// Template\n\u003cp\u003e{{ iAmObservable$ | async }}\u003c/p\u003e\n// Component\nthis.iAmObservable$ = this.yourService.someObservable();\n```\n\n#### Do not forget to unsubscribe\nForgetting to unsubscribe from observables will lead to memory leaks even after a component has been destroyed.\n```js\nngOnInit() {\n  this.subscription = this.yourService.someObservable().subscribe(...);\n}\n\nngOnDestroy() {\n  this.subscription.unsubscribe();\n}\n```\n- or you can consider the following approach\n\n```js\nngUnsubscribe: Subject\u003cvoid\u003e = new Subject();\n\nngOnInit() {\n  yourObservable.pipe(takeUntil(this.ngUnsubscribe)).subscribe();\n}\n\nngOnDestroy() {\n  this.ngUnsubscribe.next();\n  this.ngUnsubscribe.complete();\n}\n```\n\nOperators like `{ first, take, takeWhile }` might help you in some circumstances.\n\n#### Use RxJS properly\nLet's consider the most common operators (functions) and the differences between them.\n\n- `throttleTime` vs `debounceTime`\n\n```js\n// wait 2 seconds before emit a new value\nthis.someObservable.pipe(throttleTime(2000))\n```\n\n```js\n// new value will be emitted only if nothing happens for 2 seconds\nthis.someObservable.pipe(debounceTime(2000))\n```\n\n- `distinctUntilChanged`:\n\n```js\nsearchField: FormControl = new FormControl();\n\n// new value will be emitted only if it isn't the same as previous with 2 seconds delay\nngOnInit() {\n  this.searchField.valueChanges.pipe(debounceTime(2000), distinctUntilChanged()).subscribe(...);\n}\n```\n\n- `mergeMap`:\n\nif you want to concurrently handle all the emissions\n\n\n```js\nfield1: FormControl = new FormControl();\nfield2: FormControl = new FormControl();\n\n// let's say \"field1\" you fieled with \"hello\" and \"field2\" with \"world\"\nngOnInit() {\n  this.field1.valueChanges.pipe(\n    mergeMap(value1 =\u003e this.field2.valueChanges.pipe(\n      map(value2 =\u003e `${value1}--${value2}`)\n    ))\n  ).subscribe(...);  =\u003e final value is \"hello--world\"\n}\n```\n\u003e NOTE: A new value will be emitted only when all observables have emitted new value\n\n- `switchMap`:\n\nif you want to ignore the previous emissions when there is a new emission. It means when new value emits the previous subscription will be cancelled.\n\n```js\nsomeObservable1.pipe(\n  switchMap(event =\u003e someObservable2)\n).subscribe(...);\n```\n\n- `concatMap`:\n\nif you want to handle the emissions one after another as they are emitted. It means `someObservable2` will emit new value in strict sequence one by one.\n\n```js\nsomeObservable1.pipe(\n  concatMap(event =\u003e someObservable2)\n).subscribe(...);\n```\n\n- `exhaustMap`:\n\nif you want to cancel all the new emissions while processing a previous emission. It means `someObservable2` will listen to new emission only when previous is finished.\n```js\nsomeObservable1.pipe(\n  exhaustMap(event =\u003e someObservable2)\n).subscribe(...);\n```\n\nI strongly advise to go through this [documentation](https://xgrommx.github.io/rx-book/content/guidelines/introduction/index.html).\n\n\u003e NOTE: Avoid having subscriptions inside subscriptions.\n\n#### Use Lazy loading\nPerhaps one of the most useful features. Most likely that users will not visit all pages of the site, therefore consider splitting the app into small modules, decreasing the main bundle size. It can also help users with a weak internet connection to load the site faster.\n\n```js\n# app.routing-module.ts\nconst routes: Routes = [\n  {\n    path: 'admin',\n    loadChildren: '/path/to/admin/module#AdminModule'\n  }\n];\n```\n\n#### Choose Right Preload Strategy\nBy splitting our application into stand-alone modules we can lazy load the module when the user clicks the link leading to this module. But we can go further using build into Angular preload strategy or defining our own. Preload strategy gives us an ability to load all or some of modules in the background. It means Angular immediately starts rendering modules instead of waiting for the module to download over the network.\n\n```js\n@NgModule({\n  imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })],\n  exports: [RouterModule],\n  providers: [AppCustomPreloader]\n})\nexport class AppRoutingModule { }\n\n```\n\nBut this strategy `PreloadAllModules` may not be the best solution since Angular will load all modules, even those that the user visits very rarely. What we can do?\nLets define our own Preload Strategy.\n\n```js\n# app.routing-module.ts\nconst routes: Routes = [\n  { path: '', redirectTo: 'home', pathMatch: 'full' },\n  { path: 'home', component: HomeComponent },\n  {\n    path: 'profile',\n    loadChildren: '/path/to/profile/module#ProfileModule',\n    data: { preload: true }                             // =\u003e will be preloaded in the backround\n  },\n  {\n    path: 'admin',\n    loadChildren: '/path/to/admin/module#AdminModule'   // =\u003e won't be preloaded in the backround\n  }\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes, { preloadingStrategy: CustomPreloader })],\n  exports: [RouterModule],\n  providers: [CustomPreloader]\n})\nexport class AppRoutingModule { }\n```\n\n```js\n# ./custom-preload-strategy.ts\nimport { PreloadingStrategy, Route } from '@angular/router';\n\nimport { Observable, of } from 'rxjs';\n\nexport class CustomPreloader implements PreloadingStrategy {\n  preload(route: Route, load: Function): Observable\u003cany\u003e {\n    return route.data \u0026\u0026 route.data.preload ? load() : of(null);\n  }\n}\n```\n\n#### Handle failed requests\n\nQuite often API calls might fail. Consider building some logic to handle them.\n\n```js\n  ngOnInit() {\n    this.productService.allProducts.retryWhen(this.handleRetry()).subscribe(...);\n  }\n\n  handleRetry() {\n    return err =\u003e {\n      return err.scan(count =\u003e {\n        count++;\n        if (count \u003c 6) {\n          return count;\n        } else {\n          throw(err);\n        }\n      }, 0).delay(5000);\n    };\n  }\n```\n\u003e NOTE: This is just a basic example. A real project could have more complex logic.\n\n#### Always declare variables and constants with a type\n\n\n```js\nmyVar: string = 'Ruki bazuki';\nsomeArray: Array\u003cnumber\u003e = [1, 2, 3];\nanotherVar: 'a'|'b' = 'a';\n```\n#### Enforce certain rules in your code base\nAlways use lint rules. Find more [here](https://palantir.github.io/tslint/).\n\n#### Small Reusable Components\nComponents should only deal with the display logic. Try to make them reusable if possible.\n\n#### Add Caching\n\nDo not make multiple unnecessary API calls to the same endpoint if data doesn't change often. Check if requested data is already present and update it only when necessary.\n\n#### Avoid logic in templates\n\n```html\n// Template\n\u003cp *ngIf=\"showMe\"\u003e Hello world \u003c/p\u003e\n// Component\nshowMe (): boolean {\n    return couldShow ? true : false;\n}\n```\n\n#### Consider Using State Management\n\n[@ngrx/store](https://github.com/ngrx/platform) can help maintain the state of your application more easily, keeping\nstate related logic separately, reducing complexity. It also has a nice memoization mechanism which might save your app performance.\n\n#### Performance Tips\n\n- **Webpack Bundle Analyzer**. Visualize size of Webpack output files with an interactive zoomable treemap. [Docs](https://www.npmjs.com/package/webpack-bundle-analyzer).\n\n- **Lazy loading for images**. Well explained [here](https://blog.angularindepth.com/a-modern-solution-to-lazy-loading-using-intersection-observer-9280c149bbc).\n\n- **Virtual Scrolling**. Find more info [here](https://material.angular.io/cdk/scrolling/overview).\n\n- **Font loading strategy**. Nice article about the topic [here](https://www.malthemilthers.com/font-loading-strategy-acceptable-flash-of-invisible-text/).\n\n#### Angular Universal\n\nIn case users are using not only Google and performance on mobile and low-powered devices is a must have, you might consider using Server-Side Rendering (SSR). Read more about Angular Universal [here](https://angular.io/guide/universal).\n\n\n## About Codica\n\n[![Codica logo](https://www.codica.com/assets/images/logo/logo.svg)](https://www.codica.com)\n\nThe names and logos for Codica are trademarks of Codica.\n\nWe love open source software! See [our other projects](https://github.com/codica2) or [hire us](https://www.codica.com/) to design, develop, and grow your product.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodica2%2Fangular-best-practices","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodica2%2Fangular-best-practices","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodica2%2Fangular-best-practices/lists"}