Share
Embed
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
Transcript
149 slides
-
<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 -
<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 -
<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 -
<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 -
<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 -
<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 -
<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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
@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 -
@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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
@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 -
@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 -
@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 -
@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 -
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 -
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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
src/ └── app/ ├── features/ │ ├── dashboard/ │ │
└── ... │ └── profile/ │ └── ... └── shared/ ├── util-auth └── ... 1 2 3 4 5 6 7 8 9 10 -
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 -
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 -
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 -
"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 -
"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 -
"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 -
Container & Presentational Makes container "Listening" to State Changes Less
Logic in COmponents -
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 -
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 -
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 -
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 -
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 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 -
{ "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