Muitas vezes, em um projeto, o ritmo de desenvolvimento do front-end está à frente do ritmo de desenvolvimento do back-end. Nesta situação, duas coisas surgem:- a capacidade de iniciar a frente sem um back-end ou sem pontos de extremidade separados;
- Descreva para o back-end quais pontos de extremidade são necessários, o formato da solicitação, resposta etc.
Quero compartilhar minha maneira de organizar o código responsável pelas solicitações de API, que resolve perfeitamente esses dois problemas e também permite que você armazene em cache as solicitações.Crie o arquivo de configuração api.config.json na raiz do projeto:{
"apiUrl": "https://api.example.com",
"useFakeApiByDefault": false,
"fakeEndPoints": ["Sample"]
}
Aqui, definimos o URL base da API, se o parâmetro useFakeApiByDefault = true, nosso aplicativo usará apenas stubs em vez de todas as solicitações. Se falso, os stubs serão usados apenas para solicitações da matriz fakeEndPoints.Para importar JSON para o código, adicionamos duas linhas à seção CompilerOptions do arquivo tsconfig.json: "resolveJsonModule": true,
"esModuleInterop": true,
Crie a classe base BaseEndpoint./src/app/api/_base.endpoint.ts:import {ApiMethod, ApiService} from '../services/api/api.service';
import * as ApiConfig from '../../../api.config.json';
import {ErrorService} from '../services/error/error.service';
import {NgModule} from '@angular/core';
@NgModule({providers: [ApiService, ErrorService]})
export abstract class BaseEndpoint<Request, ResponseModel> {
protected url: string;
protected name: string;
protected method: ApiMethod = ApiMethod.Post;
protected sampleResponse: ResponseModel;
protected cache: boolean = false;
protected responseCache: ResponseModel = null;
constructor(private api: ApiService, private error: ErrorService) {
}
public execute(request: Request): Promise<ResponseModel> {
if (this.cache && this.responseCache !== null) {
return new Promise<ResponseModel>((resolve, reject) => {
resolve(this.responseCache);
});
} else {
if (ApiConfig.useFakeApiByDefault || ApiConfig.fakeEndPoints.includes(this.name)) {
console.log('Fake Api Request:: ', this.name, request);
console.log('Fake Api Response:: ', this.sampleResponse);
return new Promise<ResponseModel>((resolve) => resolve(this.sampleResponse));
} else {
return new Promise<ResponseModel>((resolve, reject) => {
this.api.execute(this.url, this.method, request).subscribe(
(response: Response<ResponseModel>) => {
if (response.status === 200) {
if (this.cache) { this.responseCache = response.payload; }
resolve(this.payloadMap(response.payload));
} else {
this.error.emit(response.error);
reject(response.error);
}
}, response => {
this.error.emit(' ')
reject(' '));
}
});
}
}
}
protected payloadMap(payload: ResponseModel): ResponseModel { return payload; }
}
abstract class Response<T> {
public status: number;
public error: string;
public payload: T;
}
<Request, ResponseModel> - tipos de instâncias de solicitação e resposta. Em resposta, a carga útil já foi retirada.Não postarei as classes ApiService e ErrorService, para não inflar a postagem, não há nada de especial nela. O ApiService envia solicitações HTTP, o ErrorService permite que você assine erros. Para exibir erros, você precisa se inscrever no ErrorService no componente em que realmente queremos exibir erros (assino o layout principal no qual faço um modal ou uma dica de ferramenta).A magia começa quando herdamos dessa classe. A classederivada terá a seguinte aparência: /src/app/api/get-users.endpoint.tsimport {BaseEndpoint} from './_base.endpoint';
import {Injectable} from '@angular/core';
import {UserModel} from '../models/user.model';
@Injectable()
export class GetUsersEndpoint extends BaseEndpoint<GetUsersRequest, UserModel[]> {
protected name = 'GetUsers';
protected url= '/getUsers';
protected sampleResponse = [{
id: 0,
name: 'Ivan',
age: 18
},
{
id: 1,
name: 'Igor',
age: 25
}
protected payloadMap(payload) {
return payload.map(user => new UserModel(user));
}
}
export class GetUsersRequest {
page: number;
limit: number;
}
Nada mais, do conteúdo do arquivo fica imediatamente claro qual URL, qual formato de solicitação (GetUserRequest), qual formato de resposta.Se esses arquivos estiverem em uma pasta / api separada, cada arquivo corresponder a seu próprio ponto de extremidade, acho que você pode mostrar essa pasta para o back-end e, teoricamente, se você tiver preguiça de escrever documentação na API, poderá fazê-lo sem ela. Você ainda pode espalhar arquivos dentro de pastas / api em pastas de acordo com os controladores.Se você adicionar 'GetUsers' à matriz de configuração "fakeEndPoints", não haverá solicitação e a resposta será substituída pelos dados de stub do sampleResponse.Para que solicitações falsas sejam depuradas (na guia Rede, é claro, não veremos nada), forneci a saída do console para gerar duas linhas na classe do console:console.log('Fake Api Request:: ', this.name, request);
console.log('Fake Api Response:: ', this.sampleResponse);
Se você substituir a propriedade da classe cache = true, a solicitação será armazenada em cache (na primeira vez que uma solicitação para a API for feita, o resultado da primeira solicitação será sempre retornado). A verdade vale a pena refinar: você precisa fazer o cache funcionar apenas se os parâmetros de solicitação (o conteúdo de uma instância da classe UserRequest).Redefinimos o método payloadMap se precisarmos de transformações nos dados recebidos do servidor. Se o método não for redefinido, os dados da carga útil estarão na promessa retornada.Agora, obtemos os dados da API no componente:import {Component, OnInit, ViewChild} from '@angular/core';
import {UserModel} from '../../models/user.model';
import {GetUsersEndpoint, GetUsersRequest} from '../../api/get_users.endpoint';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css'],
providers: [GetUsersEndpoint]
})
export class UsersComponent implements OnInit {
public users: UserModel[];
public request: GetUsersRequest = {page: 1, limit: 20};
constructor(private getUsersEndpoint: GetUsersEndpoint) {
}
private load() {
this.getUsersEndpoint.execute(this.request).then(data => {
this.users = data;
});
}
ngOnInit(): void {
this.load();
}
}
Em tal implementação, é possível mostrar ao cliente o resultado da conclusão de sua Lista de desejos, mesmo que o back-end ainda não tenha sido concluído para essa Lista de desejos. Coloque os pontos de extremidade necessários no esboço - e você já pode "tocar" nos recursos e obter um feedback.Nos comentários, espero críticas construtivas sobre quais problemas podem surgir ao criar funcionalidades, que outras armadilhas podem surgir.No futuro, quero reescrever esta solução de promessas para assíncrono / aguardar. Eu acho que o código será ainda mais elegante.Em estoque, existem várias outras soluções arquitetônicas para a Angular. Em breve, pretendo compartilhar.