├── 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 |
--------------------------------------------------------------------------------
/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 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------