UNPKG

@datorama/akita-ng-entity-service

Version:
402 lines (393 loc) 14.6 kB
import { HttpClient } from '@angular/common/http'; import * as i0 from '@angular/core'; import { Injectable, InjectionToken, inject } from '@angular/core'; import { isNumber, isString, isObject, isFunction, EntityService, isDefined } from '@datorama/akita'; import { filter, Subject, ReplaySubject, map, switchMap, isObservable, of, tap, catchError, finalize, throwError } from 'rxjs'; function successAction(storeName, notifier) { return function ({ payload, method, successMsg }) { notifier.dispatch({ type: 'success', storeName, payload, method: method, successMsg }); }; } function errorAction(storeName, notifier) { return function ({ payload, method, errorMsg }) { notifier.dispatch({ type: 'error', storeName, payload, method: method, errorMsg }); }; } function isID(idOrConfig) { return isNumber(idOrConfig) || isString(idOrConfig); } var HttpMethod; (function (HttpMethod) { HttpMethod["GET"] = "GET"; HttpMethod["POST"] = "POST"; HttpMethod["PUT"] = "PUT"; HttpMethod["PATCH"] = "PATCH"; HttpMethod["DELETE"] = "DELETE"; })(HttpMethod || (HttpMethod = {})); const ofType = (type) => filter((action) => action.type === type); const filterMethod = (method) => filter((action) => action.method === method); const filterStore = (name) => filter((action) => action.storeName === name); class NgEntityServiceNotifier { constructor() { this.dispatcher = new Subject(); this.action$ = this.dispatcher.asObservable(); } dispatch(event) { this.dispatcher.next(event); } } NgEntityServiceNotifier.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: NgEntityServiceNotifier, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); NgEntityServiceNotifier.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: NgEntityServiceNotifier, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: NgEntityServiceNotifier, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); const NG_ENTITY_SERVICE_CONFIG = new InjectionToken('NgEntityServiceGlobalConfig'); const defaultConfig = { httpMethods: { GET: HttpMethod.GET, POST: HttpMethod.POST, PATCH: HttpMethod.PATCH, PUT: HttpMethod.PUT, DELETE: HttpMethod.DELETE, }, }; function mergeDeep(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (isObject(target) && isObject(source)) { for (const key in source) { if (isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); mergeDeep(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return mergeDeep(target, ...sources); } function NgEntityServiceConfig(config = {}) { return function (constructor) { if (config.baseUrl) { constructor['baseUrl'] = config.baseUrl; } if (config.resourceName) { constructor['resourceName'] = config.resourceName; } }; } class NgEntityServiceLoader { constructor() { this.dispatcher = new ReplaySubject(1); this.loading$ = this.dispatcher.asObservable(); } dispatch(event) { this.dispatcher.next(event); } loadersFor(name) { const filterStore = filter(({ storeName }) => (name ? storeName === name : true)); const filterMethod = (mthd) => filter(({ method }) => { return isFunction(mthd) ? mthd(method) : method === mthd; }); const actionBased = (current) => this.loading$.pipe(filterStore, filterMethod(current), map((action) => action.loading)); const idBased = (id, mthd) => this.loading$.pipe(filterStore, filterMethod(mthd), filter((action) => action.entityId === id), map((action) => action.loading)); return { get$: actionBased(HttpMethod.GET), add$: actionBased(HttpMethod.POST), update$: actionBased((method) => method === HttpMethod.PUT || method === HttpMethod.PATCH), delete$: actionBased(HttpMethod.DELETE), getEntity: (id) => idBased(id, HttpMethod.GET), updateEntity: (id) => idBased(id, (method) => method === HttpMethod.PUT || method === HttpMethod.PATCH), deleteEntity: (id) => idBased(id, HttpMethod.DELETE), }; } } NgEntityServiceLoader.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: NgEntityServiceLoader, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); NgEntityServiceLoader.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: NgEntityServiceLoader, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: NgEntityServiceLoader, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); const mapResponse = (config) => switchMap((res) => { const mappedResponse = (config === null || config === void 0 ? void 0 : config.mapResponseFn) ? config.mapResponseFn(res) : res; return isObservable(mappedResponse) ? mappedResponse : of(mappedResponse); }); class NgEntityService extends EntityService { constructor(store, config = {}) { super(); this.store = store; this.config = config; this.http = inject(HttpClient); this.loader = inject(NgEntityServiceLoader); this.notifier = inject(NgEntityServiceNotifier); const globalConfig = inject(NG_ENTITY_SERVICE_CONFIG); this.mergedConfig = mergeDeep({}, defaultConfig, globalConfig, this.getDecoratorConfig(), config); this.baseUrl = this.mergedConfig.baseUrl; this.httpMethodMap = this.mergedConfig.httpMethods; this.dispatchSuccess = successAction(this.store.storeName, this.notifier); this.dispatchError = errorAction(this.store.storeName, this.notifier); } get api() { if (!this.baseUrl) { throw new Error(`baseUrl of ${this.constructor.name} is not defined.`); } return `${this.baseUrl}/${this.resourceName}`; } get resourceName() { return this.mergedConfig.resourceName || this.store.storeName; } setBaseUrl(baseUrl) { this.baseUrl = baseUrl; } getHttp() { return this.http; } getConfig() { return this.mergedConfig; } get(idOrConfig, config) { const method = this.getHttpMethod(HttpMethod.GET); const isSingle = isID(idOrConfig); const entityId = isSingle ? idOrConfig : undefined; const conf = (!isSingle ? idOrConfig : config) || {}; const url = this.resolveUrl(conf, entityId); this.loader.dispatch({ method, loading: true, entityId, storeName: this.store.storeName, }); return this.http.request(method, url, conf).pipe(mapResponse(conf), tap((data) => { if (!conf.skipWrite) { if (isSingle) { this.store.upsert(entityId, data, (id, newState) => (Object.assign({ id }, newState))); } else { if (conf.append) { this.store.add(data); } else if (conf.upsert) { this.store.upsertMany(data); } else { this.store.set(data); } } } this.dispatchSuccess({ method, payload: data, successMsg: conf.successMsg, }); }), catchError((error) => this.handleError(method, error, conf.errorMsg)), finalize(() => { this.loader.dispatch({ method, loading: false, entityId, storeName: this.store.storeName, }); })); } /** * Add a new entity - Creates a POST request * * @example * service.add(entity).subscribe() * service.add(entity, config).subscribe() */ add(entity, config) { const url = this.resolveUrl(config); const method = this.getHttpMethod(HttpMethod.POST); this.loader.dispatch({ method, loading: true, storeName: this.store.storeName, }); const configWithBody = Object.assign(Object.assign({}, config), { body: entity }); return this.http.request(method, url, configWithBody).pipe(mapResponse(config), tap((responseEntity) => { if (!config || (config && !config.skipWrite)) { this.store.add(responseEntity, config); } this.dispatchSuccess({ method, payload: responseEntity, successMsg: config && config.successMsg, }); }), catchError((error) => this.handleError(method, error, config && config.errorMsg)), finalize(() => { this.loader.dispatch({ method, loading: false, storeName: this.store.storeName, }); })); } /** * Update an entity - Creates a PUT/PATCH request * * @example * service.update(id, entity).subscribe() * service.update(id, entity, config).subscribe() */ update(id, entity, config) { const url = this.resolveUrl(config, id); const method = (config && config.method) || this.getHttpMethod(HttpMethod.PUT); this.loader.dispatch({ method, loading: true, entityId: id, storeName: this.store.storeName, }); const configWithBody = Object.assign(Object.assign({}, config), { body: entity }); return this.http.request(method, url, configWithBody).pipe(mapResponse(config), tap((responseEntity) => { if (!config || (config && !config.skipWrite)) { this.store.update(id, responseEntity); } this.dispatchSuccess({ method, payload: responseEntity, successMsg: config && config.successMsg, }); }), catchError((error) => this.handleError(method, error, config && config.errorMsg)), finalize(() => { this.loader.dispatch({ method, loading: false, entityId: id, storeName: this.store.storeName, }); })); } /** * Delete an entity - Creates a DELETE request * * @example * service.delete(id).subscribe() * service.delete(id, config).subscribe() */ delete(id, config) { const url = this.resolveUrl(config, id); const method = this.getHttpMethod(HttpMethod.DELETE); this.loader.dispatch({ method, loading: true, entityId: id, storeName: this.store.storeName, }); return this.http.request(method, url, config).pipe(mapResponse(config), tap((res) => { if (!config || (config && !config.skipWrite)) { this.store.remove(id); } this.dispatchSuccess({ method, payload: res, successMsg: config && config.successMsg, }); }), catchError((error) => this.handleError(method, error, config && config.errorMsg)), finalize(() => { this.loader.dispatch({ method, loading: false, entityId: id, storeName: this.store.storeName, }); })); } /** * Gets the mapped HttpMethod. * * The default HttpMethod can be changed like so: * ```ts * { * provide: NG_ENTITY_SERVICE_CONFIG, * useValue: { * httpMethods: { * PUT: HttpMethod.PATCH, * }, * } as NgEntityServiceGlobalConfig, * } * ``` * * @param type HttpMethod to get the user configured HttpMethod for * @returns User configured HttpMethod for the method, else the default HttpMethod */ getHttpMethod(type) { let httpMethod; if (this.httpMethodMap) { httpMethod = this.httpMethodMap[type]; } if (!httpMethod) { throw new Error('Unknown HttpMethod'); } return httpMethod; } /** * Gets the value given via the NgEntityServiceConfig decorator * * ```ts * @NgEntityServiceConfig({ * baseUrl: 'foo', * resourceName: 'bar', * }) * ``` * * @param key The property key * @returns The value of the given decorator key */ getDecoratorValue(key) { return this.constructor[key]; } getDecoratorConfig() { const config = {}; const baseUrl = this.getDecoratorValue('baseUrl'); if (baseUrl) { config.baseUrl = baseUrl; } const resourceName = this.getDecoratorValue('resourceName'); if (resourceName) { config.resourceName = resourceName; } return config; } resolveUrl(config, id) { const { url, urlPostfix } = Object(config); let final = this.api; if (url) { return url; } if (isDefined(id)) { final += `/${id}`; } if (urlPostfix) { final += `/${urlPostfix}`; } return final; } handleError(method, error, errorMsg) { this.dispatchError({ method, errorMsg, payload: error, }); return throwError(error); } } /** * Generated bundle index. Do not edit. */ export { HttpMethod, NG_ENTITY_SERVICE_CONFIG, NgEntityService, NgEntityServiceConfig, NgEntityServiceLoader, NgEntityServiceNotifier, defaultConfig, errorAction, filterMethod, filterStore, isID, mapResponse, mergeDeep, ofType, successAction }; //# sourceMappingURL=datorama-akita-ng-entity-service.mjs.map //# sourceMappingURL=datorama-akita-ng-entity-service.mjs.map