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="..."> <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="..."> <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="..."> 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="..."> <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="..."> 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="..."> 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="..."> <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="..."> 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="..."> 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="..."> 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="..."> <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="..."> 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="..."> 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="..."> 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="..."> <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. Easier to test @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'],

    changeDetection: ChangeDetectionStrategy.OnPush, }) export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  9. Easier to test @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'],

    changeDetection: ChangeDetectionStrategy.OnPush, }) export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) 1 2 3 4 5 6 export class DoggoListComponent { 7 doggos = input([]); 8 9 doggoSelected = output<string>(); 10 11 selectDoggo(doggo: Doggo): void { 12 this.doggoSelected.emit(doggo.id); 13 } 14 } 15
  10. Easier to test @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'],

    changeDetection: ChangeDetectionStrategy.OnPush, }) export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) 1 2 3 4 5 6 export class DoggoListComponent { 7 doggos = input([]); 8 9 doggoSelected = output<string>(); 10 11 selectDoggo(doggo: Doggo): void { 12 this.doggoSelected.emit(doggo.id); 13 } 14 } 15 export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } @Component({ 1 selector: 'app-doggo-list', 2 templateUrl: './doggo-list.component.html', 3 styleUrls: ['./doggo-list.component.scss'], 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 }) 6 7 8 9 10 11 12 13 14 15
  11. Easier to test @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'],

    changeDetection: ChangeDetectionStrategy.OnPush, }) export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) 1 2 3 4 5 6 export class DoggoListComponent { 7 doggos = input([]); 8 9 doggoSelected = output<string>(); 10 11 selectDoggo(doggo: Doggo): void { 12 this.doggoSelected.emit(doggo.id); 13 } 14 } 15 export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } @Component({ 1 selector: 'app-doggo-list', 2 templateUrl: './doggo-list.component.html', 3 styleUrls: ['./doggo-list.component.scss'], 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 }) 6 7 8 9 10 11 12 13 14 15 doggos = input([]); doggoSelected = output<string>(); @Component({ 1 selector: 'app-doggo-list', 2 templateUrl: './doggo-list.component.html', 3 styleUrls: ['./doggo-list.component.scss'], 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 }) 6 export class DoggoListComponent { 7 8 9 10 11 selectDoggo(doggo: Doggo): void { 12 this.doggoSelected.emit(doggo.id); 13 } 14 } 15
  12. Easier to test @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'],

    changeDetection: ChangeDetectionStrategy.OnPush, }) export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component({ selector: 'app-doggo-list', templateUrl: './doggo-list.component.html', styleUrls: ['./doggo-list.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) 1 2 3 4 5 6 export class DoggoListComponent { 7 doggos = input([]); 8 9 doggoSelected = output<string>(); 10 11 selectDoggo(doggo: Doggo): void { 12 this.doggoSelected.emit(doggo.id); 13 } 14 } 15 export class DoggoListComponent { doggos = input([]); doggoSelected = output<string>(); selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } } @Component({ 1 selector: 'app-doggo-list', 2 templateUrl: './doggo-list.component.html', 3 styleUrls: ['./doggo-list.component.scss'], 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 }) 6 7 8 9 10 11 12 13 14 15 doggos = input([]); doggoSelected = output<string>(); @Component({ 1 selector: 'app-doggo-list', 2 templateUrl: './doggo-list.component.html', 3 styleUrls: ['./doggo-list.component.scss'], 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 }) 6 export class DoggoListComponent { 7 8 9 10 11 selectDoggo(doggo: Doggo): void { 12 this.doggoSelected.emit(doggo.id); 13 } 14 } 15 selectDoggo(doggo: Doggo): void { this.doggoSelected.emit(doggo.id); } @Component({ 1 selector: 'app-doggo-list', 2 templateUrl: './doggo-list.component.html', 3 styleUrls: ['./doggo-list.component.scss'], 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 }) 6 export class DoggoListComponent { 7 doggos = input([]); 8 9 doggoSelected = output<string>(); 10 11 12 13 14 } 15
  13. Easier to test describe('DoggoListComponent', () => { let component: DoggoListComponent;

    let fixture: ComponentFixture<DoggoListComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); fixture = TestBed.createComponent(DoggoListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  14. Easier to test describe('DoggoListComponent', () => { let component: DoggoListComponent;

    let fixture: ComponentFixture<DoggoListComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); fixture = TestBed.createComponent(DoggoListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); describe('DoggoListComponent', () => { 1 let component: DoggoListComponent; 2 let fixture: ComponentFixture<DoggoListComponent>; 3 4 5 6 7 8 9 fixture = TestBed.createComponent(DoggoListComponent); 10 component = fixture.componentInstance; 11 fixture.detectChanges(); 12 }); 13 14 it('should create', () => { 15 expect(component).toBeTruthy(); 16 }); 17 18
  15. Easier to test describe('DoggoListComponent', () => { let component: DoggoListComponent;

    let fixture: ComponentFixture<DoggoListComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); fixture = TestBed.createComponent(DoggoListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); describe('DoggoListComponent', () => { 1 let component: DoggoListComponent; 2 let fixture: ComponentFixture<DoggoListComponent>; 3 4 5 6 7 8 9 fixture = TestBed.createComponent(DoggoListComponent); 10 component = fixture.componentInstance; 11 fixture.detectChanges(); 12 }); 13 14 it('should create', () => { 15 expect(component).toBeTruthy(); 16 }); 17 18 it('should emit event when method is called', () => { // arrange const spy = jest.spyOn(component.doggoSelected, 'emit'); // act component.selectDoggo({ id: 'my-id' } as Doggo); // assert expect(spy).toHaveBeenCalled(); }); fixture.detectChanges(); 12 }); 13 14 it('should create', () => { 15 expect(component).toBeTruthy(); 16 }); 17 18 19 20 21 22 23 24 25 26 27 28 }) 29
  16. Easier to test describe('DoggoListComponent', () => { let component: DoggoListComponent;

    let fixture: ComponentFixture<DoggoListComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); fixture = TestBed.createComponent(DoggoListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); describe('DoggoListComponent', () => { 1 let component: DoggoListComponent; 2 let fixture: ComponentFixture<DoggoListComponent>; 3 4 5 6 7 8 9 fixture = TestBed.createComponent(DoggoListComponent); 10 component = fixture.componentInstance; 11 fixture.detectChanges(); 12 }); 13 14 it('should create', () => { 15 expect(component).toBeTruthy(); 16 }); 17 18 describe('DoggoListComponent', () => { 1 let component: DoggoListComponent; 2 let fixture: ComponentFixture<DoggoListComponent>; 3 4 beforeEach(async () => { 5 await TestBed.configureTestingModule({ 6 imports: [DoggoListComponent], 7 }).compileComponents(); 8 9 fixture = TestBed.createComponent(DoggoListComponent); 10 component = fixture.componentInstance; 11 fixture.detectChanges(); 12 }); 13 14 it('should create', () => { 15 expect(component).toBeTruthy(); 16 }); 17 18 describe('DoggoListComponent', () => { let component: DoggoListComponent; let fixture: ComponentFixture<DoggoListComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [DoggoListComponent], }).compileComponents(); fixture = TestBed.createComponent(DoggoListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  17. Easier to test @Component({ selector: 'app-main-doggo', templateUrl: './main-doggo.component.html', styleUrls: ['./main-doggo.component.scss'],

    imports: [DoggoListComponent, DoggoRateComponent], }) export class MainDoggoComponent implements OnInit { doggoId = input(''); service1 = inject(Service1); service2 = inject(Service2); service3 = inject(Service3); service4 = inject(Service4); service5 = inject(Service5); private readonly destroyRef = inject(DestroyRef); ngOnInit(): void { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  18. Easier to test @Component({ selector: 'app-main-doggo', templateUrl: './main-doggo.component.html', styleUrls: ['./main-doggo.component.scss'],

    imports: [DoggoListComponent, DoggoRateComponent], }) export class MainDoggoComponent implements OnInit { doggoId = input(''); service1 = inject(Service1); service2 = inject(Service2); service3 = inject(Service3); service4 = inject(Service4); service5 = inject(Service5); private readonly destroyRef = inject(DestroyRef); ngOnInit(): void { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 service1 = inject(Service1); service2 = inject(Service2); service3 = inject(Service3); service4 = inject(Service4); service5 = inject(Service5); templateUrl: ./main-doggo.component.html , 3 styleUrls: ['./main-doggo.component.scss'], 4 imports: [DoggoListComponent, DoggoRateComponent], 5 }) 6 export class MainDoggoComponent implements OnInit { 7 doggoId = input(''); 8 9 10 11 12 13 14 15 private readonly destroyRef = inject(DestroyRef); 16 17 ngOnInit(): void { 18 this.service1.myMethod().subscribe(()=> ...); 19 this.service2.myMethod().subscribe(()=> ...); 20 // 21
  19. Easier to test @Component({ selector: 'app-main-doggo', templateUrl: './main-doggo.component.html', styleUrls: ['./main-doggo.component.scss'],

    imports: [DoggoListComponent, DoggoRateComponent], }) export class MainDoggoComponent implements OnInit { doggoId = input(''); service1 = inject(Service1); service2 = inject(Service2); service3 = inject(Service3); service4 = inject(Service4); service5 = inject(Service5); private readonly destroyRef = inject(DestroyRef); ngOnInit(): void { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 service1 = inject(Service1); service2 = inject(Service2); service3 = inject(Service3); service4 = inject(Service4); service5 = inject(Service5); @Component({ 1 selector: 'app-main-doggo', 2 templateUrl: './main-doggo.component.html', 3 styleUrls: ['./main-doggo.component.scss'], 4 imports: [DoggoListComponent, DoggoRateComponent], 5 }) 6 export class MainDoggoComponent implements OnInit { 7 doggoId = input(''); 8 9 10 11 12 13 14 15 private readonly destroyRef = inject(DestroyRef); 16 17 ngOnInit(): void { 18 ngOnInit(): void { this.service1.myMethod().subscribe(()=> ...); this.service2.myMethod().subscribe(()=> ...); // ... this.destroyRef.onDestroy(() => { this.store.stopListeningToRealtimeDoggoEvents(); }); } service4 = inject(Service4); 13 service5 = inject(Service5); 14 15 private readonly destroyRef = inject(DestroyRef); 16 17 18 19 20 21 22 23 24 25 26 27 rateDoggo(rating: number): void { 28 this.service3.myMethod().subscribe(()=> ...); 29 } 30
  20. Easier to test @Component({ selector: 'app-main-doggo', templateUrl: './main-doggo.component.html', styleUrls: ['./main-doggo.component.scss'],

    imports: [DoggoListComponent, DoggoRateComponent], }) export class MainDoggoComponent implements OnInit { doggoId = input(''); service1 = inject(Service1); service2 = inject(Service2); service3 = inject(Service3); service4 = inject(Service4); service5 = inject(Service5); private readonly destroyRef = inject(DestroyRef); ngOnInit(): void { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 service1 = inject(Service1); service2 = inject(Service2); service3 = inject(Service3); service4 = inject(Service4); service5 = inject(Service5); @Component({ 1 selector: 'app-main-doggo', 2 templateUrl: './main-doggo.component.html', 3 styleUrls: ['./main-doggo.component.scss'], 4 imports: [DoggoListComponent, DoggoRateComponent], 5 }) 6 export class MainDoggoComponent implements OnInit { 7 doggoId = input(''); 8 9 10 11 12 13 14 15 private readonly destroyRef = inject(DestroyRef); 16 17 ngOnInit(): void { 18 ngOnInit(): void { @Component({ 1 selector: 'app-main-doggo', 2 templateUrl: './main-doggo.component.html', 3 styleUrls: ['./main-doggo.component.scss'], 4 imports: [DoggoListComponent, DoggoRateComponent], 5 }) 6 export class MainDoggoComponent implements OnInit { 7 doggoId = input(''); 8 9 service1 = inject(Service1); 10 service2 = inject(Service2); 11 service3 = inject(Service3); 12 service4 = inject(Service4); 13 service5 = inject(Service5); 14 15 private readonly destroyRef = inject(DestroyRef); 16 17 18 rateDoggo(rating: number): void { this.service3.myMethod().subscribe(()=> ...); } skipDoggo(): void { this.service4.myMethod().subscribe(()=> ...); } selectDoggo(id: string): void { this.service5.myMethod().subscribe(()=> ...); } 22 this.destroyRef.onDestroy(() => { 23 this.store.stopListeningToRealtimeDoggoEvents(); 24 }); 25 } 26 27 28 29 30 31 32 33 34 35 36 37 38 } 39
  21. Easier to test describe('MainDoggoComponent', () => { let component: MainDoggoComponent;

    let fixture: ComponentFixture<MainDoggoComponent>; let service1: Service1; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), ], providers: [ MockProvider(Service1), MockProvider(Service2), MockProvider(Service3), MockProvider(Service4), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  22. Easier to test describe('MainDoggoComponent', () => { let component: MainDoggoComponent;

    let fixture: ComponentFixture<MainDoggoComponent>; let service1: Service1; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), ], providers: [ MockProvider(Service1), MockProvider(Service2), MockProvider(Service3), MockProvider(Service4), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let service1: Service1; describe('MainDoggoComponent', () => { 1 let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 MainDoggoComponent, 10 MockComponent(DoggoListComponent), 11 MockComponent(DoggoRateComponent), 12 ], 13 providers: [ 14 MockProvider(Service1), 15 MockProvider(Service2), 16 MockProvider(Service3), 17 MockProvider(Service4), 18
  23. Easier to test describe('MainDoggoComponent', () => { let component: MainDoggoComponent;

    let fixture: ComponentFixture<MainDoggoComponent>; let service1: Service1; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), ], providers: [ MockProvider(Service1), MockProvider(Service2), MockProvider(Service3), MockProvider(Service4), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let service1: Service1; describe('MainDoggoComponent', () => { 1 let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 MainDoggoComponent, 10 MockComponent(DoggoListComponent), 11 MockComponent(DoggoRateComponent), 12 ], 13 providers: [ 14 MockProvider(Service1), 15 MockProvider(Service2), 16 MockProvider(Service3), 17 MockProvider(Service4), 18 MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 let service1: Service1; 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 10 11 12 ], 13 providers: [ 14 MockProvider(Service1), 15 MockProvider(Service2), 16 MockProvider(Service3), 17 MockProvider(Service4), 18 MockProvider(Service5), 19 provideRouter([]) 20
  24. Easier to test describe('MainDoggoComponent', () => { let component: MainDoggoComponent;

    let fixture: ComponentFixture<MainDoggoComponent>; let service1: Service1; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), ], providers: [ MockProvider(Service1), MockProvider(Service2), MockProvider(Service3), MockProvider(Service4), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let service1: Service1; describe('MainDoggoComponent', () => { 1 let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 MainDoggoComponent, 10 MockComponent(DoggoListComponent), 11 MockComponent(DoggoRateComponent), 12 ], 13 providers: [ 14 MockProvider(Service1), 15 MockProvider(Service2), 16 MockProvider(Service3), 17 MockProvider(Service4), 18 MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), describe('MainDoggoComponent', () => { 1 let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 let service1: Service1; 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 10 11 12 ], 13 providers: [ 14 MockProvider(Service1), 15 MockProvider(Service2), 16 MockProvider(Service3), 17 MockProvider(Service4), 18 providers: [ MockProvider(Service1), MockProvider(Service2), MockProvider(Service3), MockProvider(Service4), MockProvider(Service5), provideRouter([]), ], imports: [ 9 MainDoggoComponent, 10 MockComponent(DoggoListComponent), 11 MockComponent(DoggoRateComponent), 12 ], 13 14 15 16 17 18 19 20 21 }).compileComponents(); 22 23 fixture = TestBed.createComponent(MainDoggoComponent); 24 component = fixture.componentInstance; 25
  25. Easier to test describe('MainDoggoComponent', () => { let component: MainDoggoComponent;

    let fixture: ComponentFixture<MainDoggoComponent>; let service1: Service1; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), ], providers: [ MockProvider(Service1), MockProvider(Service2), MockProvider(Service3), MockProvider(Service4), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let service1: Service1; describe('MainDoggoComponent', () => { 1 let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 MainDoggoComponent, 10 MockComponent(DoggoListComponent), 11 MockComponent(DoggoRateComponent), 12 ], 13 providers: [ 14 MockProvider(Service1), 15 MockProvider(Service2), 16 MockProvider(Service3), 17 MockProvider(Service4), 18 MainDoggoComponent, MockComponent(DoggoListComponent), MockComponent(DoggoRateComponent), describe('MainDoggoComponent', () => { 1 let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 let service1: Service1; 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 10 11 12 ], 13 providers: [ 14 MockProvider(Service1), 15 MockProvider(Service2), 16 MockProvider(Service3), 17 MockProvider(Service4), 18 providers: [ MockProvider(Service1), MockProvider(Service2), MockProvider(Service3), MockProvider(Service4), describe('MainDoggoComponent', () => { 1 let component: MainDoggoComponent; 2 let fixture: ComponentFixture<MainDoggoComponent>; 3 4 let service1: Service1; 5 6 beforeEach(async () => { 7 await TestBed.configureTestingModule({ 8 imports: [ 9 MainDoggoComponent, 10 MockComponent(DoggoListComponent), 11 MockComponent(DoggoRateComponent), 12 ], 13 14 15 16 17 18 fixture = TestBed.createComponent(MainDoggoComponent); component = fixture.componentInstance; service1 = TestBed.inject(Service1); jest.spyOn(service1, "myMethod").mockReturnValue(of(...)) // Make everything ready fixture.detectChanges(); MockProvider(Service5), 19 provideRouter([]), 20 ], 21 }).compileComponents(); 22 23 24 25 26 27 28 29 30 31 32 }); 33 34 it('should create ', () => { 35 expect(component).toBeTruthy(); 36
  26. @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
  27. @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
  28. 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
  29. 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
  30. export const APP_ROUTES = [ ]; 1 2 3 project-root/

    └── src/ ├── app/ │ └── shared/ │ ├── util-auth │ ├── util-logging │ └── ... └── main.ts 1 2 3 4 5 6 7 8
  31. export const APP_ROUTES = [ { path: 'users', // ...

    }, ]; 1 2 3 4 5 6 project-root/ └── src/ ├── app/ │ ├── features/ │ │ ├── users/ │ └── shared/ │ ├── util-auth │ ├── util-logging │ └── ... └── main.ts 1 2 3 4 5 6 7 8 9 10
  32. export const APP_ROUTES = [ { path: 'users', // ...

    }, { path: 'dashboard', // ... }, ]; 1 2 3 4 5 6 7 8 9 10 project-root/ └── src/ ├── app/ │ ├── features/ │ │ ├── users/ │ │ ├── dashboard/ │ └── shared/ │ ├── util-auth │ ├── util-logging │ └── ... └── main.ts 1 2 3 4 5 6 7 8 9 10 11
  33. export const APP_ROUTES = [ { path: 'users', // ...

    }, { path: 'dashboard', // ... }, { path: 'shopping-cart', // ... }, ]; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 project-root/ └── src/ ├── app/ │ ├── features/ │ │ ├── users/ │ │ ├── dashboard/ │ │ └── shopping-cart/ │ └── shared/ │ ├── util-auth │ ├── util-logging │ └── ... └── main.ts 1 2 3 4 5 6 7 8 9 10 11 12
  34. export const APP_ROUTES = [ { path: 'users', // ...

    }, { path: 'dashboard', // ... }, { path: 'shopping-cart', // ... }, ]; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 project-root/ └── src/ ├── app/ │ ├── features/ │ │ ├── users/ │ │ │ ├── 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
  35. Typed Forms @Component({ /* ... */ }) export class FormGroupComponent

    { private readonly formBuilder = inject(FormBuilder); myForm = this.formBuilder.group({ // ... age: this.formBuilder.control(0), }); onSubmit() { // both values of the form are typed here console.log(this.myForm.getRawValue()); console.log(this.myForm.value); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  36. Typed Forms @Component({ /* ... */ }) export class FormGroupComponent

    { private readonly formBuilder = inject(FormBuilder); myForm = this.formBuilder.group({ // ... age: this.formBuilder.control(0), }); onSubmit() { // both values of the form are typed here console.log(this.myForm.getRawValue()); console.log(this.myForm.value); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 myForm = this.formBuilder.group({ // ... age: this.formBuilder.control(0), }); @Component({ 1 /* ... */ 2 }) 3 export class FormGroupComponent { 4 private readonly formBuilder = inject(FormBuilder); 5 6 7 8 9 10 11 onSubmit() { 12 // both values of the form are typed here 13 console.log(this.myForm.getRawValue()); 14 console.log(this.myForm.value); 15 } 16 } 17
  37. Typed Forms @Component({ /* ... */ }) export class FormGroupComponent

    { private readonly formBuilder = inject(FormBuilder); myForm = this.formBuilder.group({ // ... age: this.formBuilder.control(0), }); onSubmit() { // both values of the form are typed here console.log(this.myForm.getRawValue()); console.log(this.myForm.value); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 myForm = this.formBuilder.group({ // ... age: this.formBuilder.control(0), }); @Component({ 1 /* ... */ 2 }) 3 export class FormGroupComponent { 4 private readonly formBuilder = inject(FormBuilder); 5 6 7 8 9 10 11 onSubmit() { 12 // both values of the form are typed here 13 console.log(this.myForm.getRawValue()); 14 console.log(this.myForm.value); 15 } 16 } 17 onSubmit() { // both values of the form are typed here console.log(this.myForm.getRawValue()); console.log(this.myForm.value); } @Component({ 1 /* ... */ 2 }) 3 export class FormGroupComponent { 4 private readonly formBuilder = inject(FormBuilder); 5 6 myForm = this.formBuilder.group({ 7 // ... 8 age: this.formBuilder.control(0), 9 }); 10 11 12 13 14 15 16 } 17
  38. value: any | T 1 status: FormControlStatus 2 valid: boolean

    3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14
  39. value: any | T 1 status: FormControlStatus 2 valid: boolean

    3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 status: FormControlStatus value: any | T 1 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14
  40. value: any | T 1 status: FormControlStatus 2 valid: boolean

    3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 status: FormControlStatus value: any | T 1 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 valid: boolean invalid: boolean pending: boolean value: any | T 1 status: FormControlStatus 2 3 4 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14
  41. value: any | T 1 status: FormControlStatus 2 valid: boolean

    3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 status: FormControlStatus value: any | T 1 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 valid: boolean invalid: boolean pending: boolean value: any | T 1 status: FormControlStatus 2 3 4 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 disabled: boolean enabled: boolean value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 6 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14
  42. value: any | T 1 status: FormControlStatus 2 valid: boolean

    3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 status: FormControlStatus value: any | T 1 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 valid: boolean invalid: boolean pending: boolean value: any | T 1 status: FormControlStatus 2 3 4 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 disabled: boolean enabled: boolean value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 6 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 errors: ValidationErrors | null value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14
  43. value: any | T 1 status: FormControlStatus 2 valid: boolean

    3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 status: FormControlStatus value: any | T 1 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 valid: boolean invalid: boolean pending: boolean value: any | T 1 status: FormControlStatus 2 3 4 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 disabled: boolean enabled: boolean value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 6 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 errors: ValidationErrors | null value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 pristine: boolean dirty: boolean touched: boolean untouched: boolean value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 9 10 11 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14
  44. value: any | T 1 status: FormControlStatus 2 valid: boolean

    3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 status: FormControlStatus value: any | T 1 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 valid: boolean invalid: boolean pending: boolean value: any | T 1 status: FormControlStatus 2 3 4 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 disabled: boolean enabled: boolean value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 6 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 errors: ValidationErrors | null value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 pristine: boolean dirty: boolean touched: boolean untouched: boolean value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 9 10 11 12 valueChanges: Observable<...> 13 statusChanges: Observable<...> 14 valueChanges: Observable<...> statusChanges: Observable<...> value: any | T 1 status: FormControlStatus 2 valid: boolean 3 invalid: boolean 4 pending: boolean 5 disabled: boolean 6 enabled: boolean 7 errors: ValidationErrors | null 8 pristine: boolean 9 dirty: boolean 10 touched: boolean 11 untouched: boolean 12 13 14
  45. Validation Errors export class AgeValidator { static ageValidator(control: AbstractControl<number>) {

    if (control.value < 0) { return { ageNotValid: true }; } if (control.value > 100) { return { ageNotValid: true }; } return null; } } 1 2 3 4 5 6 7 8 9 10 11 12 13
  46. Validation Errors export class AgeValidator { static ageValidator(control: AbstractControl<number>) {

    if (control.value < 0) { return { ageNotValid: true }; } if (control.value > 100) { return { ageNotValid: true }; } return null; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 return { ageNotValid: true }; return { ageNotValid: true }; export class AgeValidator { 1 static ageValidator(control: AbstractControl<number>) { 2 if (control.value < 0) { 3 4 } 5 6 if (control.value > 100) { 7 8 } 9 10 return null; 11 } 12 } 13
  47. Validation @Component({ /* ... */ }) export class FormGroupComponent {

    private readonly formBuilder = inject(FormBuilder); myForm = this.formBuilder.group({ // ... age: this.formBuilder.control(-10, [AgeValidator.ageValidator]), }); } 1 2 3 4 5 6 7 8 9 10 11
  48. Validation @Component({ /* ... */ }) export class FormGroupComponent {

    private readonly formBuilder = inject(FormBuilder); myForm = this.formBuilder.group({ // ... age: this.formBuilder.control(-10, [AgeValidator.ageValidator]), }); } 1 2 3 4 5 6 7 8 9 10 11 `myForm` is `INVALID` `myForm.errors` is `null` `myForm.get('age').errors` would be `{ ageNotValid: true }` `myForm.get('age').errors` is `INVALID` 1 2 3 4
  49. Cross Control Validation @Component({ /* ... */ }) export class

    FormGroupComponent { private readonly formBuilder = inject(FormBuilder); myForm = this.formBuilder.group( { // ... age: this.formBuilder.control(-10, [AgeValidator.ageValidator]), }, { validators: [RestrictAgeRoomValidator.ageRoomValidator], } ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  50. Cross Control Validation @Component({ /* ... */ }) export class

    FormGroupComponent { private readonly formBuilder = inject(FormBuilder); myForm = this.formBuilder.group( { // ... age: this.formBuilder.control(-10, [AgeValidator.ageValidator]), }, { validators: [RestrictAgeRoomValidator.ageRoomValidator], } ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 myForm = this.formBuilder.group( { validators: [RestrictAgeRoomValidator.ageRoomValidator], } ); @Component({ 1 /* ... */ 2 }) 3 export class FormGroupComponent { 4 private readonly formBuilder = inject(FormBuilder); 5 6 7 { 8 // ... 9 age: this.formBuilder.control(-10, [AgeValidator.ageValidator]), 10 }, 11 12 13 14 15 } 16
  51. Cross Control Validation export class RestrictAgeRoomValidator { static ageRoomValidator(formGroup: AbstractControl)

    { const ageControl = formGroup.get("age"); const roomControl = formGroup.get("room"); if (!ageControl || !roomControl) { return null; } // return error or null } } 1 2 3 4 5 6 7 8 9 10 11 12 `myForm` is `INVALID` `myForm.errors` is `{ ... }` `myForm.get('age').errors` would be `...` `myForm.get('age').errors` is `...` 1 2 3 4
  52. @Component({ selector: 'app-root', template: ` <button (click)="increaseCounter()"> Increase counter </button>

    {{ counter }} `, }) export class App { counter = 0; increaseCounter(){ this.counter++; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  53. @Component({ selector: 'app-root', template: ` <button (click)="increaseCounter()"> Increase counter </button>

    {{ counter }} `, }) export class App { counter = 0; increaseCounter(){ this.counter++; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 {{ counter }} counter = 0; @Component({ 1 selector: 'app-root', 2 template: ` 3 <button (click)="increaseCounter()"> 4 Increase counter 5 </button> 6 7 `, 8 }) 9 export class App { 10 11 12 increaseCounter(){ 13 this.counter++; 14 } 15 } 16
  54. @Component({ selector: 'app-root', template: ` <button (click)="increaseCounter()"> Increase counter </button>

    {{ counter() }} `, }) export class App { counter = signal(0); increaseCounter(){ this.counter.update((counter) => counter+1) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  55. @Component({ selector: 'app-root', template: ` <button (click)="increaseCounter()"> Increase counter </button>

    {{ counter() }} `, }) export class App { counter = signal(0); increaseCounter(){ this.counter.update((counter) => counter+1) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 {{ counter() }} counter = signal(0); @Component({ 1 selector: 'app-root', 2 template: ` 3 <button (click)="increaseCounter()"> 4 Increase counter 5 </button> 6 7 `, 8 }) 9 export class App { 10 11 12 increaseCounter(){ 13 this.counter.update((counter) => counter+1) 14 } 15 } 16
  56. export class SimpleFormControlComponent { valueAdded = output<string>(); myControl = new

    FormControl("my value"); submitted() { console.log(this.myControl.value); this.valueAdded.emit(this.myControl.value); } } 1 2 3 4 5 6 7 8 9 10 11 Closed Component State
  57. export class TodoMainComponent { todos: Todo[] = []; addTodo(todo: Todo)

    { this.todos.push(todo); } deleteTodo(todoId: number) { this.todos = this.todos.filter(...); } } 1 2 3 4 5 6 7 8 9 10 11 container Component State
  58. @Injectable({ providedIn: 'root' }) export class DashboardService { private dashboard:

    Dashboard = ...; getDashboard(): Observable<Dashboard> { return this.http.get<Dashboard>('api/dashboard').pipe( tap(dashboard => this.dashboard = dashboard) ); } updateDashboard(...): void { this.dashboard = ...; // or return this.http.put<Dashboard>('api/dashboard').pipe( tap(newDashboard => this.dashboard = newDashboard) ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Feature State
  59. @Injectable({ providedIn: 'root' }) export class DashboardService { private dashboard:

    Dashboard = ...; getDashboard(): Observable<Dashboard> { return this.http.get<Dashboard>('api/dashboard').pipe( tap(dashboard => this.dashboard = dashboard) ); } updateDashboard(...): void { this.dashboard = ...; // or return this.http.put<Dashboard>('api/dashboard').pipe( tap(newDashboard => this.dashboard = newDashboard) ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private dashboard: Dashboard = ...; @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 tap(dashboard => this.dashboard = dashboard) 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 Feature State
  60. @Injectable({ providedIn: 'root' }) export class DashboardService { private dashboard:

    Dashboard = ...; getDashboard(): Observable<Dashboard> { return this.http.get<Dashboard>('api/dashboard').pipe( tap(dashboard => this.dashboard = dashboard) ); } updateDashboard(...): void { this.dashboard = ...; // or return this.http.put<Dashboard>('api/dashboard').pipe( tap(newDashboard => this.dashboard = newDashboard) ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private dashboard: Dashboard = ...; @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 tap(dashboard => this.dashboard = dashboard) 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 tap(dashboard => this.dashboard = dashboard) @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 private dashboard: Dashboard = ...; 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 Feature State
  61. @Injectable({ providedIn: 'root' }) export class DashboardService { private dashboard:

    Dashboard = ...; getDashboard(): Observable<Dashboard> { return this.http.get<Dashboard>('api/dashboard').pipe( tap(dashboard => this.dashboard = dashboard) ); } updateDashboard(...): void { this.dashboard = ...; // or return this.http.put<Dashboard>('api/dashboard').pipe( tap(newDashboard => this.dashboard = newDashboard) ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private dashboard: Dashboard = ...; @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 tap(dashboard => this.dashboard = dashboard) 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 tap(dashboard => this.dashboard = dashboard) @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 private dashboard: Dashboard = ...; 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 this.dashboard = ...; @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 private dashboard: Dashboard = ...; 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 tap(dashboard => this.dashboard = dashboard) 7 ); 8 } 9 10 updateDashboard(...): void { 11 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 Feature State
  62. @Injectable({ providedIn: 'root' }) export class DashboardService { private dashboard:

    Dashboard = ...; getDashboard(): Observable<Dashboard> { return this.http.get<Dashboard>('api/dashboard').pipe( tap(dashboard => this.dashboard = dashboard) ); } updateDashboard(...): void { this.dashboard = ...; // or return this.http.put<Dashboard>('api/dashboard').pipe( tap(newDashboard => this.dashboard = newDashboard) ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private dashboard: Dashboard = ...; @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 tap(dashboard => this.dashboard = dashboard) 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 tap(dashboard => this.dashboard = dashboard) @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 private dashboard: Dashboard = ...; 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 this.dashboard = ...; @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 private dashboard: Dashboard = ...; 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 tap(dashboard => this.dashboard = dashboard) 7 ); 8 } 9 10 updateDashboard(...): void { 11 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 tap(newDashboard => this.dashboard = newDashboard) 17 ); 18 } 19 } 20 tap(newDashboard => this.dashboard = newDashboard) @Injectable({ providedIn: 'root' }) 1 export class DashboardService { 2 private dashboard: Dashboard = ...; 3 4 getDashboard(): Observable<Dashboard> { 5 return this.http.get<Dashboard>('api/dashboard').pipe( 6 tap(dashboard => this.dashboard = dashboard) 7 ); 8 } 9 10 updateDashboard(...): void { 11 this.dashboard = ...; 12 13 // or 14 15 return this.http.put<Dashboard>('api/dashboard').pipe( 16 17 ); 18 } 19 } 20 Feature State
  63. @Injectable({ providedIn: "root" }) export class AuthService { private isAuthenticated

    = signal<boolean>(false); isAuthenticated = this.isAuthenticated.asReadOnly(); login() { // ... this.isAuthenticated.set(true); } logout() { // ... this.isAuthenticated.set(false); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Application State
  64. src/ └── app/ ├── features/ │ ├── dashboard/ │ │

    └── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10
  65. 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
  66. 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
  67. 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
  68. "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
  69. "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
  70. "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
  71. 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
  72. 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
  73. export class ProductsComponent implements OnInit { private readonly store =

    inject(Store); readonly productsByCategories = this.store.productsByCategories(); ngOnInit(): void { this.store.loadProducts(); } onProductClicked(id: string): void { this.store.navigateToDetail({ id }); } onCartClicked(product: Product): void { this.store.addProduct({ product }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  74. describe('ProductsComponent', () => { let component: ProductsComponent; let fixture: ComponentFixture<ProductsComponent>;

    beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProductsComponent], providers: [ MockProvider(Store, { productsByCategories: signal(...), loadProducts: jest.fn(), navigateToDetail: jest.fn(), addProduct: jest.fn(), }), ], }).compileComponents(); fixture = TestBed.createComponent(ProductsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  75. describe('ProductsComponent', () => { let component: ProductsComponent; let fixture: ComponentFixture<ProductsComponent>;

    beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProductsComponent], providers: [ MockProvider(Store, { productsByCategories: signal(...), loadProducts: jest.fn(), navigateToDetail: jest.fn(), addProduct: jest.fn(), }), ], }).compileComponents(); fixture = TestBed.createComponent(ProductsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProductsComponent], providers: [ MockProvider(Store, { productsByCategories: signal(...), loadProducts: jest.fn(), navigateToDetail: jest.fn(), addProduct: jest.fn(), }), ], }).compileComponents(); describe('ProductsComponent', () => { 1 let component: ProductsComponent; 2 let fixture: ComponentFixture<ProductsComponent>; 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fixture = TestBed.createComponent(ProductsComponent); 18 component = fixture.componentInstance; 19 fixture.detectChanges(); 20 }); 21 }); 22
  76. describe('ProductsComponent', () => { let component: ProductsComponent; let fixture: ComponentFixture<ProductsComponent>;

    beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProductsComponent], providers: [ MockProvider(Store, { productsByCategories: signal(...), loadProducts: jest.fn(), navigateToDetail: jest.fn(), addProduct: jest.fn(), }), ], }).compileComponents(); fixture = TestBed.createComponent(ProductsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProductsComponent], providers: [ MockProvider(Store, { productsByCategories: signal(...), loadProducts: jest.fn(), navigateToDetail: jest.fn(), addProduct: jest.fn(), }), ], }).compileComponents(); describe('ProductsComponent', () => { 1 let component: ProductsComponent; 2 let fixture: ComponentFixture<ProductsComponent>; 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fixture = TestBed.createComponent(ProductsComponent); 18 component = fixture.componentInstance; 19 fixture.detectChanges(); 20 }); 21 }); 22 describe('ProductsComponent', () => { let component: ProductsComponent; let fixture: ComponentFixture<ProductsComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProductsComponent], providers: [ MockProvider(Store, { productsByCategories: signal(...), loadProducts: jest.fn(), navigateToDetail: jest.fn(), addProduct: jest.fn(), }), ], }).compileComponents(); fixture = TestBed.createComponent(ProductsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  77. { "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