├── README.md └── angular-brushup ├── src ├── app │ ├── app.scss │ ├── ngrx │ │ ├── components │ │ │ ├── counter │ │ │ │ ├── counter.scss │ │ │ │ ├── counter.html │ │ │ │ ├── counter.spec.ts │ │ │ │ └── counter.ts │ │ │ ├── search-todo │ │ │ │ ├── search-todo.scss │ │ │ │ ├── search-todo.html │ │ │ │ ├── search-todo.spec.ts │ │ │ │ └── search-todo.ts │ │ │ ├── todo-list │ │ │ │ ├── todo-list.scss │ │ │ │ ├── todo-list.spec.ts │ │ │ │ ├── todo-list.html │ │ │ │ └── todo-list.ts │ │ │ ├── todo-container │ │ │ │ ├── todo-container.scss │ │ │ │ ├── todo-container.html │ │ │ │ ├── todo-container.spec.ts │ │ │ │ └── todo-container.ts │ │ │ └── search-input │ │ │ │ ├── search-input.html │ │ │ │ ├── search-input.scss │ │ │ │ └── search-input.ts │ │ ├── pages │ │ │ └── ngrx-page │ │ │ │ ├── ngrx-page.scss │ │ │ │ ├── ngrx-page.html │ │ │ │ ├── ngrx-page.ts │ │ │ │ └── ngrx-page.spec.ts │ │ └── store │ │ │ ├── counter.selector.ts │ │ │ ├── counter.actions.ts │ │ │ ├── counter.reducer.ts │ │ │ └── todo-list │ │ │ ├── todo-list.selector.ts │ │ │ ├── todo-list.actions.ts │ │ │ └── todo-list.reducer.ts │ ├── rxjs │ │ ├── pages │ │ │ └── rxjs-page │ │ │ │ ├── rxjs-page.scss │ │ │ │ ├── rxjs-page.html │ │ │ │ ├── rxjs-page.ts │ │ │ │ └── rxjs-page.spec.ts │ │ └── components │ │ │ └── rxjs-test │ │ │ ├── rxjs-test.scss │ │ │ ├── rxjs-test.html │ │ │ ├── rxjs-test.spec.ts │ │ │ └── rxjs-test.ts │ ├── routing │ │ └── pages │ │ │ └── routing-page │ │ │ ├── routing-page.scss │ │ │ ├── routing-page.html │ │ │ ├── routing-page.ts │ │ │ └── routing-page.spec.ts │ ├── signal │ │ ├── pages │ │ │ └── signal-page │ │ │ │ ├── signal-page.scss │ │ │ │ ├── signal-page.html │ │ │ │ ├── signal-page.spec.ts │ │ │ │ └── signal-page.ts │ │ └── components │ │ │ ├── signal-child │ │ │ ├── signal-child.scss │ │ │ ├── signal-child.html │ │ │ ├── signal-child.spec.ts │ │ │ └── signal-child.ts │ │ │ ├── signal-parent │ │ │ ├── signal-parent.scss │ │ │ ├── signal-parent.html │ │ │ ├── signal-parent.spec.ts │ │ │ └── signal-parent.ts │ │ │ └── signal-child-two │ │ │ ├── signal-child-two.scss │ │ │ ├── signal-child-two.html │ │ │ └── signal-child-two.ts │ ├── app.html │ ├── app.ts │ ├── exchange-rate.spec.ts │ ├── exchange-rate.ts │ ├── app.routes.ts │ ├── app.spec.ts │ └── app.config.ts ├── styles.scss ├── main.ts └── index.html ├── public └── favicon.ico ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── .editorconfig ├── tsconfig.spec.json ├── tsconfig.app.json ├── .gitignore ├── tsconfig.json ├── package.json ├── README.md └── angular.json /README.md: -------------------------------------------------------------------------------- 1 | # angular-brushup -------------------------------------------------------------------------------- /angular-brushup/src/app/app.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/counter/counter.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/pages/ngrx-page/ngrx-page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/pages/rxjs-page/rxjs-page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/search-todo/search-todo.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-list/todo-list.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/routing/pages/routing-page/routing-page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/components/rxjs-test/rxjs-test.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/pages/signal-page/signal-page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-child/signal-child.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-container/todo-container.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-parent/signal-parent.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/components/rxjs-test/rxjs-test.html: -------------------------------------------------------------------------------- 1 |

rxjs-test works!

2 | -------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/pages/rxjs-page/rxjs-page.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-child-two/signal-child-two.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #1976d2; 3 | } 4 | -------------------------------------------------------------------------------- /angular-brushup/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /angular-brushup/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidthesloth92/angular-brushup/main/angular-brushup/public/favicon.ico -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/search-todo/search-todo.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/pages/ngrx-page/ngrx-page.html: -------------------------------------------------------------------------------- 1 | This is the NgRx page 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /angular-brushup/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/store/counter.selector.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface AppState { 4 | count: number; 5 | } 6 | 7 | export const selectCount = (state: AppState) => state.count; -------------------------------------------------------------------------------- /angular-brushup/src/app/routing/pages/routing-page/routing-page.html: -------------------------------------------------------------------------------- 1 |

routing-page works!

2 | 3 | {{ this.router.snapshot.url }} 4 | 5 | {{ this.router.snapshot.params | json }} 6 | 7 | {{ this.router.snapshot.queryParams | json }} 8 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/search-input/search-input.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /angular-brushup/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { App } from './app/app'; 4 | 5 | bootstrapApplication(App, appConfig) 6 | .catch((err) => console.error(err)); 7 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/counter/counter.html: -------------------------------------------------------------------------------- 1 |

This is the counter component

2 | 3 | {{ count$ | async }} 4 | {{ counterSignal() }} 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/search-input/search-input.scss: -------------------------------------------------------------------------------- 1 | input {input { 2 | 3 | padding: 0.5rem; padding: 0.5rem; 4 | 5 | border: 1px solid #ccc; border: 1px solid #ccc; 6 | 7 | border-radius: 4px; border-radius: 4px; 8 | 9 | width: 100%; width: 100%; 10 | 11 | }} 12 | 13 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/store/counter.actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from "@ngrx/store"; 2 | 3 | 4 | export const increment = createAction( 5 | '[Counter] Increment' 6 | ); 7 | 8 | export const decrement = createAction( 9 | '[Counter] Decrement' 10 | ); 11 | 12 | export const reset = createAction( 13 | '[Counter] Reset' 14 | ); -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-container/todo-container.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
-------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/pages/rxjs-page/rxjs-page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RxjsTest } from '../../components/rxjs-test/rxjs-test'; 3 | 4 | @Component({ 5 | selector: 'app-rxjs-page', 6 | imports: [RxjsTest], 7 | templateUrl: './rxjs-page.html', 8 | styleUrl: './rxjs-page.scss' 9 | }) 10 | export class RxjsPage { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-child/signal-child.html: -------------------------------------------------------------------------------- 1 | 2 |

Signal Child

3 | 4 | Non Signal Counter: {{ nonSignalCounter }} 5 | 6 | 7 |
8 | 9 | Signal Counter: {{ signalCounter() }} 10 | 11 | 12 | -------------------------------------------------------------------------------- /angular-brushup/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularBrushup 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-child-two/signal-child-two.html: -------------------------------------------------------------------------------- 1 |

Signal Child Two

2 | 3 | Non Signal Counter: {{ nonSignalCounter }} 4 | 5 | 6 |
7 | 8 | Signal Counter: {{ signalCounter() }} 9 | 10 | 11 | -------------------------------------------------------------------------------- /angular-brushup/src/app/app.html: -------------------------------------------------------------------------------- 1 |

Angular Brushup

2 | 3 |

This app is a collection of various angular concepts and examples.

4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /angular-brushup/src/app/app.ts: -------------------------------------------------------------------------------- 1 | import { Component, signal } from '@angular/core'; 2 | import { RouterLink, RouterOutlet } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | imports: [RouterOutlet, RouterLink], 7 | templateUrl: './app.html', 8 | styleUrl: './app.scss' 9 | }) 10 | export class App { 11 | protected readonly title = signal('angular-brushup'); 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /angular-brushup/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | ij_typescript_use_double_quotes = false 14 | 15 | [*.md] 16 | max_line_length = off 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/store/counter.reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer, on } from "@ngrx/store"; 2 | import { increment, decrement, reset } from "./counter.actions"; 3 | 4 | const initialState = 0; 5 | 6 | export const counterFeatureKey = 'count'; 7 | 8 | export const counterReducer = createReducer( 9 | initialState, 10 | on(increment, s => s + 1), 11 | on(decrement, s => s > 0 ? s - 1 : 0 ), 12 | on(reset, () => 0) 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /angular-brushup/src/app/exchange-rate.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ExchangeRateService } from './exchange-rate'; 4 | 5 | describe('ExchangeRate', () => { 6 | let service: ExchangeRateService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ExchangeRateService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-parent/signal-parent.html: -------------------------------------------------------------------------------- 1 |

signal-parent works!

2 | 3 | Non Signal Counter: {{ nonSignalCounter }} 4 | 5 | 6 |
7 | 8 | Signal Counter: {{ signalCounter() }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /angular-brushup/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/spec", 7 | "types": [ 8 | "jasmine" 9 | ] 10 | }, 11 | "include": [ 12 | "src/**/*.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /angular-brushup/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/app", 7 | "types": [] 8 | }, 9 | "include": [ 10 | "src/**/*.ts" 11 | ], 12 | "exclude": [ 13 | "src/**/*.spec.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /angular-brushup/src/app/routing/pages/routing-page/routing-page.ts: -------------------------------------------------------------------------------- 1 | import { JsonPipe } from '@angular/common'; 2 | import { Component, inject } from '@angular/core'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-routing-page', 7 | imports: [JsonPipe], 8 | templateUrl: './routing-page.html', 9 | styleUrl: './routing-page.scss' 10 | }) 11 | export class RoutingPage { 12 | 13 | protected readonly router = inject(ActivatedRoute); 14 | 15 | constructor() {} 16 | } 17 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/pages/ngrx-page/ngrx-page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Counter } from '../../components/counter/counter'; 3 | import { TodoList } from '../../components/todo-list/todo-list'; 4 | import { TodoContainer } from '../../components/todo-container/todo-container'; 5 | 6 | @Component({ 7 | selector: 'app-ngrx-page', 8 | imports: [ Counter, TodoContainer ], 9 | providers: [], 10 | templateUrl: './ngrx-page.html', 11 | styleUrl: './ngrx-page.scss' 12 | }) 13 | export class NgrxPage { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /angular-brushup/src/app/exchange-rate.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { map } from 'rxjs'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class ExchangeRateService { 9 | 10 | 11 | 12 | constructor(private http: HttpClient, @Inject('API_KEY') private apiKey: string) {} 13 | 14 | getLatestRates() { 15 | const url = `https://rest.coincap.io/v3/exchanges?limit=5&offset=0`; 16 | return this.http.get(url).pipe(map((response: any) => response.data)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /angular-brushup/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/pages/signal-page/signal-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @if(isLoading()) { 6 |

Loading exchange rates...

7 | } 8 | @else if(exchangeRates()) { 9 |

Exchange Rates:

10 | 11 | @for(rate of exchangeRates(); track rate.exchangeId) { 12 |

{{ rate.name }}: {{ rate.rank }}

13 | } 14 |
{{ exchangeRates() | json }}
15 | } 16 | @else { 17 |

No exchange rates available.

18 | } 19 |

No exchange rates available.

20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/counter/counter.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { Counter } from './counter'; 4 | 5 | describe('Counter', () => { 6 | let component: Counter; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [Counter] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(Counter); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/pages/ngrx-page/ngrx-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NgrxPage } from './ngrx-page'; 4 | 5 | describe('NgrxPage', () => { 6 | let component: NgrxPage; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [NgrxPage] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(NgrxPage); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/pages/rxjs-page/rxjs-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RxjsPage } from './rxjs-page'; 4 | 5 | describe('RxjsPage', () => { 6 | let component: RxjsPage; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [RxjsPage] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(RxjsPage); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-list/todo-list.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TodoList } from './todo-list'; 4 | 5 | describe('TodoList', () => { 6 | let component: TodoList; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [TodoList] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(TodoList); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/components/rxjs-test/rxjs-test.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RxjsTest } from './rxjs-test'; 4 | 5 | describe('RxjsTest', () => { 6 | let component: RxjsTest; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [RxjsTest] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(RxjsTest); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/pages/signal-page/signal-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignalPage } from './signal-page'; 4 | 5 | describe('SignalPage', () => { 6 | let component: SignalPage; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SignalPage] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SignalPage); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/search-todo/search-todo.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SearchTodo } from './search-todo'; 4 | 5 | describe('SearchTodo', () => { 6 | let component: SearchTodo; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SearchTodo] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SearchTodo); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/routing/pages/routing-page/routing-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RoutingPage } from './routing-page'; 4 | 5 | describe('RoutingPage', () => { 6 | let component: RoutingPage; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [RoutingPage] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(RoutingPage); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-child/signal-child.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignalChild } from './signal-child'; 4 | 5 | describe('SignalChild', () => { 6 | let component: SignalChild; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SignalChild] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SignalChild); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-parent/signal-parent.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignalParent } from './signal-parent'; 4 | 5 | describe('SignalParent', () => { 6 | let component: SignalParent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SignalParent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SignalParent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-container/todo-container.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TodoContainer } from './todo-container'; 4 | 5 | describe('TodoContainer', () => { 6 | let component: TodoContainer; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [TodoContainer] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(TodoContainer); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { provideState } from '@ngrx/store'; 3 | import { todoListFeature } from './ngrx/store/todo-list/todo-list.selector'; 4 | 5 | export const routes: Routes = [ 6 | { path: 'signals', loadComponent: () => import('./signal/pages/signal-page/signal-page').then(m => m.SignalPage) }, 7 | { path: 'ngrx', loadComponent: () => import('./ngrx/pages/ngrx-page/ngrx-page').then(m => m.NgrxPage), providers: [ 8 | provideState(todoListFeature) 9 | ] }, 10 | { path: 'rxjs', loadComponent: () => import('./rxjs/pages/rxjs-page/rxjs-page').then(m => m.RxjsPage) }, 11 | { path: 'routing', loadComponent: () => import('./routing/pages/routing-page/routing-page').then(m => m.RoutingPage) }, 12 | ]; 13 | -------------------------------------------------------------------------------- /angular-brushup/src/app/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { App } from './app'; 3 | 4 | describe('App', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | imports: [App], 8 | }).compileComponents(); 9 | }); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(App); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it('should render title', () => { 18 | const fixture = TestBed.createComponent(App); 19 | fixture.detectChanges(); 20 | const compiled = fixture.nativeElement as HTMLElement; 21 | expect(compiled.querySelector('h1')?.textContent).toContain('Hello, angular-brushup'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-brushup/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | __screenshots__/ 40 | 41 | # System files 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-list/todo-list.html: -------------------------------------------------------------------------------- 1 |

Todo List

2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | 11 |
    12 | @for (todo of todos$ | async; track todo.id) { 13 |
  • 14 |
    15 | {{ todo.title }} 16 | 17 | 18 |
    19 |
  • 20 | } 21 | @empty { 22 |

    No todos yet

    23 | } 24 |
-------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/store/todo-list/todo-list.selector.ts: -------------------------------------------------------------------------------- 1 | import { createFeature, createSelector } from "@ngrx/store"; 2 | import { todoListFeatureKey, todoListReducer } from "./todo-list.reducer"; 3 | 4 | 5 | export const todoListFeature = createFeature({ 6 | name: todoListFeatureKey, 7 | reducer: todoListReducer 8 | }); 9 | 10 | 11 | export const { 12 | selectToDos, 13 | selectLoading, 14 | selectSearchTerm, 15 | } = todoListFeature; 16 | 17 | 18 | export const selectFilteredTodoList = createSelector( 19 | selectToDos, 20 | selectSearchTerm, 21 | (toDos, searchTerm) => { 22 | if (!searchTerm) { 23 | return toDos; 24 | } 25 | const lowerSearchTerm = searchTerm.toLowerCase(); 26 | return toDos.filter(todo => todo.title.toLowerCase().includes(lowerSearchTerm)); 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/search-todo/search-todo.ts: -------------------------------------------------------------------------------- 1 | import { Component, input, OnInit, output, signal } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'app-search-todo', 6 | imports: [FormsModule], 7 | templateUrl: './search-todo.html', 8 | styleUrl: './search-todo.scss' 9 | }) 10 | export class SearchTodo implements OnInit { 11 | placeholder = input(''); 12 | initialValue = input(''); 13 | 14 | protected searchTerm = signal(''); 15 | 16 | ngOnInit() { 17 | if (this.initialValue()) { 18 | this.searchTerm.set(this.initialValue()); 19 | } 20 | } 21 | 22 | searchChange = output(); 23 | 24 | onChange(event: Event) { 25 | const target = event.target as HTMLInputElement; 26 | console.log('Search value changed to:', target.value); 27 | this.searchTerm.set(target.value); 28 | this.searchChange.emit(target.value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/pages/signal-page/signal-page.ts: -------------------------------------------------------------------------------- 1 | import { Component, signal } from '@angular/core'; 2 | import { SignalParent } from '../../components/signal-parent/signal-parent'; 3 | import { ExchangeRateService } from '../../../exchange-rate'; 4 | import { JsonPipe } from '@angular/common'; 5 | 6 | @Component({ 7 | selector: 'app-signal-page', 8 | imports: [ SignalParent, JsonPipe], 9 | templateUrl: './signal-page.html', 10 | styleUrl: './signal-page.scss' 11 | }) 12 | export class SignalPage { 13 | protected readonly exchangeRates = signal(null); 14 | protected readonly isLoading = signal(false); 15 | 16 | constructor(private exchangeRateService: ExchangeRateService) {} 17 | 18 | fetchExchangeRates() { 19 | this.isLoading.set(true); 20 | this.exchangeRateService.getLatestRates().subscribe((data) => { 21 | this.exchangeRates.set(data); 22 | this.isLoading.set(false);`` 23 | console.log('Fetched Exchange Rates:', data); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /angular-brushup/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /angular-brushup/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "compileOnSave": false, 5 | "compilerOptions": { 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "skipLibCheck": true, 12 | "isolatedModules": true, 13 | "experimentalDecorators": true, 14 | "importHelpers": true, 15 | "target": "ES2022", 16 | "module": "preserve" 17 | }, 18 | "angularCompilerOptions": { 19 | "enableI18nLegacyMessageIdFormat": false, 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "typeCheckHostBindings": true, 23 | "strictTemplates": true 24 | }, 25 | "files": [], 26 | "references": [ 27 | { 28 | "path": "./tsconfig.app.json" 29 | }, 30 | { 31 | "path": "./tsconfig.spec.json" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-child-two/signal-child-two.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, DoCheck, OnInit, signal } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-signal-child-two', 5 | // changeDetection: ChangeDetectionStrategy.OnPush, 6 | templateUrl: './signal-child-two.html', 7 | styleUrl: './signal-child-two.scss', 8 | imports: [], 9 | }) 10 | export class SignalChildTwo implements DoCheck, OnInit { 11 | ngOnInit(): void { 12 | // setTimeout(() => { 13 | // this.signalCounter.set(9); 14 | // console.log('Signal Child Two Signal Counter updated to 9 after 5 seconds'); 15 | // }, 5000); 16 | } 17 | 18 | ngDoCheck(): void { 19 | console.log('Signal Child Two Changes Detected'); 20 | } 21 | 22 | nonSignalCounter = 0; 23 | signalCounter = signal(0); 24 | 25 | updateSignalCounter() { 26 | this.signalCounter.update(value => value + 1); 27 | console.log('Signal Child Two Signal Counter clicked'); 28 | } 29 | 30 | updateNonSignalCounter() { 31 | this.nonSignalCounter = this.nonSignalCounter + 1; 32 | console.log('Signal Child Two Non Signal Counter clicked'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/counter/counter.ts: -------------------------------------------------------------------------------- 1 | import { Component, Signal } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { decrement, increment, reset } from '../../store/counter.actions'; 4 | import { Observable } from 'rxjs'; 5 | import { AsyncPipe } from '@angular/common'; 6 | import { selectCount } from '../../store/counter.selector'; 7 | 8 | @Component({ 9 | selector: 'app-counter', 10 | imports: [ AsyncPipe], 11 | templateUrl: './counter.html', 12 | styleUrl: './counter.scss' 13 | }) 14 | export class Counter { 15 | 16 | count$: Observable; 17 | counterSignal: Signal; 18 | 19 | constructor(private store: Store<{count: number}>) { 20 | this.count$ = store.select(selectCount); 21 | this.counterSignal = store.selectSignal(selectCount); 22 | } 23 | 24 | increment() { 25 | console.log('Increment button clicked'); 26 | this.store.dispatch(increment()); 27 | } 28 | 29 | decrement() { 30 | console.log('Decrement button clicked'); 31 | this.store.dispatch(decrement()) 32 | } 33 | 34 | reset() { 35 | console.log('Reset button clicked'); 36 | this.store.dispatch(reset()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/store/todo-list/todo-list.actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionGroup, props } from "@ngrx/store"; 2 | 3 | // export const addTodo = createAction( 4 | // '[Todo List] Add Todo', 5 | // props<{ 6 | // title: string 7 | // }>() 8 | // ); 9 | 10 | // export const removeTodo = createAction('[Todo List] Remove Todo', props<{ id: number }>()); 11 | // export const toggleTodo = createAction('[Todo List] Toggle Todo', props<{ id: number }>()); 12 | // export const editTodo = createAction('[Todo List] Edit Todo', props<{ id: number; title: string }>()); 13 | 14 | 15 | // export const searchTodos = createAction( 16 | // '[Search Todos] Search Todos', 17 | // props<{ searchTerm: string }>() 18 | // ); 19 | 20 | export const ToDoListContainerActions = createActionGroup({ 21 | source: 'TodoList Container', 22 | events: { 23 | 'Add Todo': props<{ 24 | title: string 25 | }>(), 26 | 'Remove Todo': props<{ id: number }>(), 27 | 'Toggle Todo': props<{ id: number }>(), 28 | 'Edit Todo': props<{ id: number; title: string }>(), 29 | 'Search Todos': props<{ searchTerm: string }>() 30 | } 31 | }) 32 | 33 | -------------------------------------------------------------------------------- /angular-brushup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-brushup", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "prettier": { 12 | "printWidth": 100, 13 | "singleQuote": true, 14 | "overrides": [ 15 | { 16 | "files": "*.html", 17 | "options": { 18 | "parser": "angular" 19 | } 20 | } 21 | ] 22 | }, 23 | "private": true, 24 | "dependencies": { 25 | "@angular/common": "^20.3.0", 26 | "@angular/compiler": "^20.3.0", 27 | "@angular/core": "^20.3.0", 28 | "@angular/forms": "^20.3.0", 29 | "@angular/platform-browser": "^20.3.0", 30 | "@angular/router": "^20.3.0", 31 | "@ngrx/store": "^20.0.1", 32 | "rxjs": "~7.8.0", 33 | "tslib": "^2.3.0", 34 | "zone.js": "~0.15.0" 35 | }, 36 | "devDependencies": { 37 | "@angular/build": "^20.3.3", 38 | "@angular/cli": "^20.3.3", 39 | "@angular/compiler-cli": "^20.3.0", 40 | "@types/jasmine": "~5.1.0", 41 | "jasmine-core": "~5.9.0", 42 | "karma": "~6.4.0", 43 | "karma-chrome-launcher": "~3.2.0", 44 | "karma-coverage": "~2.2.0", 45 | "karma-jasmine": "~5.1.0", 46 | "karma-jasmine-html-reporter": "~2.1.0", 47 | "typescript": "~5.9.2" 48 | } 49 | } -------------------------------------------------------------------------------- /angular-brushup/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | 4 | import { routes } from './app.routes'; 5 | import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptors } from '@angular/common/http'; 6 | import { provideState, provideStore, StoreModule } from '@ngrx/store'; 7 | import { counterFeatureKey, counterReducer } from './ngrx/store/counter.reducer'; 8 | 9 | export const appConfig: ApplicationConfig = { 10 | providers: [ 11 | provideBrowserGlobalErrorListeners(), 12 | provideZoneChangeDetection({ eventCoalescing: true }), 13 | provideRouter(routes), 14 | { 15 | provide: 'API_KEY', 16 | useValue: "cc11f98d25d37fbbb6686899d903e5828a81b21a23317267245915aae1c2fe61" 17 | }, 18 | provideHttpClient(withInterceptors([ 19 | (req, next) => { 20 | const apiKey = "cc11f98d25d37fbbb6686899d903e5828a81b21a23317267245915aae1c2fe61"; 21 | const authReq = req.clone({ 22 | setHeaders: { 23 | Authorization: `Bearer ${apiKey}` 24 | } 25 | }); 26 | return next(authReq); 27 | } 28 | ])), 29 | provideStore(), 30 | provideState({ name: counterFeatureKey, reducer: counterReducer }) 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/search-input/search-input.ts: -------------------------------------------------------------------------------- 1 | import { Component, forwardRef, input, Input } from '@angular/core'; 2 | import { NG_VALUE_ACCESSOR, ControlValueAccessor, ReactiveFormsModule } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'app-search-input', 6 | standalone: true, 7 | imports: [ReactiveFormsModule], 8 | templateUrl: './search-input.html', 9 | styleUrl: './search-input.scss', 10 | providers: [ 11 | { 12 | provide: NG_VALUE_ACCESSOR, 13 | useExisting: forwardRef(() => SearchInput), 14 | multi: true, 15 | }, 16 | ], 17 | }) 18 | export class SearchInput implements ControlValueAccessor { 19 | placeholder = input('') 20 | 21 | // internal value 22 | value = ''; 23 | disabled = false; 24 | 25 | // callbacks provided by forms API 26 | private onChange: (v: string) => void = () => {}; 27 | private onTouched: () => void = () => {}; 28 | 29 | // ControlValueAccessor API 30 | writeValue(obj: any): void { 31 | this.value = obj ?? ''; 32 | } 33 | 34 | registerOnChange(fn: any): void { 35 | this.onChange = fn; 36 | } 37 | 38 | registerOnTouched(fn: any): void { 39 | this.onTouched = fn; 40 | } 41 | 42 | setDisabledState?(isDisabled: boolean): void { 43 | this.disabled = !!isDisabled; 44 | } 45 | 46 | // UI handlers 47 | onInput(value: string) { 48 | this.value = value; 49 | this.onChange(value); 50 | } 51 | 52 | onBlur() { 53 | this.onTouched(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-list/todo-list.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { selectFilteredTodoList, selectToDos } from '../../store/todo-list/todo-list.selector'; 4 | import { AsyncPipe } from '@angular/common'; 5 | import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; 6 | import { ToDoListContainerActions } from '../../store/todo-list/todo-list.actions'; 7 | 8 | @Component({ 9 | selector: 'app-todo-list', 10 | imports: [AsyncPipe, ReactiveFormsModule], 11 | templateUrl: './todo-list.html', 12 | styleUrl: './todo-list.scss' 13 | }) 14 | export class TodoList { 15 | 16 | store = inject(Store); 17 | todos$ = this.store.select(selectFilteredTodoList); 18 | 19 | 20 | protected todoForm = new FormGroup({ 21 | title: new FormControl('', { nonNullable: true, validators: [ 22 | Validators.required, Validators.minLength(3), Validators.maxLength(50) 23 | ] }) 24 | }); 25 | 26 | addToDo() { 27 | // Logic to add a new todo item 28 | console.log('Add Todo button clicked'); 29 | this.store.dispatch(ToDoListContainerActions.addTodo({ title: this.todoForm.value.title! })); 30 | this.todoForm.reset(); 31 | } 32 | 33 | toggleTodo(id: number) { 34 | console.log('Toggle Todo with id:', id); 35 | this.store.dispatch(ToDoListContainerActions.toggleTodo({ id })); 36 | } 37 | 38 | deleteTodo(id: number) { 39 | console.log("Delete todo with id: ", id); 40 | this.store.dispatch(ToDoListContainerActions.removeTodo({ id })); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /angular-brushup/README.md: -------------------------------------------------------------------------------- 1 | # AngularBrushup 2 | 3 | This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.3. 4 | 5 | ## Development server 6 | 7 | To start a local development server, run: 8 | 9 | ```bash 10 | ng serve 11 | ``` 12 | 13 | Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. 14 | 15 | ## Code scaffolding 16 | 17 | Angular CLI includes powerful code scaffolding tools. To generate a new component, run: 18 | 19 | ```bash 20 | ng generate component component-name 21 | ``` 22 | 23 | For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: 24 | 25 | ```bash 26 | ng generate --help 27 | ``` 28 | 29 | ## Building 30 | 31 | To build the project run: 32 | 33 | ```bash 34 | ng build 35 | ``` 36 | 37 | This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. 38 | 39 | ## Running unit tests 40 | 41 | To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: 42 | 43 | ```bash 44 | ng test 45 | ``` 46 | 47 | ## Running end-to-end tests 48 | 49 | For end-to-end (e2e) testing, run: 50 | 51 | ```bash 52 | ng e2e 53 | ``` 54 | 55 | Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. 56 | 57 | ## Additional Resources 58 | 59 | For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. 60 | -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-child/signal-child.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, DoCheck, NgZone, OnInit, signal } from '@angular/core'; 2 | import { timer } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-signal-child', 6 | imports: [], 7 | templateUrl: './signal-child.html', 8 | styleUrl: './signal-child.scss', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | // Using OnPush is the modern default with signals, maximizing efficiency 11 | }) 12 | export class SignalChild implements DoCheck, OnInit{ 13 | 14 | constructor(private ngZone: NgZone) {} 15 | 16 | ngOnInit(): void { 17 | 18 | // timer(4000).subscribe(() => { 19 | // console.log('Signal Child Signal Counter updated to 9 after 3 seconds (via Observable)'); 20 | // this.signalCounter.set(3); 21 | // }); 22 | 23 | // this.ngZone.runOutsideAngular(() => { 24 | // setTimeout(() => { 25 | // // This will only update the signal and the child view, not trigger parent change detection 26 | // this.signalCounter.set(20); 27 | // console.log('Signal Child Signal Counter updated to 20 after 6 seconds (outside Angular zone)'); 28 | // }, 6000); 29 | // }); 30 | } 31 | ngDoCheck(): void { 32 | console.log('Signal Child Changes Detected'); 33 | } 34 | 35 | 36 | nonSignalCounter = 0; 37 | signalCounter = signal(0); 38 | 39 | updateSignalCounter() { 40 | 41 | 42 | console.log('Signal Child Signal Counter clicked') 43 | this.signalCounter.update(value => value + 1); 44 | 45 | 46 | } 47 | 48 | 49 | updateNonSignalCounter() { 50 | console.log('Signal Child Non Signal Counter clicked') 51 | this.nonSignalCounter = this.nonSignalCounter + 1; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/store/todo-list/todo-list.reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer, on } from "@ngrx/store"; 2 | import { ToDoListContainerActions } from "./todo-list.actions"; 3 | 4 | interface ToDoItem { 5 | id: number; 6 | title: string; 7 | completed: boolean; 8 | } 9 | 10 | interface TodoListState { 11 | toDos: ToDoItem[]; 12 | loading: boolean; 13 | searchTerm: string; 14 | } 15 | 16 | const initialState: TodoListState = { 17 | toDos: [], 18 | loading: false, 19 | searchTerm: '' 20 | }; 21 | 22 | export const todoListFeatureKey = 'todoListFeature'; 23 | 24 | let idCounter = 0; 25 | export const todoListReducer = createReducer( 26 | initialState, 27 | on(ToDoListContainerActions.addTodo, (state, { title }) => ( 28 | { 29 | ...state, 30 | toDos: [ 31 | ...state.toDos, 32 | { 33 | id: ++idCounter, 34 | title, 35 | completed: false 36 | } 37 | ] 38 | } 39 | )), 40 | on(ToDoListContainerActions.removeTodo, (state, { id }) => ({ 41 | ...state, 42 | toDos: state.toDos.filter(todo => todo.id !== id) 43 | })), 44 | on(ToDoListContainerActions.toggleTodo, (state, { id }) => ({ 45 | ...state, 46 | toDos: state.toDos.map(todo => 47 | todo.id === id ? { ...todo, completed: !todo.completed } : todo 48 | ) 49 | })), 50 | on(ToDoListContainerActions.editTodo, (state, { id, title }) => ({ 51 | ...state, 52 | toDos: state.toDos.map(todo => 53 | todo.id === id ? { ...todo, title } : todo 54 | ) 55 | })), 56 | on(ToDoListContainerActions.searchTodos, (state, { searchTerm }) => ({ 57 | ...state, 58 | searchTerm 59 | })) 60 | ); -------------------------------------------------------------------------------- /angular-brushup/src/app/signal/components/signal-parent/signal-parent.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, DoCheck, NgZone, OnChanges, OnInit, signal } from '@angular/core'; 2 | import { SignalChild } from '../signal-child/signal-child'; 3 | import { timer } from 'rxjs'; 4 | import { SignalChildTwo } from '../signal-child-two/signal-child-two'; 5 | 6 | @Component({ 7 | selector: 'app-signal-parent', 8 | imports: [SignalChild, SignalChildTwo], 9 | templateUrl: './signal-parent.html', 10 | styleUrl: './signal-parent.scss', 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | }) 13 | export class SignalParent implements OnInit, DoCheck { 14 | constructor(private ngZone: NgZone) {} 15 | ngDoCheck(): void { 16 | console.log('Signal Parent Changes Detected') 17 | } 18 | 19 | ngOnInit(): void { 20 | 21 | // timer(3000).subscribe(() => { 22 | console.log('Signal Parent Signal Counter updated to 9 after 3 seconds (via Observable)'); 23 | this.signalCounter.set(9); 24 | // }); 25 | 26 | // this.ngZone.runOutsideAngular(() => { 27 | // setTimeout(() => { 28 | // // This will only update the signal and the child view, not trigger parent change detection 29 | // this.signalCounter.set(20); 30 | // console.log('Signal Parent Signal Counter updated to 20 after 6 seconds (outside Angular zone)'); 31 | // }, 6000); 32 | // }); 33 | } 34 | 35 | nonSignalCounter = 0; 36 | signalCounter = signal(0); 37 | 38 | updateSignalCounter() { 39 | console.log('Signal Parent Signal Counter clicked') 40 | this.signalCounter.update(value => value + 1); 41 | } 42 | 43 | 44 | updateNonSignalCounter() { 45 | console.log('Signal Parent Non Signal Counter clicked') 46 | this.nonSignalCounter = this.nonSignalCounter + 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /angular-brushup/src/app/ngrx/components/todo-container/todo-container.ts: -------------------------------------------------------------------------------- 1 | import { Component, DestroyRef, inject, OnInit } from '@angular/core'; 2 | import { TodoList } from '../todo-list/todo-list'; 3 | import { SearchTodo } from '../search-todo/search-todo'; 4 | import { Store } from '@ngrx/store'; 5 | import { selectSearchTerm } from '../../store/todo-list/todo-list.selector'; 6 | import { AsyncPipe } from '@angular/common'; 7 | import { SearchInput } from '../search-input/search-input'; 8 | import { FormControl, ReactiveFormsModule } from '@angular/forms'; 9 | import { debounceTime, distinctUntilChanged, firstValueFrom } from 'rxjs'; 10 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; 11 | import { ToDoListContainerActions } from '../../store/todo-list/todo-list.actions'; 12 | 13 | @Component({ 14 | selector: 'app-todo-container', 15 | imports: [TodoList, SearchTodo, AsyncPipe, SearchInput, ReactiveFormsModule], 16 | templateUrl: './todo-container.html', 17 | styleUrl: './todo-container.scss' 18 | }) 19 | export class TodoContainer implements OnInit{ 20 | store = inject(Store); 21 | searchTerm$ = this.store.select(selectSearchTerm); 22 | 23 | searchFormControl = new FormControl(''); 24 | 25 | private destroyRef = inject(DestroyRef); 26 | 27 | 28 | async ngOnInit() { 29 | const initial = await firstValueFrom(this.store.select(selectSearchTerm)); 30 | this.searchFormControl.setValue(initial ?? '', { emitEvent: false }); 31 | 32 | this.searchFormControl.valueChanges 33 | .pipe(debounceTime(300), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) 34 | .subscribe(value => { 35 | this.store.dispatch(ToDoListContainerActions.searchTodos({ searchTerm: value || '' })); 36 | }); 37 | } 38 | 39 | 40 | onSearchChange(searchValue: string) { 41 | console.log('Search value in TodoContainer:', searchValue); 42 | this.store.dispatch(ToDoListContainerActions.searchTodos({ searchTerm: searchValue })); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /angular-brushup/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-brushup": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular/build:application", 19 | "options": { 20 | "browser": "src/main.ts", 21 | "polyfills": [ 22 | "zone.js" 23 | ], 24 | "tsConfig": "tsconfig.app.json", 25 | "inlineStyleLanguage": "scss", 26 | "assets": [ 27 | { 28 | "glob": "**/*", 29 | "input": "public" 30 | } 31 | ], 32 | "styles": [ 33 | "src/styles.scss" 34 | ] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "budgets": [ 39 | { 40 | "type": "initial", 41 | "maximumWarning": "500kB", 42 | "maximumError": "1MB" 43 | }, 44 | { 45 | "type": "anyComponentStyle", 46 | "maximumWarning": "4kB", 47 | "maximumError": "8kB" 48 | } 49 | ], 50 | "outputHashing": "all" 51 | }, 52 | "development": { 53 | "optimization": false, 54 | "extractLicenses": false, 55 | "sourceMap": true 56 | } 57 | }, 58 | "defaultConfiguration": "production" 59 | }, 60 | "serve": { 61 | "builder": "@angular/build:dev-server", 62 | "configurations": { 63 | "production": { 64 | "buildTarget": "angular-brushup:build:production" 65 | }, 66 | "development": { 67 | "buildTarget": "angular-brushup:build:development" 68 | } 69 | }, 70 | "defaultConfiguration": "development" 71 | }, 72 | "extract-i18n": { 73 | "builder": "@angular/build:extract-i18n" 74 | }, 75 | "test": { 76 | "builder": "@angular/build:karma", 77 | "options": { 78 | "polyfills": [ 79 | "zone.js", 80 | "zone.js/testing" 81 | ], 82 | "tsConfig": "tsconfig.spec.json", 83 | "inlineStyleLanguage": "scss", 84 | "assets": [ 85 | { 86 | "glob": "**/*", 87 | "input": "public" 88 | } 89 | ], 90 | "styles": [ 91 | "src/styles.scss" 92 | ] 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /angular-brushup/src/app/rxjs/components/rxjs-test/rxjs-test.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first, from, map, Observable, pipe } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-rxjs-test', 6 | imports: [], 7 | templateUrl: './rxjs-test.html', 8 | styleUrl: './rxjs-test.scss' 9 | }) 10 | export class RxjsTest implements OnInit { 11 | message: string = ''; 12 | numbers: number[] = []; 13 | 14 | ngOnInit(): void { 15 | 16 | const observable = new Observable((subscriber) => { 17 | try{ 18 | subscriber.next(1); 19 | subscriber.next(2); 20 | subscriber.next(3); 21 | 22 | setTimeout(() => { 23 | console.log("Producding value 4 after 2 seconds"); 24 | subscriber.next(4); 25 | subscriber.complete(); 26 | }, 2000); 27 | } catch (err) { 28 | subscriber.error(err); 29 | } 30 | }); 31 | 32 | observable.subscribe((value) => { 33 | console.log(value); 34 | }); 35 | console.log('Observable created and subscribed'); 36 | 37 | 38 | observable.subscribe({ 39 | next: (value) => { 40 | console.log('Received value:', value); 41 | }, 42 | complete: () => { 43 | console.log('Observable completed'); 44 | } 45 | 46 | 47 | }); 48 | 49 | 50 | this.listenToAnObservableAndSubscriberStopsAfterSomeTime(); 51 | this.createMyOwnOfObservable(); 52 | 53 | this.pipeDream(); 54 | } 55 | 56 | createMyOwnOfObservable() { 57 | this.dbOf([10, 20, 30, 40]).subscribe({ 58 | next: (value) => { 59 | this.numbers.push(value); 60 | console.log('DB Value:', value); 61 | }, 62 | complete: () => { 63 | this.message = 'All DB values received'; 64 | console.log('DB Observable completed'); 65 | } 66 | }); 67 | } 68 | 69 | listenToAnObservableAndSubscriberStopsAfterSomeTime() { 70 | 71 | // the subscriber decides to stop listening after receiving 5 values. Other subscribers can continue to listen. 72 | // This is useful in scenarios like user navigates away from a page, so we want to stop listening to avoid memory leaks. 73 | const emitter = new Observable (s => { 74 | let count = 0; 75 | const interval = setInterval(() => { 76 | const nextVal = ++count; 77 | console.log('Emitting:', nextVal); 78 | s.next(nextVal); 79 | }, 1000); 80 | 81 | return () => { 82 | clearInterval(interval); 83 | console.log('Observable cleaned up'); 84 | }; 85 | }); 86 | 87 | 88 | const subscription = emitter.subscribe({ 89 | next: (value) => { 90 | console.log('Received value:', value); 91 | 92 | if (value == 5) { 93 | console.log('Unsubscribing after receiving 5'); 94 | subscription.unsubscribe(); 95 | } 96 | }, 97 | complete: () => { 98 | console.log('Observable completed'); 99 | } 100 | }); 101 | } 102 | 103 | 104 | dbOf(params: Array): Observable { 105 | return new Observable(s => { 106 | params.forEach(p => { 107 | s.next(p); 108 | }); 109 | s.complete(); 110 | }); 111 | } 112 | 113 | pipeDream() { 114 | const source = from([1, 2, 3]); 115 | 116 | source.pipe(map(v => v * 2)).subscribe(v => console.log("mapped value:", v)); 117 | 118 | 119 | const myPipe = pipe(map((v: number) => v + 10), first()) 120 | 121 | myPipe(source).subscribe(v => console.log("piped value:", v)); 122 | 123 | } 124 | } 125 | --------------------------------------------------------------------------------