angular - Entendendo Mat Table e DataSource em relação ao tratamento de erros
Estou tentando lidar com erros de backend que possivelmente podem surgir ao buscar dados para minha MatTable
Pelo que eu entendo, vai assim
service --> FonteDeDados --> componente
Eu quero que meus erros cheguem ao componente para que eu possa (possivelmente) exibi-los para o usuário.
O comportamento que estou vendo é: o datasource não "vê" o erro e simplesmente não retorna nada para o componente quando ocorre um erro. Estou completamente entendendo errado como esse sistema deveria funcionar. A maioria dos exemplos que vi usa o seguinte código em sua implementação do DataSource:
loadContact() {
this.loadingSubject.next(true);
this.contactsService
.getContact()
.pipe(
catchError(() => of([])) // <----- what is going on here?
finalize(() => this.loadingSubject.next(false)),
)
.subscribe((contacts) => this.contactSubject.next(contacts));
}
Eu pensei que talvez, se eu lançasse um erro no service, ele seria capturado aqui no catchError, mas esse não é o caso. Nenhum dos exemplos que pude encontrar realmente trata erros de forma alguma e não tenho certeza do que o catchError realmente faz nesse caso.
Aqui está a minha implementação:
contato.servico.ts
import { Injectable } from '@angular/core';
import { Apollo } from 'apollo-angular';
import { Observable, Subscription, throwError } from 'rxjs';
import { GET_ORGANIZATION_CONTACT_QUERY } from '../shared/queries/contact';
import { ErrorHandlerService } from './error-handler/error-handler.service';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class ContactService {
getContactsSub: Subscription;
constructor(
private apollo: Apollo,
private errorHandler: ErrorHandlerService,
) {}
getContact(): Observable<any> {
const query = GET_ORGANIZATION_CONTACT_QUERY;
// const variables = { organizationId: 6 };
const variables = { organizationId: 69 }; // forces backend error for testing
return this.apollo
.watchQuery<any>({ query, variables })
.valueChanges.pipe(
map(
(response) => {
const { data, errors } = response;
if (errors) {
throw throwError(this.errorHandler.handleErrors(errors));
}
const { contact = {} } = data;
return contact;
},
(error) => {
throw throwError(error);
},
),
);
}
}
error-handler.service.ts
/*
basically maps errors to specific messages I want to display to users.
*/
import { Injectable } from '@angular/core';
import { Errors } from './error';
@Injectable({ providedIn: 'root' })
export class ErrorHandlerService {
errors: object = Errors;
constructor() { }
handleErrors(errors: any): string {
const errorCode: string = errors[0].extensions.code;
let errorMessage: string = '';
// construct a string of relevant error messages to be passed to the
// user
Object.keys(this.errors).map((key) => {
if (errorCode === key) {
errorMessage += this.errors[key].message;
}
});
return errorMessage;
}
}
erro.ts
export const Errors = {
NOT_FOUND: { message: 'Not found' },
CONTACT_NOT_FOUND: { message: 'Contact could not be found' },
EMAIL_ALREADY_EXISTS: { message: 'Email already exists' },
ENTITY_ALREADY_EXISTS: { message: 'Entity already exists' },
ENTITY_NOT_FOUND: { message: 'Entity could not be found' },
INVALID_COUNTRY_CODE: { message: 'Invalid country' },
INVALID_HOME_JURISDICTION: { message: 'Invalid home jurisdiction' },
INVALID_ORG_TYPE: { message: 'Invalid organization type' },
INVALID_PROV_OR_STATE: { message: 'Invalid province or state' },
INVALID_REQUEST: { message: 'Invalid request' },
INVALID_USER_ROLE: { message: 'Invalid user role' },
OKTA_ERROR: { message: 'Okta error' },
ORG_NOT_FOUND: { message: 'Organization could not be found' },
ORG_ALREADY_ENTITY: {
message: 'Organization is already defined as an Entity',
},
ORG_POSITION_NOT_FOUND: {
message: 'Organization position could not be found',
},
ORG_WITH_CAN_ALREADY_EXISTS: {
message: 'Organization with given can number already exists',
},
ORG_WITH_NAME_AND_JURISDICTION_ALREADY_EXISTS: {
message:
'Organization with given name and jurisdiction already exists',
},
POSITION_NOT_FOUND: { message: 'Position could not be found' },
TOKEN_NOT_FOUND: { message: 'Token could not be found' },
TOKEN_WITH_EMAIL_ALREADY_EXISTS: {
message: 'Token with given email already exists',
},
UNHANDLED_EXCEPTION: { message: 'Unhandled exception' },
USER_ALREADY_EXISTS: { message: 'User already exists' },
USER_NOT_FOUND: { message: 'User could not be found' },
};
fonte-de-dados-de-contato.ts
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { ContactService } from './contact.service';
import { catchError, finalize } from 'rxjs/operators';
export class ContactDataSource implements DataSource<any> {
private contactSubject = new BehaviorSubject<Array<any>>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);
public loading$ = this.loadingSubject.asObservable();
constructor(private contactsService: ContactService) {}
connect(collectionViewer: CollectionViewer): Observable<Array<any>> {
return this.contactSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.contactSubject.complete();
this.loadingSubject.complete();
}
loadContact() {
this.loadingSubject.next(true);
this.contactsService
.getContact()
.pipe(
catchError((error) => {
// this code is never executed
console.log('inside loadContact; error:', error);
return of(error);
}),
finalize(() => this.loadingSubject.next(false)),
)
.subscribe((contacts) => this.contactSubject.next(contacts));
}
}
contatos.componente.ts
import {
AfterViewInit,
Component,
OnInit,
ViewChild,
} from '@angular/core';
import { CrumbModel } from '../../shared/models/crumb.model';
import {
MatDialog,
MatTableDataSource,
MatPaginator,
} from '@angular/material';
import { FormGroup } from '@angular/forms';
import { ContactDataSource } from 'src/app/services/contact-data-source';
import { ContactService } from 'src/app/services/contact.service';
import { contactFilter } from 'src/app/shared/filters/contact.filter';
import { AddContactComponent } from 'src/app/shared/components/add-contact/add-contact.component';
import { Observable } from 'apollo-link';
import { Subject, BehaviorSubject } from 'rxjs';
@Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.scss'],
})
export class ContactsComponent implements OnInit, AfterViewInit {
public dataSource = new MatTableDataSource<ContactDataSource>();
public crumbs: CrumbModel[];
@ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
constructor(
public dialog: MatDialog,
public contactService: ContactService,
) {}
async ngOnInit() {
this.dataSource.filterPredicate = contactFilter;
this.crumbs = [
{ label: 'Contacts', link: '/contacts' },
{ label: 'Contact Name', link: '/contacts/' },
{ label: 'Contact Detail', link: '' },
];
this.contactService.getContact().subscribe(
(response) => {
// this code is never executed when an error occurs
// this is where I'd like to see the error message and display it
console.log('response:', response);
let contacts = [];
contacts.push(response);
this.dataSource.data = contacts;
},
(error) => {
error.subscribe((error) => console.error(error));
},
);
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
}
handleOnSearchChange(value: string) {
this.dataSource.filter = value.trim().toLocaleLowerCase();
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
handleSortChange() {}
openDialog(): void {
const dialogRef = this.dialog.open(AddContactComponent, {
width: '760px',
data: {},
});
dialogRef
.afterClosed()
.subscribe((result: FormGroup | undefined) => {});
}
}