本篇開始把Demo替換成firebase。
web.service.ts
首先是基本的CRUD連線方式。
export class WebService {
constructor(private logger: LoggerService) {}
getF<T>(afc: AngularFirestoreCollection): Observable<T[]> {
return afc.snapshotChanges().pipe(
map((actions: DocumentChangeAction<T>[]) => {
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
return { id, ...data };
});
}),
tap(res => this.logger.print("response", res)),
catchError(this.handleError)
);
}
getO<T>(afc: AngularFirestoreCollection, id: string): Observable<T> {
return afc
.doc(id)
.valueChanges()
.pipe(
map((action: T) => {
return action;
}),
tap(res => this.logger.print("response one", res)),
catchError(this.handleError)
);
}
postF<T>(afd: AngularFirestoreDocument, data: T): Observable<T> {
return from(afd.set(data)).pipe(
tap(res => this.logger.print("create", res)),
catchError(this.handleError)
);
}
patchF<T>(afd: AngularFirestoreDocument, data: T): Observable<T> {
return from(afd.update(data)).pipe(
tap(res => this.logger.print("update", res)),
catchError(this.handleError)
);
}
delF<T>(afd: AngularFirestoreDocument): Observable<T> {
return from(afd.delete()).pipe(
tap(res => this.logger.print("delete", res)),
catchError(this.handleError)
);
}
private handleError(error: Response | any): Observable<any> {
console.error(`${error.status}`);
return of(null);
}
}
data.service.ts
export interface IFilter {
key: string;
val: string | number;
}
export interface ICount {
count: number;
}
@Injectable({
providedIn: "root"
})
export class DataService {
constructor(
private webService: WebService,
private afs: AngularFirestore
) {}
connect(token: string): Observable<IData> {
let link = {
join: "holds",
key: "adminId"
};
let query = this.setQuery([{ key: "token", val: token }]);
return this.webService.getF(this.afs.collection("admins", query))
.pipe(
this.docJoinFilter(link),
map((res: IModel[]) => {
let errocode = 0;
if (!res || !res.length) {
errocode = 2;
}
if (!!res[0]) {
let user = <IUser>res[0];
if (user.status != 1) {
errocode = 3;
}
}
return this.resData(res, errocode);
})
);
}
resData(res: IModel[] | IModel, errocode: number): IData {
return <IData>{
res: res,
errorcode: errocode
};
}
setQuery(filters: IFilter[]): QueryFn {
let query: QueryFn = null;
filters.filter((filter: IFilter) => {
switch (filter.key) {
case "inserted_gte":
query = query => query.where(
"inserted",
">",
<number>filter.val
);
break;
case "inserted_lte":
query = query => query.where(
"inserted",
"<",
<number>filter.val
);
break;
default:
query = query => query.where(filter.key, "==", filter.val);
break;
}
});
return query;
}
getData(
url: string,
id?: number,
filters?: IFilter[],
pageObj?: IPage,
link?: { join: string; key: string }
): Observable<IData> {
if (!!id) {
return this.getOne(url, id, link);
}
if (!!filters && !!filters.length) {
let query = this.setQuery(filters);
return this.getList(
this.afs.collection(url, query),
pageObj,
link
);
}
return this.getList(this.afs.collection(url), pageObj, link);
}
getOne(url: string, id: number, link?: { join: string; key: string })
: Observable<IData>
{
return this.webService
.getO(this.afs.collection(url), id.toString())
.pipe(
map((res: IModel) => {
if (!res) {
res = <IModel>{};
}
return res;
}),
this.docJoinFilterOne(id, link),
map((res: IModel) => {
res.id = id;
return res;
}),
map((res: IModel) => {
return this.resData(res, 0);
})
);
}
getList(
afc: AngularFirestoreCollection,
pageObj?: IPage,
link?: { join: string; key: string }
): Observable<IData> {
return this.webService.getF(afc).pipe(
map((res: IModel[]) => {
if (!!pageObj) {
pageObj.length = res.length;
res["pageObj"] = pageObj;
res = this.rangeData(res, pageObj);
}
return res;
}),
this.docJoin(link),
map((res: IModel[]) => {
res.forEach((model: IModel, index: number) => {
model.id = +model.id;
});
return res;
}),
map((res: IModel[]) => {
return this.resData(res, 0);
})
);
}
insertOne(url: string, obj: IModel): Observable<IData> {
let countAfc = this.afs.collection("counters");
let afc = this.afs.collection(url);
const count$ = this.webService.getO(countAfc, url)
.pipe(
map((action: ICount) => {
return action.count;
}),
take(1)
);
const countUpdate$ = count$.pipe(
concatMap((id: number) => {
return this.webService
.patchF(countAfc.doc(url), { count: ++id })
.pipe(
map(() => {
return id;
})
);
})
);
const insert$ = countUpdate$.pipe(
concatMap((id: number) =>
this.webService
.postF(afc.doc(id.toString()), obj)
.pipe(
map(() => {
let obj=<IModel>{
id:id
}
return this.resData(obj, 0);
})
)
)
);
return insert$;
}
updateOne(url: string, obj: IModel, id: number): Observable<IData> {
return this.webService
.patchF(this.afs.collection(url).doc(id.toString()), obj)
.pipe(
map(() => {
return this.resData(obj, 0);
})
);
}
deleteOne(url: string, id: number): Observable<IData> {
return this.webService
.delF(this.afs.collection(url).doc(id.toString()))
.pipe(
map(() => {
return this.resData(null, 0);
})
);
}
//加上新增或是更新日期
checkData(obj: IModel, operatorId: number, isInsert = true): IModel {
let insert = <IModel>{
insertBy: +operatorId,
inserted: new Date().getTime()
};
let update = <IModel>{
updateBy: +operatorId,
updated: new Date().getTime()
};
if (isInsert) {
obj = { ...obj, ...insert, ...update };
} else {
//update
obj = { ...obj, ...update };
}
return obj;
}
rangeData(obj: IModel[], pageObj: IPage): IModel[] {
let start = pageObj.pageIndex * pageObj.pageSize;
let end = start + pageObj.pageSize;
if (pageObj.length < end) {
end = pageObj.length;
}
return obj.slice(start, end);
}
makeToken(account: string): string {
return <string>Md5.hashStr(`${account}`);
}
/**Join */
docJoin =
(link?: { join: string; key: string })=> (obs: Observable<IModel[]>)
=> obs.pipe(
switchMap((res: IModel[]) => {
if (!!res && !!res.length && !!link) {
return combineLatest(
res.map((model: IModel) =>
this.webService
.getO(
this.afs.collection<IModel>(link.join),
model[link.key].toString()
)
.pipe(
map((joinObj: IModel) => {
let o = {};
let t = link.join.substring(0, link.join.length - 1);
o[t] = joinObj;
return { ...model, ...o };
})
)
)
);
} else {
return of(res);
}
})
);
docJoinFilter =
(link?: { join: string; key: string }) => (obs: Observable<IModel[]>)
=> obs.pipe(
switchMap((res: IModel[]) => {
if (!!res && !!res.length && !!link) {
return combineLatest(
res.map((model: IModel) => {
let query = this.setQuery(
[{ key: link.key, val: +model.id }]
);
return this.webService
.getF(this.afs.collection(link.join, query))
.pipe(
map((joinObj: IModel[]) => {
let o = {};
o[link.join] = joinObj;
return { ...model, ...o };
})
);
})
);
} else {
return of(res);
}
})
);
docJoinFilterOne =
(id: number, link?: { join: string; key: string })
=> ( obs: Observable<IModel>)
=> obs.pipe(
switchMap((res: IModel) => {
if (!!res && !!link) {
let query = this.setQuery([{ key: link.key, val: id }]);
return this.webService
.getF(this.afs.collection(link.join, query))
.pipe(
map((joinObj: IModel[]) => {
let o = {};
o[link.join] = joinObj;
return { ...res, ...o };
})
);
} else {
return of(res);
}
})
);
}
首先 DataService 要分幾塊說明:
link?: { join: string; key: string }
//購物車表關聯商品表,關鍵字為expand
setCars() {
let filters: IFilter[] = [
{ key: "orderId", val: this.data.order.id },
{ key: "_expand", val: "product" }
];
this.dataService.getData("cars", null, filters)
.subscribe((data: IData) => {
let carsArr = <IOrderCar[]>data.res;
this.data.oldCars = carsArr.slice(0);
this.cars = carsArr;
this.setArrs();
});
}
但因為firebase是nosql資料庫,所以無法關聯其他表,
因此得自己手動寫一個,如docJoin()
,
傳送回來的list把內部每一個都送去拿關聯式的值。
getOne() 利用的關聯式function為
docJoinFilterOne()
,
而docJoin()
是把list中有指定某筆送去找關聯式資料,
目前只有 connect() 用到
getData() 內部作分流,根據參數是否有送id,來決定是送list函式還是送one函式。
setQuery() 就是把filters拆出來變成firebase的query,可以多種篩選值。
insertOne() 內部流程改變,因不需要firebase自己產生的id,
又需要產生自動流水號,所以利用counters
table內的紀錄編號,
只要觸發 insertOne(),count+1後再新增資料。
需要改掉list中搜尋的關聯式,
在json-server裡的關聯式是 _embed
、_expand
所以直接在vscode中直接搜尋,
並且改成如下:
let link = {
join: "customers",
key: "customerId"
}
.getData("orders", null, this.setFilters(), this.tab.pageObj,link )
舉個例子:
//查看購物車資訊。
//car.component.ts
setDatas() {
let filters: IFilter[] = [
{ key: "orderId", val: this.order.id }
// { key: "_expand", val: "product" }
];
let link = {
join: "products",
key: "productId"
};
this.dataService.getData("cars", null, filters, null, link)
.subscribe((data: IData) => {
if (!data.errorcode && !!data.res) {
this.cars = <IOrderCar[]>data.res;
}
});
}