Consolidated commit message to v22 signals

This commit is contained in:
2026-08-25 14:45:09 +00:00
parent 6403d25d3e
commit df289b20b8
443 changed files with 16058 additions and 17332 deletions
@@ -1,54 +1,61 @@
<h2 class="row-container space-between wrapped">
<span>Account</span>
<mat-icon matSuffix (click)="item.isStarred = !item.isStarred" class="pointer" [class.gold]="item.isStarred">
{{ item.isStarred ? 'star' : 'star_border' }}
<mat-icon matSuffix (click)="item().isStarred = !item().isStarred" class="pointer" [class.gold]="item().isStarred">
{{ item().isStarred ? 'star' : 'star_border' }}
</mat-icon>
</h2>
<form [formGroup]="form" class="flex-col wrapped">
@if (itemResource.isLoading()) {
<app-skeleton-loader type="card" [count]="1" />
<app-skeleton-loader type="line" [count]="10" />
} @else if (itemResource.error()) {
<app-error-state [message]="'Failed to load data.'" (retryAction)="itemResource.reload()" />
} @else {
<form class="flex-col wrapped" [formRoot]="accountForm">
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Code</mat-label>
<input matInput [formField]="accountForm.code" />
</mat-form-field>
</div>
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Name</mat-label>
<input matInput [formField]="accountForm.name" />
</mat-form-field>
</div>
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Account Type</mat-label>
<mat-select [formField]="accountForm.type">
@for (at of accountTypes(); track at) {
<mat-option [value]="at.id">
{{ at.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="row-container">
<mat-checkbox [formField]="accountForm.isActive" class="flex-auto">Is Active?</mat-checkbox>
<mat-checkbox [formField]="accountForm.isReconcilable" class="flex-auto">Is Reconcilable?</mat-checkbox>
</div>
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Cost Centre</mat-label>
<mat-select [formField]="accountForm.costCentre">
@for (cs of costCentres(); track cs) {
<mat-option [value]="cs.id">
{{ cs.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</form>
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Code</mat-label>
<input matInput formControlName="code" />
</mat-form-field>
<button mat-raised-button color="primary" (click)="save()" class="">Save</button>
@if (!!item().id) {
<button mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
}
</div>
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Name</mat-label>
<input matInput #nameElement formControlName="name" />
</mat-form-field>
</div>
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Account Type</mat-label>
<mat-select formControlName="type">
@for (at of accountTypes; track at) {
<mat-option [value]="at.id">
{{ at.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="row-container">
<mat-checkbox formControlName="isActive" class="flex-auto">Is Active?</mat-checkbox>
<mat-checkbox formControlName="isReconcilable" class="flex-auto">Is Reconcilable?</mat-checkbox>
</div>
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Cost Centre</mat-label>
<mat-select formControlName="costCentre">
@for (cs of costCentres; track cs) {
<mat-option [value]="cs.id">
{{ cs.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</form>
<div class="row-container">
<button mat-raised-button color="primary" (click)="save()" class="">Save</button>
@if (!!item.id) {
<button mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
}
</div>
}
@@ -1,5 +1,5 @@
import { AfterViewInit, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { Component, inject, afterNextRender, linkedSignal, input, computed } from '@angular/core';
import { form, FormField, FormRoot } from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatOptionModule } from '@angular/material/core';
@@ -9,13 +9,24 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { Account } from '../../core/account';
import { AccountType } from '../../core/account-type';
import { AccountService } from '../../core/account.service';
import { CostCentre } from '../../core/cost-centre';
import { CostCentreService } from '../../cost-centre/cost-centre.service';
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
import { ErrorStateComponent } from '../../shared/error-state/error-state.component';
import { SkeletonLoaderComponent } from '../../shared/skeleton-loader/skeleton-loader.component';
import { AccountTypeService } from '../account-type.service';
export interface AccountDetailFormData {
code: string;
name: string;
type: number;
isActive: boolean;
isReconcilable: boolean;
costCentre: string;
}
@Component({
selector: 'app-account-detail',
@@ -23,79 +34,59 @@ import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dial
styleUrls: ['./account-detail.component.css'],
imports: [
MatIconModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatOptionModule,
MatCheckboxModule,
MatButtonModule,
FormField,
FormRoot,
SkeletonLoaderComponent,
ErrorStateComponent,
],
})
export class AccountDetailComponent implements OnInit, AfterViewInit {
private route = inject(ActivatedRoute);
export class AccountDetailComponent {
private router = inject(Router);
private dialog = inject(MatDialog);
private snackBar = inject(MatSnackBar);
private ser = inject(AccountService);
private accountTypeService = inject(AccountTypeService);
private costCentreService = inject(CostCentreService);
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
@ViewChild('nameElement', { static: true }) nameElement!: ElementRef<HTMLInputElement>;
form: FormGroup<{
code: FormControl<number | string>;
name: FormControl<string | null>;
type: FormControl<number>;
isActive: FormControl<boolean>;
isReconcilable: FormControl<boolean>;
costCentre: FormControl<string | null>;
}>;
itemResource = this.ser.get(this.id);
accountTypes: AccountType[] = [];
costCentres: CostCentre[] = [];
item: Account = new Account();
item = computed(() => this.itemResource.value() ?? new Account());
accountModel = linkedSignal({
source: this.item,
computation: (itemVal): AccountDetailFormData => {
return {
code: (itemVal.code || '(Auto)').toString(),
name: itemVal.name || '',
type: itemVal.type ?? 0,
isActive: itemVal.isActive ?? true,
isReconcilable: itemVal.isReconcilable ?? false,
costCentre: itemVal.costCentre?.id ?? '',
};
},
});
accountForm = form(this.accountModel);
accountTypesResource = this.accountTypeService.list();
costCentresResource = this.costCentreService.list();
accountTypes = computed(() => this.accountTypesResource.value() || []);
costCentres = computed(() => this.costCentresResource.value() || []);
constructor() {
this.form = new FormGroup({
code: new FormControl<string | number>({ value: 0, disabled: true }, { nonNullable: true }),
name: new FormControl<string | null>(null),
type: new FormControl<number>(0, { nonNullable: true }),
isActive: new FormControl<boolean>(true, { nonNullable: true }),
isReconcilable: new FormControl<boolean>(false, { nonNullable: true }),
costCentre: new FormControl<string | null>(null),
afterNextRender(() => {
this.accountForm.name().focusBoundControl();
});
}
ngOnInit() {
this.route.data.subscribe((value) => {
const data = value as {
item: Account;
accountTypes: AccountType[];
costCentres: CostCentre[];
};
this.accountTypes = data.accountTypes;
this.costCentres = data.costCentres;
this.showItem(data.item);
});
}
showItem(item: Account) {
this.item = item;
this.form.setValue({
code: this.item.code || '(Auto)',
name: this.item.name || '',
type: this.item.type,
isActive: this.item.isActive,
isReconcilable: this.item.isReconcilable,
costCentre: this.item.costCentre.id ?? '',
});
}
ngAfterViewInit() {
setTimeout(() => {
this.nameElement.nativeElement.focus();
}, 0);
}
save() {
this.ser.saveOrUpdate(this.getItem()).subscribe({
next: () => {
@@ -103,19 +94,19 @@ export class AccountDetailComponent implements OnInit, AfterViewInit {
this.router.navigateByUrl('/accounts');
},
error: (error) => {
this.snackBar.open(error, 'Danger');
this.snackBar.open(error as string, 'Danger');
},
});
}
delete() {
this.ser.delete(this.item.id as string).subscribe({
this.ser.delete(this.item().id as string).subscribe({
next: () => {
this.snackBar.open('', 'Success');
this.router.navigateByUrl('/accounts');
},
error: (error) => {
this.snackBar.open(error, 'Danger');
this.snackBar.open(error as string, 'Danger');
},
});
}
@@ -134,12 +125,13 @@ export class AccountDetailComponent implements OnInit, AfterViewInit {
}
getItem(): Account {
const formModel = this.form.value;
this.item.name = formModel.name ?? '';
this.item.type = formModel.type ?? 0;
this.item.isActive = formModel.isActive ?? true;
this.item.isReconcilable = formModel.isReconcilable ?? false;
this.item.costCentre.id = formModel.costCentre ?? '';
return this.item;
const formModel = this.accountModel();
const item = this.item();
item.name = formModel.name ?? '';
item.type = formModel.type ?? 0;
item.isActive = formModel.isActive ?? true;
item.isReconcilable = formModel.isReconcilable ?? false;
item.costCentre.id = formModel.costCentre ?? '';
return item;
}
}
@@ -1,18 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ResolveFn } from '@angular/router';
import { Account } from '../core/account';
import { accountListResolver } from './account-list.resolver';
describe('accountListResolver', () => {
const executeResolver: ResolveFn<Account[]> = (...resolverParameters) =>
TestBed.runInInjectionContext(() => accountListResolver(...resolverParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeResolver).toBeTruthy();
});
});
@@ -1,9 +0,0 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { Account } from '../core/account';
import { AccountService } from '../core/account.service';
export const accountListResolver: ResolveFn<Account[]> = () => {
return inject(AccountService).list();
};
@@ -1,101 +0,0 @@
import { DataSource } from '@angular/cdk/collections';
import { EventEmitter } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSort, Sort } from '@angular/material/sort';
import { merge, Observable, of as observableOf } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { Account } from '../../core/account';
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
export class AccountListDataSource extends DataSource<Account> {
private filterValue = '';
constructor(
public data: Account[],
private filter: Observable<string>,
private paginator?: MatPaginator,
private sort?: MatSort,
) {
super();
this.filter = filter.pipe(
tap((x) => {
this.filterValue = x;
}),
);
}
connect(): Observable<Account[]> {
const dataMutations: (EventEmitter<PageEvent> | EventEmitter<Sort>)[] = [];
if (this.paginator) {
dataMutations.push((this.paginator as MatPaginator).page);
}
if (this.sort) {
dataMutations.push((this.sort as MatSort).sortChange);
}
return merge(observableOf(this.data), this.filter, ...dataMutations)
.pipe(
map(() => this.getFilteredData([...this.data])),
tap((x: Account[]) => {
if (this.paginator) {
this.paginator.length = x.length;
}
}),
)
.pipe(map((x: Account[]) => this.getPagedData(this.getSortedData(x))));
}
disconnect() {}
private getFilteredData(data: Account[]): Account[] {
return this.filterValue.split(' ').reduce(
(p: Account[], c: string) =>
p.filter((x) => {
const accountString = `${x.name} ${x.type} ${x.costCentre}${
x.isActive ? ' active' : ' inactive'
}`.toLowerCase();
return accountString.indexOf(c) !== -1;
}),
Object.assign([], data),
);
}
private getPagedData(data: Account[]) {
if (this.paginator === undefined) {
return data;
}
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
return data.splice(startIndex, this.paginator.pageSize);
}
private getSortedData(data: Account[]) {
if (this.sort === undefined) {
return data;
}
if (!this.sort.active || this.sort.direction === '') {
return data;
}
const sort = this.sort as MatSort;
return data.sort((a, b) => {
const isAsc = sort.direction === 'asc';
switch (sort.active) {
case 'name':
return compare(a.name, b.name, isAsc);
case 'type':
return compare(a.typeName, b.typeName, isAsc);
case 'isActive':
return compare(a.isActive, b.isActive, isAsc);
case 'isReconcilable':
return compare(a.isReconcilable, b.isReconcilable, isAsc);
case 'costCentre':
return compare(a.costCentre.name, b.costCentre.name, isAsc);
default:
return 0;
}
});
}
}
@@ -6,61 +6,67 @@
</a>
</h2>
<form [formGroup]="form" class="flex-col">
<form class="flex-col" [formRoot]="accountForm">
<div class="row-container">
<mat-form-field class="flex-auto">
<mat-label>Filter</mat-label>
<input type="text" matInput #filterElement formControlName="filter" autocomplete="off" />
<input type="text" matInput #filterElement [formField]="accountForm.filter" autocomplete="off" />
</mat-form-field>
</div>
</form>
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
<mat-cell *matCellDef="let row">
<a [routerLink]="['/accounts', row.id]">
@if (row.isStarred) {
<mat-icon matSuffix class="gold">star </mat-icon>
}
{{ row.name }}
</a>
</mat-cell>
</ng-container>
@if (listResource.isLoading()) {
<app-skeleton-loader type="table" [count]="1" />
} @else if (listResource.error()) {
<app-error-state [message]="'Failed to load data.'" (retryAction)="listResource.reload()" />
} @else {
<mat-table #table [dataSource]="dataSource()" matSort (matSortChange)="sortData($event)">
<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
<mat-cell *matCellDef="let row">
<a [routerLink]="['/accounts', row.id]">
@if (row.isStarred) {
<mat-icon matSuffix class="gold">star </mat-icon>
}
{{ row.name }}
</a>
</mat-cell>
</ng-container>
<!-- Type Column -->
<ng-container matColumnDef="type">
<mat-header-cell *matHeaderCellDef mat-sort-header>Type</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.typeName }}</mat-cell>
</ng-container>
<!-- Type Column -->
<ng-container matColumnDef="type">
<mat-header-cell *matHeaderCellDef mat-sort-header>Type</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.typeName }}</mat-cell>
</ng-container>
<!-- Is Active? Column -->
<ng-container matColumnDef="isActive">
<mat-header-cell *matHeaderCellDef mat-sort-header>Is Active?</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.isActive }}</mat-cell>
</ng-container>
<!-- Is Active? Column -->
<ng-container matColumnDef="isActive">
<mat-header-cell *matHeaderCellDef mat-sort-header>Is Active?</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.isActive }}</mat-cell>
</ng-container>
<!-- Is Reconcilable? Column -->
<ng-container matColumnDef="isReconcilable">
<mat-header-cell *matHeaderCellDef mat-sort-header>Is Reconcilable?</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.isReconcilable }}</mat-cell>
</ng-container>
<!-- Is Reconcilable? Column -->
<ng-container matColumnDef="isReconcilable">
<mat-header-cell *matHeaderCellDef mat-sort-header>Is Reconcilable?</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.isReconcilable }}</mat-cell>
</ng-container>
<!-- Cost Centre Column -->
<ng-container matColumnDef="costCentre">
<mat-header-cell *matHeaderCellDef mat-sort-header>Cost Centre</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.costCentre.name }}</mat-cell>
</ng-container>
<!-- Cost Centre Column -->
<ng-container matColumnDef="costCentre">
<mat-header-cell *matHeaderCellDef mat-sort-header>Cost Centre</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.costCentre.name }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
<mat-paginator
#paginator
[length]="dataSource.data.length"
[pageIndex]="0"
[pageSize]="50"
[pageSizeOptions]="[25, 50, 100, 250]"
>
</mat-paginator>
<mat-paginator
(page)="handlePageEvent($event)"
[length]="filteredList().length"
[pageIndex]="pageIndex()"
[pageSize]="pageSize()"
[pageSizeOptions]="[25, 50, 100, 250]"
>
</mat-paginator>
}
@@ -1,77 +1,174 @@
import { AfterViewInit, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import {
Component,
inject,
signal,
effect,
computed,
debounced,
input,
linkedSignal,
afterNextRender,
} from '@angular/core';
import { form, FormField, FormRoot } from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
import { MatSortModule, Sort } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Account } from '../../core/account';
import { AccountType } from '../../core/account-type';
import { AccountListDataSource } from './account-list-datasource';
import { AccountService } from '../../core/account.service';
import { ErrorStateComponent } from '../../shared/error-state/error-state.component';
import { SkeletonLoaderComponent } from '../../shared/skeleton-loader/skeleton-loader.component';
import { AccountTypeService } from '../account-type.service';
export interface AccountListFormData {
filter: string;
}
@Component({
selector: 'app-account-list',
templateUrl: './account-list.component.html',
styleUrls: ['./account-list.component.css'],
imports: [
RouterModule,
RouterLink,
MatButtonModule,
MatIconModule,
ReactiveFormsModule,
FormField,
FormRoot,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatSortModule,
MatPaginatorModule,
SkeletonLoaderComponent,
ErrorStateComponent,
],
})
export class AccountListComponent implements OnInit, AfterViewInit {
export class AccountListComponent {
private route = inject(ActivatedRoute);
private router = inject(Router);
private ser = inject(AccountService);
private accountTypeSer = inject(AccountTypeService);
pageSize = signal(50);
pageIndex = signal(0);
sortActive = signal('');
sortDirection = signal('');
q = input('', { transform: (v: string | null | undefined) => v ?? '' });
formModel = linkedSignal({
source: this.q,
computation: (qValue): AccountListFormData => ({ filter: qValue }),
});
@ViewChild('filterElement', { static: true }) filterElement!: ElementRef<HTMLInputElement>;
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
@ViewChild(MatSort, { static: true }) sort!: MatSort;
dataSource: AccountListDataSource;
filter: Observable<string>;
form: FormGroup<{
filter: FormControl<string>;
}>;
accountForm = form(this.formModel);
list: Account[] = [];
accountTypes: AccountType[] = [];
filterSignal = computed(() => this.formModel().filter);
debouncedFilter = debounced(this.filterSignal, 150);
listResource = this.ser.list();
accountTypesResource = this.accountTypeSer.list();
accountTypes = computed(() => this.accountTypesResource.value() ?? []);
list = computed(() => this.listResource.value() ?? []);
listWithTypes = computed(() => {
const data = this.list();
const types = this.accountTypes();
return data.map(
(account) =>
new Account({
...account,
typeName: types.find((t) => t.id === account.type)?.name ?? account.type.toString(),
}),
);
});
filteredList = computed(() => {
const q = (this.debouncedFilter.value() ?? '').trim().toLowerCase();
const data = this.listWithTypes();
if (!q) {
return data;
}
return q.split(' ').reduce(
(p: Account[], c: string) =>
p.filter((x) => {
const accountString = `${x.name} ${x.type} ${x.costCentre}${
x.isActive ? ' active' : ' inactive'
}`.toLowerCase();
return accountString.indexOf(c) !== -1;
}),
[...data],
);
});
sortedList = computed(() => {
const data = this.filteredList();
const active = this.sortActive();
const direction = this.sortDirection();
if (!active || direction === '') {
return data;
}
return [...data].sort((a, b) => {
const isAsc = direction === 'asc';
switch (active) {
case 'name':
return compare(a.name, b.name, isAsc);
case 'type':
return compare(a.typeName, b.typeName, isAsc);
case 'isActive':
return compare(a.isActive, b.isActive, isAsc);
case 'isReconcilable':
return compare(a.isReconcilable, b.isReconcilable, isAsc);
case 'costCentre':
return compare(a.costCentre.name, b.costCentre.name, isAsc);
default:
return 0;
}
});
});
dataSource = computed(() => {
const data = this.sortedList();
const pageIndex = this.pageIndex();
const pageSize = this.pageSize();
return data.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
});
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['name', 'type', 'isActive', 'isReconcilable', 'costCentre'];
constructor() {
this.form = new FormGroup({
filter: new FormControl<string>('', { nonNullable: true }),
afterNextRender(() => {
this.accountForm().focusBoundControl();
});
effect(() => {
const q = this.debouncedFilter.value() ?? '';
this.router.navigate([], {
relativeTo: this.route,
queryParams: { q: q || null },
queryParamsHandling: 'merge',
replaceUrl: true,
});
});
// Listen to Filter Change
this.filter = this.form.controls.filter.valueChanges.pipe(debounceTime(150), distinctUntilChanged());
this.dataSource = new AccountListDataSource(this.list, this.filter, this.paginator, this.sort);
}
ngOnInit() {
this.route.data.subscribe((value) => {
const data = value as { list: Account[]; accountTypes: AccountType[] };
this.accountTypes = data.accountTypes;
data.list.forEach((x) => (x.typeName = this.accountTypes.find((y) => y.id === x.type)?.name ?? ''));
this.list = data.list;
});
this.dataSource = new AccountListDataSource(this.list, this.filter, this.paginator, this.sort);
handlePageEvent(e: PageEvent) {
this.pageSize.set(e.pageSize);
this.pageIndex.set(e.pageIndex);
}
ngAfterViewInit() {
setTimeout(() => {
this.filterElement.nativeElement.focus();
}, 0);
sortData(sort: Sort) {
this.sortActive.set(sort.active);
this.sortDirection.set(sort.direction);
}
}
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
@@ -1,18 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ResolveFn } from '@angular/router';
import { AccountType } from '../core/account-type';
import { accountTypeResolver } from './account-type.resolver';
describe('accountTypeResolver', () => {
const executeResolver: ResolveFn<AccountType[]> = (...resolverParameters) =>
TestBed.runInInjectionContext(() => accountTypeResolver(...resolverParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeResolver).toBeTruthy();
});
});
@@ -1,9 +0,0 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { AccountType } from '../core/account-type';
import { AccountTypeService } from './account-type.service';
export const accountTypeResolver: ResolveFn<AccountType[]> = () => {
return inject(AccountTypeService).list();
};
@@ -1,14 +1,10 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, httpResource } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { catchError } from 'rxjs/operators';
import { AccountType } from '../core/account-type';
import { ErrorLoggerService } from '../core/error-logger.service';
const url = '/api/account-types';
const serviceName = 'AccountTypeService';
@Injectable({
providedIn: 'root',
})
@@ -16,9 +12,7 @@ export class AccountTypeService {
private http = inject(HttpClient);
private log = inject(ErrorLoggerService);
list(): Observable<AccountType[]> {
return this.http.get<AccountType[]>(url).pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<
AccountType[]
>;
list() {
return httpResource<AccountType[]>(() => url);
}
}
@@ -1,18 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ResolveFn } from '@angular/router';
import { Account } from '../core/account';
import { accountResolver } from './account.resolver';
describe('accountResolver', () => {
const executeResolver: ResolveFn<Account> = (...resolverParameters) =>
TestBed.runInInjectionContext(() => accountResolver(...resolverParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeResolver).toBeTruthy();
});
});
@@ -1,10 +0,0 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { Account } from '../core/account';
import { AccountService } from '../core/account.service';
export const accountResolver: ResolveFn<Account> = (route) => {
const id = route.paramMap.get('id');
return inject(AccountService).get(id);
};
@@ -1,12 +1,8 @@
import { Routes } from '@angular/router';
import { authGuard } from '../auth/auth-guard.service';
import { costCentreListResolver } from '../cost-centre/cost-centre-list.resolver';
import { AccountDetailComponent } from './account-detail/account-detail.component';
import { accountListResolver } from './account-list.resolver';
import { AccountListComponent } from './account-list/account-list.component';
import { accountTypeResolver } from './account-type.resolver';
import { accountResolver } from './account.resolver';
export const routes: Routes = [
{
@@ -16,10 +12,6 @@ export const routes: Routes = [
data: {
permission: 'Accounts',
},
resolve: {
list: accountListResolver,
accountTypes: accountTypeResolver,
},
},
{
path: 'new',
@@ -28,11 +20,6 @@ export const routes: Routes = [
data: {
permission: 'Accounts',
},
resolve: {
item: accountResolver,
accountTypes: accountTypeResolver,
costCentres: costCentreListResolver,
},
},
{
path: ':id',
@@ -41,10 +28,5 @@ export const routes: Routes = [
data: {
permission: 'Accounts',
},
resolve: {
item: accountResolver,
accountTypes: accountTypeResolver,
costCentres: costCentreListResolver,
},
},
];