Upgrading to Angular 20:

Replaced checked in package-lock.json by regenerated one.
Ran ng update @angular/core@20 @angular/cli@20
Ran npm install @angular-builders/jest@^20.0.0 @fortawesome/angular-fontawesome@^3.0.0 @swimlane/ngx-graph@^11
Ran npm install ngx-mat-select-search@^8.0.2.
Ran ng update @angular/material@20.
Updated all eslint and typescript to the latest versions.
Removed @angular-devkit/schematics.
Updated fontawesome.
Updated rxjs and tslib.
Moved required node version from 16 to 20.
Updated jest-preset-angular.
Set preserveWhitespaces to false.
Corrected two type declarations in two typescript files to make ng serve run without errors.
Removed unused angular-devkit/schematics from view-user.component.ts .
Updated list of supported browsers.
Upgraded prettier.
Ran prettier . --write the first time.
Ran prettier . --write the second time.
Ran ng generate @angular/core:inject with defaults, excepted a handful of files.
Fixed remaining eslint problems.
Added dist directory to ignores in eslint.config.js.
Let prettier fix formatting, almost everything is just line breaks.
Made htmlhint ignore @if and @for Angular constructs.
Updated node version in Github workflow to 22.12.0 (build.yml).
Updated .env.ts.
Updated Angular datetime format strings from YYYY-MM-DD to yyyy-MM-dd.
Merged changes from dev into angular20 branch.
This commit is contained in:
Felix van Hove
2025-12-08 11:01:01 +01:00
parent f1b4ec2cf2
commit 7fd1486c08
1516 changed files with 39367 additions and 36037 deletions
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
/** Custom Services */
@@ -14,14 +14,8 @@ const log = new Logger('AuthenticationGuard');
*/
@Injectable()
export class AuthenticationGuard {
/**
* @param {Router} router Router for navigation.
* @param {AuthenticationService} authenticationService Authentication Service.
*/
constructor(
private router: Router,
private authenticationService: AuthenticationService
) {}
private router = inject(Router);
private authenticationService = inject(AuthenticationService);
/**
* Ensures route access is authorized only when user is authenticated, otherwise redirects to login.
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
/** rxjs Imports */
@@ -27,7 +27,7 @@ const twoFactorAccessTokenHeader = 'Fineract-Platform-TFA-Token';
*/
@Injectable()
export class AuthenticationInterceptor implements HttpInterceptor {
constructor(private settingsService: SettingsService) {}
private settingsService = inject(SettingsService);
/**
* Intercepts a Http request and sets the request headers.
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
/** rxjs Imports */
@@ -25,6 +25,10 @@ import { OAuth2Token } from './o-auth2-token.model';
*/
@Injectable()
export class AuthenticationService {
private http = inject(HttpClient);
private alertService = inject(AlertService);
private authenticationInterceptor = inject(AuthenticationInterceptor);
changePassword(userId: string, passwordObj: any) {
return this.http.put(`/users/${userId}`, passwordObj);
}
@@ -61,11 +65,9 @@ export class AuthenticationService {
* @param {AlertService} alertService Alert Service.
* @param {AuthenticationInterceptor} authenticationInterceptor Authentication Interceptor.
*/
constructor(
private http: HttpClient,
private alertService: AlertService,
private authenticationInterceptor: AuthenticationInterceptor
) {
constructor() {
const authenticationInterceptor = this.authenticationInterceptor;
this.userLoggedIn = false;
this.rememberMe = false;
this.storage = sessionStorage;
+4 -3
View File
@@ -1,5 +1,5 @@
/** Angular Imports */
import { NgModule, Optional, SkipSelf, Injector } from '@angular/core';
import { NgModule, Optional, Injector, inject } from '@angular/core';
import {
HTTP_INTERCEPTORS,
HttpClient,
@@ -49,7 +49,6 @@ import { ContentComponent } from './shell/content/content.component';
@NgModule({
exports: [
SharedModule // TO BE REMOVED: Once all components have replaced the core module import by shared module.
],
imports: [
SharedModule,
@@ -112,7 +111,9 @@ import { ContentComponent } from './shell/content/content.component';
]
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
constructor() {
const parentModule = inject(CoreModule, { optional: true, skipSelf: true });
// Import guard
if (parentModule) {
throw new Error(`${parentModule} has already been loaded. Import Core module in the AppModule only.`);
@@ -38,7 +38,9 @@ export interface ErrorMessage {
})
export class ErrorHandlerService {
constructor(
// eslint-disable-next-line @angular-eslint/prefer-inject
private snackBar: MatSnackBar,
// eslint-disable-next-line @angular-eslint/prefer-inject
private router: Router
) {}
+2 -5
View File
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
/** rxjs Imports */
@@ -13,10 +13,7 @@ import { SettingsService } from 'app/settings/settings.service';
*/
@Injectable()
export class ApiPrefixInterceptor implements HttpInterceptor {
/**
* @param {SettingsService} settingsService Settings Service
*/
constructor(private settingsService: SettingsService) {}
private settingsService = inject(SettingsService);
/**
* Intercepts a Http request and prefixes it with `serverUrl`.
+1
View File
@@ -17,6 +17,7 @@ import { environment } from '../../../environments/environment';
export class CacheInterceptor implements HttpInterceptor {
private forceUpdate = false;
// eslint-disable-next-line @angular-eslint/prefer-inject
constructor(private httpCacheService: HttpCacheService) {}
/**
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
/** rxjs Imports */
@@ -22,14 +22,8 @@ const log = new Logger('ErrorHandlerInterceptor');
*/
@Injectable()
export class ErrorHandlerInterceptor implements HttpInterceptor {
/**
* @param {AlertService} alertService Alert Service.
* @param {TranslateService} translate Translation Service.
*/
constructor(
private alertService: AlertService,
private translate: TranslateService // Added TranslateService
) {}
private alertService = inject(AlertService);
private translate = inject(TranslateService);
/**
* Intercepts a Http request and adds a default error handler.
+3 -1
View File
@@ -1,3 +1,4 @@
/* eslint-disable @angular-eslint/prefer-inject */
/** Angular Imports */
import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core';
import { HttpClient, HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
@@ -81,7 +82,8 @@ export class HttpService extends HttpClient {
// Configure default interceptors that can be disabled here
this.interceptors = [
this.injector.get(ApiPrefixInterceptor),
this.injector.get(ErrorHandlerInterceptor)];
this.injector.get(ErrorHandlerInterceptor)
];
}
}
+1
View File
@@ -7,6 +7,7 @@ import { Observable } from 'rxjs';
@Injectable()
export class I18nService {
// eslint-disable-next-line @angular-eslint/prefer-inject
constructor(private translateService: TranslateService) {}
public translate(key: string, params?: object): Observable<string> {
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
/** rxjs Imports */
@@ -14,10 +14,7 @@ import { ProgressBarService } from './progress-bar.service';
*/
@Injectable()
export class ProgressInterceptor implements HttpInterceptor {
/**
* @param {ProgressBarService} progressBarService Progress Bar Service.
*/
constructor(private progressBarService: ProgressBarService) {}
private progressBarService = inject(ProgressBarService);
/**
* Intercepts a Http request to start loading the progress bar for a pending request
@@ -1,15 +1,27 @@
<nav class="breadcrumb-wrapper">
<ul class="breadcrumb">
<span #breadcrumb class="breadcrumb-title">{{ getTranslate(breadcrumbs[breadcrumbs?.length - 1].label) }}</span>
<span *ngIf="breadcrumbs.length - 1 !== 0" class="separator">
<li *ngFor="let breadcrumb of breadcrumbs; let last = last" class="breadcrumb-label">
<span *ngIf="!last" class="breadcrumb-link">
<a *ngIf="breadcrumb.url" [routerLink]="breadcrumb.url">{{ getTranslate(breadcrumb.label) }}</a>
<span *ngIf="!breadcrumb.url">{{ getTranslate(breadcrumb.label) }}</span>
</span>
<span *ngIf="last">{{ getTranslate(breadcrumb.label) }}</span>
</li>
</span>
@if (breadcrumbs.length - 1 !== 0) {
<span class="separator">
@for (breadcrumb of breadcrumbs; track breadcrumb; let last = $last) {
<li class="breadcrumb-label">
@if (!last) {
<span class="breadcrumb-link">
@if (breadcrumb.url) {
<a [routerLink]="breadcrumb.url">{{ getTranslate(breadcrumb.label) }}</a>
}
@if (!breadcrumb.url) {
<span>{{ getTranslate(breadcrumb.label) }}</span>
}
</span>
}
@if (last) {
<span>{{ getTranslate(breadcrumb.label) }}</span>
}
</li>
}
</span>
}
</ul>
</nav>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, TemplateRef, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import { Component, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
import { ActivatedRoute, Router, NavigationEnd, Data, RouterLink } from '@angular/router';
/** rxjs Imports */
@@ -55,6 +55,12 @@ const routeAddBreadcrumbLink = 'addBreadcrumbLink';
]
})
export class BreadcrumbComponent implements AfterViewInit {
private activatedRoute = inject(ActivatedRoute);
private router = inject(Router);
private configurationWizardService = inject(ConfigurationWizardService);
private popoverService = inject(PopoverService);
private translateService = inject(TranslateService);
/** Array of breadcrumbs. */
breadcrumbs: Breadcrumb[];
/* Reference of breadcrumb */
@@ -69,13 +75,7 @@ export class BreadcrumbComponent implements AfterViewInit {
* @param {ConfigurationWizardService} configurationWizardService ConfigurationWizard Service.
* @param {PopoverService} popoverService PopoverService.
*/
constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
private configurationWizardService: ConfigurationWizardService,
private popoverService: PopoverService,
private translateService: TranslateService
) {
constructor() {
this.generateBreadcrumbs();
}
+5 -3
View File
@@ -20,9 +20,11 @@
<!-- Toolbar -->
<mifosx-toolbar [sidenav]="sidenav" (collapse)="toggleCollapse($event)"></mifosx-toolbar>
<!-- Progress Bar -->
<div *ngIf="progressBarMode !== 'none'">
<div class="loading"></div>
</div>
@if (progressBarMode !== 'none') {
<div>
<div class="loading"></div>
</div>
}
<!-- Breadcrumb -->
<mifosx-breadcrumb></mifosx-breadcrumb>
+6 -13
View File
@@ -1,6 +1,6 @@
/** Angular Imports */
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';
/** rxjs Imports */
import { Observable, Subscription } from 'rxjs';
@@ -9,7 +9,7 @@ import { map } from 'rxjs/operators';
/** Custom Services */
import { ProgressBarService } from '../progress-bar/progress-bar.service';
import { MatSidenavContainer, MatSidenav, MatSidenavContent } from '@angular/material/sidenav';
import { NgClass, NgIf, AsyncPipe } from '@angular/common';
import { NgClass, AsyncPipe } from '@angular/common';
import { SidenavComponent } from './sidenav/sidenav.component';
import { ToolbarComponent } from './toolbar/toolbar.component';
import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
@@ -39,6 +39,10 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ShellComponent implements OnInit, OnDestroy {
private breakpointObserver = inject(BreakpointObserver);
private progressBarService = inject(ProgressBarService);
private cdr = inject(ChangeDetectorRef);
/** Subscription to breakpoint observer for handset. */
isHandset$: Observable<boolean> = this.breakpointObserver
.observe(Breakpoints.Handset)
@@ -50,17 +54,6 @@ export class ShellComponent implements OnInit, OnDestroy {
/** Subscription to progress bar. */
progressBar$: Subscription;
/**
* @param {BreakpointObserver} breakpointObserver Breakpoint Observer to detect screen size.
* @param {ProgressBarService} progressBarService Progress Bar Service.
* @param {ChangeDetectorRef} cdr Change Detector Ref.
*/
constructor(
private breakpointObserver: BreakpointObserver,
private progressBarService: ProgressBarService,
private cdr: ChangeDetectorRef
) {}
/**
* Subscribes to progress bar to update its mode.
*/
@@ -40,16 +40,18 @@
</div>
</div>
<div class="icon-menu">
<div class="icon-menu-item" *ngFor="let activity of mappedActivities">
<button
mat-raised-button
[matTooltipPosition]="tooltipPosition"
[matTooltip]="activity.toolTip"
[routerLink]="[activity.path]"
>
<fa-icon [icon]="activity.icon" size="lg"></fa-icon>
</button>
</div>
@for (activity of mappedActivities; track activity) {
<div class="icon-menu-item">
<button
mat-raised-button
[matTooltipPosition]="tooltipPosition"
[matTooltip]="activity.toolTip"
[routerLink]="[activity.path]"
>
<fa-icon [icon]="activity.icon" size="lg"></fa-icon>
</button>
</div>
}
</div>
<div class="nav-item-sep">
+11 -11
View File
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit, Input, TemplateRef, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import { Component, OnInit, Input, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router, RouterLink, RouterLinkActive } from '@angular/router';
@@ -14,7 +14,7 @@ import { ConfigurationWizardService } from '../../../configuration-wizard/config
/** Custom Imports */
import { frequentActivities } from './frequent-activities';
import { SettingsService } from 'app/settings/settings.service';
import { NgClass, NgFor } from '@angular/common';
import { NgClass } from '@angular/common';
import { MatIconButton, MatButton } from '@angular/material/button';
import { MatTooltip } from '@angular/material/tooltip';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
@@ -50,6 +50,14 @@ import { AuthService } from 'app/zitadel/auth.service';
]
})
export class SidenavComponent implements OnInit, AfterViewInit {
private router = inject(Router);
dialog = inject(MatDialog);
private authenticationService = inject(AuthenticationService);
private settingsService = inject(SettingsService);
private configurationWizardService = inject(ConfigurationWizardService);
private popoverService = inject(PopoverService);
private authService = inject(AuthService);
/** True if sidenav is in collapsed state. */
@Input() sidenavCollapsed: boolean;
/** Tooltip position */
@@ -80,15 +88,7 @@ export class SidenavComponent implements OnInit, AfterViewInit {
* @param {ConfigurationWizardService} configurationWizardService ConfigurationWizard Service.
* @param {PopoverService} popoverService PopoverService.
*/
constructor(
private router: Router,
public dialog: MatDialog,
private authenticationService: AuthenticationService,
private settingsService: SettingsService,
private configurationWizardService: ConfigurationWizardService,
private popoverService: PopoverService,
private authService: AuthService
) {
constructor() {
this.userActivity = JSON.parse(localStorage.getItem('mifosXLocation'));
}
@@ -1,25 +1,31 @@
<mat-toolbar class="layout-row" color="primary" id="mifosx-toolbar">
<button
mat-icon-button
*ngIf="!sidenavCollapsed"
(click)="toggleSidenav()"
matTooltip="{{ 'tooltips.Toggle Hide/Open' | translate }}"
tabindex="0"
>
<fa-icon icon="bars" size="lg"></fa-icon>
</button>
@if (!sidenavCollapsed) {
<button
mat-icon-button
(click)="toggleSidenav()"
matTooltip="{{ 'tooltips.Toggle Hide/Open' | translate }}"
tabindex="0"
>
<fa-icon icon="bars" size="lg"></fa-icon>
</button>
}
<button
mat-icon-button
*ngIf="sidenav.opened"
(click)="toggleSidenavCollapse()"
matTooltip="{{ 'tooltips.Toggle Collapse' | translate }}"
class="hide-lt-md"
tabindex="0"
>
<fa-icon *ngIf="!sidenavCollapsed" icon="chevron-left"></fa-icon>
<fa-icon *ngIf="sidenavCollapsed" icon="chevron-right"></fa-icon>
</button>
@if (sidenav.opened) {
<button
mat-icon-button
(click)="toggleSidenavCollapse()"
matTooltip="{{ 'tooltips.Toggle Collapse' | translate }}"
class="hide-lt-md"
tabindex="0"
>
@if (!sidenavCollapsed) {
<fa-icon icon="chevron-left"></fa-icon>
}
@if (sidenavCollapsed) {
<fa-icon icon="chevron-right"></fa-icon>
}
</button>
}
<div class="tab-nav-bar">
<a
+11 -20
View File
@@ -10,7 +10,8 @@ import {
ElementRef,
TemplateRef,
AfterContentChecked,
ChangeDetectorRef
ChangeDetectorRef,
inject
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSidenav } from '@angular/material/sidenav';
@@ -69,6 +70,15 @@ import { AuthService } from 'app/zitadel/auth.service';
]
})
export class ToolbarComponent implements OnInit, AfterViewInit, AfterContentChecked {
private breakpointObserver = inject(BreakpointObserver);
private router = inject(Router);
private authenticationService = inject(AuthenticationService);
private popoverService = inject(PopoverService);
private configurationWizardService = inject(ConfigurationWizardService);
private dialog = inject(MatDialog);
private changeDetector = inject(ChangeDetectorRef);
private authService = inject(AuthService);
/* Reference of institution */
@ViewChild('institution') institution: ElementRef<any>;
/* Template for popover on institution */
@@ -92,25 +102,6 @@ export class ToolbarComponent implements OnInit, AfterViewInit, AfterContentChec
/** Sidenav collapse event. */
@Output() collapse = new EventEmitter<boolean>();
/**
* @param {BreakpointObserver} breakpointObserver Breakpoint observer to detect screen size.
* @param {Router} router Router for navigation.
* @param {AuthenticationService} authenticationService Authentication service.
* @param {MatDialog} dialog MatDialog.
* @param {ConfigurationWizardService} configurationWizardService ConfigurationWizard Service.
* @param {PopoverService} popoverService PopoverService.
*/
constructor(
private breakpointObserver: BreakpointObserver,
private router: Router,
private authenticationService: AuthenticationService,
private popoverService: PopoverService,
private configurationWizardService: ConfigurationWizardService,
private dialog: MatDialog,
private changeDetector: ChangeDetectorRef,
private authService: AuthService
) {}
/**
* Subscribes to breakpoint for handset.
*/
+4 -6
View File
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { SettingsService } from 'app/settings/settings.service';
import { CheckboxBase } from 'app/shared/form-dialog/formfield/model/checkbox-base';
import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base';
@@ -11,6 +11,9 @@ import { Dates } from './dates';
providedIn: 'root'
})
export class Datatables {
private dateUtils = inject(Dates);
private settingsService = inject(SettingsService);
systemFields: string[] = [
'id',
'created_at',
@@ -30,11 +33,6 @@ export class Datatables {
'share_product_id'
];
constructor(
private dateUtils: Dates,
private settingsService: SettingsService
) {}
public getFormfields(columns: any, dateTransformColumns: string[], dataTableEntryObject: any) {
return columns.map((column: any) => {
switch (column.columnDisplayType) {
+4 -4
View File
@@ -1,18 +1,18 @@
import { DatePipe } from '@angular/common';
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import moment from 'moment';
@Injectable({
providedIn: 'root'
})
export class Dates {
private datePipe = inject(DatePipe);
public static DEFAULT_DATEFORMAT = 'yyyy-MM-dd';
public static DEFAULT_DATETIMEFORMAT = 'yyyy-MM-dd HH:mm';
constructor(private datePipe: DatePipe) {}
public getDate(timestamp: any): string {
return this.datePipe.transform(timestamp, 'YYYY-MM-DD');
return this.datePipe.transform(timestamp, 'yyyy-MM-dd');
}
public formatDate(timestamp: any, dateFormat: string): string {