Upgrade to Pro — share decks privately, control downloads, hide ads and more …

My Biggest Angular Mistakes and How To Avoid Them

My Biggest Angular Mistakes and How To Avoid Them

Every developer makes mistakes, even with years of experience. But these mistakes are a great opportunity to learn something new and gain valuable lessons. In this talk, Fabian Gosebrink shares the mistakes he has made while developing with Angular and how to avoid them. Using real-world projects as examples, he highlights common pitfalls that can affect both beginners and experienced developers alike. These include architectural challenges, inefficient state management strategies, and unexpected performance issues. By the end of this talk, participants will gain insights on how to make their projects more future-proof and stable.

Fabian Gosebrink

December 12, 2024
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. <section class="vh-100"> <div class="container py-5 h-100"> <div class="row d-flex justify-content-center

    align-items-center h-100"> <div class="col col-xl-10"> <div class="card" style="border-radius: 15px"> <div class="card-body p-5"> <h6 class="mb-3"> Awesome Todo List ({{ store.doneCount() }}/{{ store.undoneCount() }} {{ store.percentageDone() | number : "1.0-0" }}% done) </h6> <form (ngSubmit)="addTodo()" class="d-flex justify-content-center align-items-center mb-4" [formGroup]="form" > <div class="form-outline flex-fill"> <input formControlName="todoValue" type="text" id="form1" class="form-control form-control-lg" /> </div> <button type="submit" [disabled]="form.invalid" class="btn btn-primary btn-lg ms-2" > Add </button> </form> <ul class="list-group mb-0"> @for (item of store.items(); track item.id) { <li class="list-group-item d-flex" > <div class="d-flex align-items-center"> <input class="form-check-input me-2" type="checkbox" [checked]="item.done" value="" aria-label="..." (click)="store.toggleDone(item)" /> {{ item.value }} </div> <button type="button" class="btn btn-danger btn-sm" (click)="store.deleteItem(item)" > Delete </button> </li> } </ul> </div> </div> </div> </div> </div> </section> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
  2. <section class="vh-100"> <div class="container py-5 h-100"> <div class="row d-flex justify-content-center

    align-items-center h-100"> <div class="col col-xl-10"> <div class="card" style="border-radius: 15px"> <div class="card-body p-5"> <h6 class="mb-3"> Awesome Todo List ({{ store.doneCount() }}/{{ store.undoneCount() }} {{ store.percentageDone() | number : "1.0-0" }}% done) </h6> <form (ngSubmit)="addTodo()" class="d-flex justify-content-center align-items-center mb-4" [formGroup]="form" > <div class="form-outline flex-fill"> <input formControlName="todoValue" type="text" id="form1" class="form-control form-control-lg" /> </div> <button type="submit" [disabled]="form.invalid" class="btn btn-primary btn-lg ms-2" > Add </button> </form> <ul class="list-group mb-0"> @for (item of store.items(); track item.id) { <li class="list-group-item d-flex" > <div class="d-flex align-items-center"> <input class="form-check-input me-2" type="checkbox" [checked]="item.done" value="" aria-label="..." (click)="store.toggleDone(item)" /> {{ item.value }} </div> <button type="button" class="btn btn-danger btn-sm" (click)="store.deleteItem(item)" > Delete </button> </li> } </ul> </div> </div> </div> </div> </div> </section> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 That's a lot! Doing multiple Things
  3. <div class="max-w-lg mx-auto p-4"> <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <app-todo-form

    (todoAdded)="addTodo($event)" /> <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Separation Container Presentational
  4. <div class="max-w-lg mx-auto p-4"> <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <app-todo-form

    (todoAdded)="addTodo($event)" /> <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <div class="max-w-lg mx-auto p-4"> 1 2 3 4 5 6 7 8 <app-todo-form 9 (todoAdded)="addTodo($event)" /> 10 11 <app-todo-list 12 [items]="sortedTodos()" 13 (delete)="deleteTodo($event)" 14 (markAsDone)="markAsDone($event)" 15 /> 16 17 </div> 18 Separation Container Presentational
  5. <div class="max-w-lg mx-auto p-4"> <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <app-todo-form

    (todoAdded)="addTodo($event)" /> <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <div class="max-w-lg mx-auto p-4"> 1 2 3 4 5 6 7 8 <app-todo-form 9 (todoAdded)="addTodo($event)" /> 10 11 <app-todo-list 12 [items]="sortedTodos()" 13 (delete)="deleteTodo($event)" 14 (markAsDone)="markAsDone($event)" 15 /> 16 17 </div> 18 <app-todo-form (todoAdded)="addTodo($event)" /> <div class="max-w-lg mx-auto p-4"> 1 2 <app-todo-header 3 [count]="count()" 4 [doneItems]="doneItems()" 5 [openItems]="openItems()" 6 /> 7 8 9 10 11 <app-todo-list 12 [items]="sortedTodos()" 13 (delete)="deleteTodo($event)" 14 (markAsDone)="markAsDone($event)" 15 /> 16 17 </div> 18 Separation Container Presentational
  6. <div class="max-w-lg mx-auto p-4"> <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <app-todo-form

    (todoAdded)="addTodo($event)" /> <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <div class="max-w-lg mx-auto p-4"> 1 2 3 4 5 6 7 8 <app-todo-form 9 (todoAdded)="addTodo($event)" /> 10 11 <app-todo-list 12 [items]="sortedTodos()" 13 (delete)="deleteTodo($event)" 14 (markAsDone)="markAsDone($event)" 15 /> 16 17 </div> 18 <app-todo-form (todoAdded)="addTodo($event)" /> <div class="max-w-lg mx-auto p-4"> 1 2 <app-todo-header 3 [count]="count()" 4 [doneItems]="doneItems()" 5 [openItems]="openItems()" 6 /> 7 8 9 10 11 <app-todo-list 12 [items]="sortedTodos()" 13 (delete)="deleteTodo($event)" 14 (markAsDone)="markAsDone($event)" 15 /> 16 17 </div> 18 <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> <div class="max-w-lg mx-auto p-4"> 1 2 <app-todo-header 3 [count]="count()" 4 [doneItems]="doneItems()" 5 [openItems]="openItems()" 6 /> 7 8 <app-todo-form 9 (todoAdded)="addTodo($event)" /> 10 11 12 13 14 15 16 17 </div> 18 Separation Container Presentational
  7. <div class="max-w-lg mx-auto p-4"> <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <app-todo-form

    (todoAdded)="addTodo($event)" /> <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <div class="max-w-lg mx-auto p-4"> 1 2 3 4 5 6 7 8 <app-todo-form 9 (todoAdded)="addTodo($event)" /> 10 11 <app-todo-list 12 [items]="sortedTodos()" 13 (delete)="deleteTodo($event)" 14 (markAsDone)="markAsDone($event)" 15 /> 16 17 </div> 18 <app-todo-form (todoAdded)="addTodo($event)" /> <div class="max-w-lg mx-auto p-4"> 1 2 <app-todo-header 3 [count]="count()" 4 [doneItems]="doneItems()" 5 [openItems]="openItems()" 6 /> 7 8 9 10 11 <app-todo-list 12 [items]="sortedTodos()" 13 (delete)="deleteTodo($event)" 14 (markAsDone)="markAsDone($event)" 15 /> 16 17 </div> 18 <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> <div class="max-w-lg mx-auto p-4"> 1 2 <app-todo-header 3 [count]="count()" 4 [doneItems]="doneItems()" 5 [openItems]="openItems()" 6 /> 7 8 <app-todo-form 9 (todoAdded)="addTodo($event)" /> 10 11 12 13 14 15 16 17 </div> 18 <div class="max-w-lg mx-auto p-4"> <app-todo-header [count]="count()" [doneItems]="doneItems()" [openItems]="openItems()" /> <app-todo-form (todoAdded)="addTodo($event)" /> <app-todo-list [items]="sortedTodos()" (delete)="deleteTodo($event)" (markAsDone)="markAsDone($event)" /> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Separation Container Presentational
  8. @Component({ /* ... */ }) export class UserBaseComponent { //

    Shared Logic // Shared Inputs // ... } @Component({ /* ... */ }) export class AddUserComponent extends UserBaseComponent { // ... } @Component({ /* ... */ }) export class EditUserComponent extends UserBaseComponent { // ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  9. @Injectable({ providedIn: "root" }) export class UserService { // Shared

    Logic // ... } @Component({ /* ... */ }) export class AddUserComponent { private userService = inject(UserService); // ... } @Component({ /* ... */ }) export class EditUserComponent { private userService = inject(UserService); // ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  10. project-root/ ├── app/ │ ├── header.component.ts │ ├── footer.component.ts │

    ├── data.service.ts │ ├── utils.ts │ ├── old/ │ │ ├── old-helper.js │ │ └── unused-file.md │ ├── temp.component.ts │ ├── feature/ │ │ ├── feature.component.ts │ │ ├── styles.css │ │ └── random-script.js │ ├── helper.ts │ └── app.module.ts └── main.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  11. project-root/ └── src/ ├── app/ │ ├── features/ │ │

    ├── user/ │ │ │ ├── container │ │ │ ├── ui │ │ │ └── ... │ │ ├── dashboard/ │ │ │ ├── container │ │ │ ├── ui │ │ │ └── ... │ │ └── shopping-cart/ │ │ ├── container │ │ ├── ui │ │ └── ... │ └── shared/ │ ├── util-auth │ ├── util-logging │ └── ... └── main.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  12. { "dashboard": { // ... }, "profile": { // ...

    }, "shared": { "auth": { ... } }, } 1 2 3 4 5 6 7 8 9 10 11
  13. "dashboard": { { 1 2 // ... 3 }, 4

    "profile": { 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 src/ └── app/ ├── features/ │ ├── dashboard/ │ │ └── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10
  14. "dashboard": { { 1 2 // ... 3 }, 4

    "profile": { 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "profile": { { 1 "dashboard": { 2 // ... 3 }, 4 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 src/ └── app/ ├── features/ │ ├── dashboard/ │ │ └── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10
  15. "dashboard": { { 1 2 // ... 3 }, 4

    "profile": { 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "profile": { { 1 "dashboard": { 2 // ... 3 }, 4 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "shared": { "auth": { ... } { 1 "dashboard": { 2 // ... 3 }, 4 "profile": { 5 // ... 6 }, 7 8 9 }, 10 } 11 src/ └── app/ ├── features/ │ ├── dashboard/ │ │ └── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10
  16. "dashboard": { { 1 2 // ... 3 }, 4

    "profile": { 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "profile": { { 1 "dashboard": { 2 // ... 3 }, 4 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "shared": { "auth": { ... } { 1 "dashboard": { 2 // ... 3 }, 4 "profile": { 5 // ... 6 }, 7 8 9 }, 10 } 11 src/ └── app/ ├── features/ │ ├── dashboard/ │ │ └── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10 │ ├── dashboard/ src/ 1 └── app/ 2 ├── features/ 3 4 │ │ └── ... 5 │ └── profile/ 6 │ └── ... 7 └── shared/ 8 ├── util-auth 9 └── ... 10
  17. "dashboard": { { 1 2 // ... 3 }, 4

    "profile": { 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "profile": { { 1 "dashboard": { 2 // ... 3 }, 4 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "shared": { "auth": { ... } { 1 "dashboard": { 2 // ... 3 }, 4 "profile": { 5 // ... 6 }, 7 8 9 }, 10 } 11 src/ └── app/ ├── features/ │ ├── dashboard/ │ │ └── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10 │ ├── dashboard/ src/ 1 └── app/ 2 ├── features/ 3 4 │ │ └── ... 5 │ └── profile/ 6 │ └── ... 7 └── shared/ 8 ├── util-auth 9 └── ... 10 │ └── profile/ src/ 1 └── app/ 2 ├── features/ 3 │ ├── dashboard/ 4 │ │ └── ... 5 6 │ └── ... 7 └── shared/ 8 ├── util-auth 9 └── ... 10
  18. "dashboard": { { 1 2 // ... 3 }, 4

    "profile": { 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "profile": { { 1 "dashboard": { 2 // ... 3 }, 4 5 // ... 6 }, 7 "shared": { 8 "auth": { ... } 9 }, 10 } 11 "shared": { "auth": { ... } { 1 "dashboard": { 2 // ... 3 }, 4 "profile": { 5 // ... 6 }, 7 8 9 }, 10 } 11 src/ └── app/ ├── features/ │ ├── dashboard/ │ │ └── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10 │ ├── dashboard/ src/ 1 └── app/ 2 ├── features/ 3 4 │ │ └── ... 5 │ └── profile/ 6 │ └── ... 7 └── shared/ 8 ├── util-auth 9 └── ... 10 │ └── profile/ src/ 1 └── app/ 2 ├── features/ 3 │ ├── dashboard/ 4 │ │ └── ... 5 6 │ └── ... 7 └── shared/ 8 ├── util-auth 9 └── ... 10 └── shared/ ├── util-auth src/ 1 └── app/ 2 ├── features/ 3 │ ├── dashboard/ 4 │ │ └── ... 5 │ └── profile/ 6 │ └── ... 7 8 9 └── ... 10
  19. export class ProductsComponent implements OnInit { private readonly store =

    inject(Store); readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); ngOnInit(): void { this.store.dispatch(ProductsActions.loadProducts()); } onProductClicked(id: string): void { this.store.dispatch(ProductsActions.navigateToDetail({ id })); } onCartClicked(product: Product): void { this.store.dispatch(CheckoutActions.addProduct({ product })); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
  20. export class ProductsComponent implements OnInit { private readonly store =

    inject(Store); readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); ngOnInit(): void { this.store.dispatch(ProductsActions.loadProducts()); } onProductClicked(id: string): void { this.store.dispatch(ProductsActions.navigateToDetail({ id })); } onCartClicked(product: Product): void { this.store.dispatch(CheckoutActions.addProduct({ product })); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); this.store.dispatch(ProductsActions.loadProducts()); this.store.dispatch(ProductsActions.navigateToDetail({ id })); this.store.dispatch(CheckoutActions.addProduct({ product })); export class ProductsComponent implements OnInit { 1 private readonly store = inject(Store); 2 3 4 5 6 7 ngOnInit(): void { 8 9 } 10 11 onProductClicked(id: string): void { 12 13 } 14 15 onCartClicked(product: Product): void { 16 17 } 18 } 19
  21. { "dashboard": { // ... }, "profile": { // ...

    }, "shared": { "auth": { ... } }, } 1 2 3 4 5 6 7 8 9 10 11 export const APP_ROUTES: Routes = [ { path: 'dashboard', loadChildren: () => // ... }, { path: 'profile', loadChildren: () => // ... }, ]; 1 2 3 4 5 6 7 8 9 10