Skip to content

Commit 9992ca3

Browse files
feat: use newest Angular features such as signals and more + use vitest (#293)
* chore: add prettier config in package, remove angular animations and lang service * chore: update gitignore and editorconfig * chore: add agents.md and copilot istructions * chore: ignore sass warnings during build, replace import with use * refactor: migrate to new angular apps structure * replace main.ts * add app.config * add app.routes * use standalone components * replace input decorator * chore: use signals in cats component * chore: use signals in account component * chore: use signals in admin component * fix: prevent triggering editCat on cancel * chore: remove number 2 from the project name * fix: show add new cat form * fix: use update instead of push to update cats when added * chore: align tsconfigs to newest angular project * chore: remove jasmine and karma, add vitest * chore: align angular json to latest angular project * chore: run npm i, update lock * test: replace app component spec with new one * test: about component * test: refactor tests, add account component tests * test: add cat form tests * test: admin tests * test: cats tests * test: login tests * test: one beforeEach * test: register tests * fix: add missing await on testbed * fix: toast component not showing correctly * test: loading component * ci: update test cmd * chore: remove agents.md, leave copilot istructions * fix: use signals on auth service and guards * fix: test for logout * chore: remove useless standalone true, turn on eslint rule too * fix: comma in json * fix: use input signal for cats in add cat form * fix: show error on console error * refactor: rewrite toast as a service * fix: cats list not updating * perf: dont reload the cats, use copy * chore: remove valueof * chore: use ngmodel and ngmodelchange * fix: scss override * refactor: dont use effect in loading component * refactor: use output instead of signal to add cat form * perf: remove workaround, use update * fix: remove providers from app * test: fix authservice missing in providers * fix: update token so user role is updated even if app is reloaded * test: add more tests for app, as logged in, as admin * test: remove jwt providers * test: user auth service mock with signals * fix: remove useless async
1 parent 293c017 commit 9992ca3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1606
-854
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ trim_trailing_whitespace = true
1010

1111
[*.ts]
1212
quote_type = single
13+
ij_typescript_use_double_quotes = false
1314

1415
[*.md]
1516
max_line_length = off

.github/copilot-instructions.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
3+
4+
## TypeScript Best Practices
5+
6+
- Use strict type checking
7+
- Prefer type inference when the type is obvious
8+
- Avoid the `any` type; use `unknown` when type is uncertain
9+
10+
## Angular Best Practices
11+
12+
- Always use standalone components over NgModules
13+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
14+
- Use signals for state management
15+
- Implement lazy loading for feature routes
16+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
17+
- Use `NgOptimizedImage` for all static images.
18+
- `NgOptimizedImage` does not work for inline base64 images.
19+
20+
## Accessibility Requirements
21+
22+
- It MUST pass all AXE checks.
23+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
24+
25+
### Components
26+
27+
- Keep components small and focused on a single responsibility
28+
- Use `input()` and `output()` functions instead of decorators
29+
- Use `computed()` for derived state
30+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
31+
- Prefer inline templates for small components
32+
- Prefer Reactive forms instead of Template-driven ones
33+
- Do NOT use `ngClass`, use `class` bindings instead
34+
- Do NOT use `ngStyle`, use `style` bindings instead
35+
- When using external templates/styles, use paths relative to the component TS file.
36+
37+
## State Management
38+
39+
- Use signals for local component state
40+
- Use `computed()` for derived state
41+
- Keep state transformations pure and predictable
42+
- Do NOT use `mutate` on signals, use `update` or `set` instead
43+
44+
## Templates
45+
46+
- Keep templates simple and avoid complex logic
47+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
48+
- Use the async pipe to handle observables
49+
- Do not assume globals like (`new Date()`) are available.
50+
- Do not write arrow functions in templates (they are not supported).
51+
52+
## Services
53+
54+
- Design services around a single responsibility
55+
- Use the `providedIn: 'root'` option for singleton services
56+
- Use the `inject()` function instead of constructor injection

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ jobs:
2222
node-version: '24'
2323
cache: 'npm'
2424
- run: npm ci
25-
- run: npm run test -- --watch=false --progress=false --browsers=ChromeHeadless
25+
- run: npm run test -- --watch=false
2626
- run: npm run test:be

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ yarn-error.log
2626
!.vscode/tasks.json
2727
!.vscode/launch.json
2828
!.vscode/extensions.json
29+
!.vscode/mcp.json
2930
.history/*
3031

3132
# Miscellaneous
@@ -36,6 +37,7 @@ yarn-error.log
3637
/libpeerconnection.log
3738
testem.log
3839
/typings
40+
__screenshots__/
3941

4042
# System files
4143
.DS_Store

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ Angular Full Stack is a project to easly get started with the latest Angular usi
77
This project uses the [MEAN stack](https://en.wikipedia.org/wiki/MEAN_(software_bundle)):
88
* [**M**ongoose.js](http://www.mongoosejs.com) ([MongoDB](https://www.mongodb.com)): database
99
* [**E**xpress.js](http://expressjs.com): backend framework
10-
* [**A**ngular 2+](https://angular.io): frontend framework
10+
* [**A**ngular](https://angular.io): frontend framework
1111
* [**N**ode.js](https://nodejs.org): runtime environment
1212

1313
Other tools and technologies used:
1414
* [Angular CLI](https://cli.angular.io): frontend scaffolding
1515
* [Bootstrap](http://www.getbootstrap.com): layout and styles
1616
* [Font Awesome](http://fontawesome.com): icons
1717
* [JSON Web Token](https://jwt.io): user authentication
18-
* [Angular 2 JWT](https://github.com/auth0/angular2-jwt): JWT helper for Angular 2+
18+
* [Angular 2 JWT](https://github.com/auth0/angular2-jwt): JWT helper for Angular
1919
* [Bcrypt.js](https://github.com/dcodeIO/bcrypt.js): password encryption
2020

2121
## Prerequisites
@@ -55,14 +55,14 @@ A window will automatically open at [localhost:4200](http://localhost:4200). Ang
5555
10. Tip: use [pm2](https://pm2.keymetrics.io/) to run the app instead of `npm start`, eg: `pm2 start dist/server/app.js`
5656

5757
## Preview
58-
![Preview](https://raw.githubusercontent.com/DavideViolante/Angular2-Full-Stack/master/demo.gif "Preview")
58+
![Preview](https://raw.githubusercontent.com/DavideViolante/Angular-Full-Stack/master/demo.gif "Preview")
5959

6060
## Please open an issue if
6161
* you have any suggestion to improve this project
6262
* you noticed any problem or error
6363

6464
## Running tests
65-
Run `ng test` to execute the frontend unit tests via [Karma](https://karma-runner.github.io).
65+
Run `ng test` to execute the frontend unit tests via [Vitest](https://vitest.dev/).
6666

6767
Run `npm run test:be` to execute the backend tests via [Jest](https://jestjs.io/) (it requires `mongod` already running).
6868

angular.json

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": 1,
44
"newProjectRoot": "projects",
55
"projects": {
6-
"angular2-full-stack": {
6+
"angular-full-stack": {
77
"projectType": "application",
88
"schematics": {
99
"@schematics/angular:component": {
@@ -18,14 +18,14 @@
1818
"builder": "@angular/build:application",
1919
"options": {
2020
"outputPath": "dist/public",
21-
"index": "client/index.html",
2221
"browser": "client/main.ts",
23-
"polyfills": [
24-
"zone.js"
25-
],
2622
"tsConfig": "tsconfig.app.json",
2723
"inlineStyleLanguage": "scss",
2824
"assets": [
25+
{
26+
"glob": "**/*",
27+
"input": "public"
28+
},
2929
"client/assets/favicon.ico",
3030
"client/assets"
3131
],
@@ -35,7 +35,12 @@
3535
],
3636
"scripts": [
3737
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
38-
]
38+
],
39+
"stylePreprocessorOptions": {
40+
"sass": {
41+
"silenceDeprecations": ["color-functions", "global-builtin", "import", "if-function"]
42+
}
43+
}
3944
},
4045
"configurations": {
4146
"production": {
@@ -65,11 +70,11 @@
6570
"builder": "@angular/build:dev-server",
6671
"configurations": {
6772
"production": {
68-
"buildTarget": "angular2-full-stack:build:production"
73+
"buildTarget": "angular-full-stack:build:production"
6974
},
7075
"development": {
7176
"proxyConfig": "proxy.conf.json",
72-
"buildTarget": "angular2-full-stack:build:development"
77+
"buildTarget": "angular-full-stack:build:development"
7378
}
7479
},
7580
"defaultConfiguration": "development"
@@ -78,26 +83,7 @@
7883
"builder": "@angular/build:extract-i18n"
7984
},
8085
"test": {
81-
"builder": "@angular/build:karma",
82-
"options": {
83-
"polyfills": [
84-
"zone.js",
85-
"zone.js/testing"
86-
],
87-
"tsConfig": "tsconfig.spec.json",
88-
"inlineStyleLanguage": "scss",
89-
"assets": [
90-
"client/assets/favicon.ico",
91-
"client/assets"
92-
],
93-
"styles": [
94-
"node_modules/font-awesome/css/font-awesome.min.css",
95-
"client/styles.scss"
96-
],
97-
"scripts": [
98-
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
99-
]
100-
}
86+
"builder": "@angular/build:unit-test"
10187
},
10288
"lint": {
10389
"builder": "@angular-eslint/builder:lint",
@@ -113,6 +99,7 @@
11399
}
114100
},
115101
"cli": {
102+
"packageManager": "npm",
116103
"schematicCollections": [
117104
"angular-eslint"
118105
]
Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,27 @@
1-
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
22

33
import { AboutComponent } from './about.component';
44

55
describe('Component: About', () => {
6-
let component: AboutComponent;
76
let fixture: ComponentFixture<AboutComponent>;
87
let compiled: HTMLElement;
8+
9+
beforeEach(async() => {
10+
await TestBed.configureTestingModule({
11+
imports: [AboutComponent]
12+
}).compileComponents();
913

10-
beforeEach(waitForAsync(() => {
11-
TestBed.configureTestingModule({
12-
declarations: [ AboutComponent ]
13-
})
14-
.compileComponents();
15-
}));
16-
17-
beforeEach(() => {
1814
fixture = TestBed.createComponent(AboutComponent);
19-
component = fixture.componentInstance;
20-
fixture.detectChanges();
15+
await fixture.whenStable();
2116
compiled = fixture.nativeElement as HTMLElement;
2217
});
2318

24-
it('should create', () => {
25-
expect(component).toBeTruthy();
19+
it('should create the about component', () => {
20+
const app = fixture.componentInstance;
21+
expect(app).toBeTruthy();
2622
});
2723

28-
it('should display the page header text', () => {
29-
const header = compiled.querySelector('.card-header');
30-
expect(header?.textContent).toContain('About');
24+
it('should render the header', () => {
25+
expect(compiled.querySelector('.card-header')?.textContent).toContain('About');
3126
});
32-
3327
});

client/app/about/about.component.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Component } from '@angular/core';
44
selector: 'app-about',
55
templateUrl: './about.component.html',
66
styleUrls: ['./about.component.scss'],
7-
standalone: false
87
})
98
export class AboutComponent {
109

client/app/account/account.component.html

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
1-
<app-loading [condition]="isLoading"></app-loading>
1+
<app-loading [condition]="isLoading()"></app-loading>
22

3-
<app-toast [message]="toast.message"></app-toast>
3+
<app-toast></app-toast>
44

5-
@if (!isLoading) {
5+
@if (!isLoading()) {
66
<div class="card">
77
<h4 class="card-header">Account settings</h4>
88
<div class="card-body">
9-
<form #accountForm="ngForm" (ngSubmit)="save(user)">
9+
<form #accountForm="ngForm" (ngSubmit)="save()">
1010
<div class="input-group">
1111
<span class="input-group-text">
1212
<i class="fa fa-user"></i>
1313
</span>
1414
<input class="form-control" type="text" name="username"
15-
[(ngModel)]="user.username" placeholder="Username" required>
15+
[ngModel]="user().username" (ngModelChange)="updateUserField('username', $event)"
16+
placeholder="Username" required>
1617
</div>
1718
<div class="input-group">
1819
<span class="input-group-text">
1920
<i class="fa fa-envelope"></i>
2021
</span>
2122
<input class="form-control" type="email" name="email"
22-
[(ngModel)]="user.email" placeholder="Email" required>
23+
[ngModel]="user().email" (ngModelChange)="updateUserField('email', $event)"
24+
placeholder="Email" required>
2325
</div>
2426
<div class="input-group">
2527
<span class="input-group-text">
2628
<i class="fa fa-black-tie"></i>
2729
</span>
28-
<select class="form-control" name="role" [(ngModel)]="user.role">
30+
<select class="form-control" name="role"
31+
[ngModel]="user().role" (ngModelChange)="updateUserField('role', $event)">
2932
<option value="" disabled>Role</option>
3033
<option value="user">User</option>
3134
<option value="admin">Admin</option>

0 commit comments

Comments
 (0)