DAY26 - 展现成果,建立 firestore 动态与复杂的查询

https://ithelp.ithome.com.tw/upload/images/20211011/20120107y2NxOjJwRo.png

建立动态查询

还记得之几篇一开始如何使用条件查询 firestore 的资料吗?

this.firestore.collection("fruits",ref=>ref
.where("price", ">=", 200)
.orderBy("price", "asc"))

这样的查询翻译成白话文就是,我要查询水果,价格要大於等於200,且且结果要升幂排列。但是这样有一个缺点,有没有注意到200这个数字是写死的,如果想要改成低於300,且结果降幂排列的时候,就要重新写一个 function ,并且是写死的,无法动态查询。

因此官方文件给了一个动态查询的例子

const size$ = new Subject<string>();
const queryObservable = size$.pipe(
  switchMap(size => 
    afs.collection('items', ref => ref.where('size', '==', size)).valueChanges()
  )
);

// subscribe to changes
queryObservable.subscribe(queriedItems => {
  console.log(queriedItems);  
});

// trigger the query
size$.next('large');

// re-trigger the query!!!
size$.next('small');

说起来复杂不复杂,简单也不简单,如果要看懂上面的程序码,就必须对 rxjs 有一定的了解,如果对 rxjs 了解的,就大约可以了解上面的行为流程

  1. 将每一个查询的参数都包装成 Subject
  2. 如果参数改变的话,就使用 subject的 next 去触发新的资料流
  3. 而订阅参数的可被观察对象在收到新的资料流後,使用 switchMap 转换资料流,转换成查询 firestore 的资料流,并且以最新的参数作为变数去查询。

如此一来,就完成动态的查询方法。

再回到 side project 也是使用同样的原理来做动态查询

页面

页面照样利用套件的元件,所做的只是将资料绑定上去

<div class="container-fluid">
  <div class="row">
    <div class="col col12">
      <nb-tabset fullWidth (changeTab)="changeMode($event)">
        <nb-tab tabTitle="看看自己"> </nb-tab>
        <nb-tab tabTitle="看看大家">
          <div class="col col-12">
            <nb-select
              placeholder="选择挑战者"
              (selectedChange)="changeUser($event)"
              [(selected)]="selectedUserId"
            >
              <nb-option
                *ngFor="let user of userList$ | async"
                [value]="user.userId"
                >{{ user.name }}</nb-option
              >
            </nb-select>
            <input
              nbInput
              placeholder="选择日期"
              [nbDatepicker]="dateTimePicker"
              [(ngModel)]="selectedDate"
              (ngModelChange)="changeDate()"
            />
            <nb-datepicker #dateTimePicker format="yyyy-MM-dd"></nb-datepicker>
            <button nbButton hero status="danger" (click)="clearFilter()">清除</button>
          </div>
        </nb-tab>
      </nb-tabset>
    </div>
  </div>
  <div class="row justify-content-start">
    <div
      class="col col-md-4 col-12"
      *ngFor="let checkin of checkinList$ | async"
    >
      <challenge90days-checkin-card
      [checkin]="checkin"
      ></challenge90days-checkin-card>
    </div>
  </div>
</div>

样式

无,淋漓尽致地使用,一个自订样式都不写。

逻辑

import { Component, OnInit } from '@angular/core';
import {
  AngularFirestore,
  AngularFirestoreCollection,
} from '@angular/fire/firestore';
import { Observable, Subject } from 'rxjs';
import { UserService } from '../../../../services/user.service';
import {
  debounceTime,
  distinctUntilChanged,
  switchMap,
} from 'rxjs/operators';
import { Checkin, UserInfo } from '@challenge90days/api-interfaces';

import { ActivatedRoute } from '@angular/router';
import { DateService } from '../../../../services/date.service';

@Component({
  selector: 'challenge90days-myself',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.scss'],
})
export class ListComponent implements OnInit {
  userId: string;
  userCollection: AngularFirestoreCollection<any>;
  userList$: Observable<UserInfo[]>;
  checkinList$: Observable<Checkin[]>;
  checkinListQuery$ = new Subject<unknown>();
  selectedDate: Date;
  selectedUserId: string;
  mode = false;
  constructor(
    private firestore: AngularFirestore,
    private userService: UserService,
    private dateService: DateService,
    private activatedRoute: ActivatedRoute
  ) {
    this.userId = this.userService.userId$.value;
  }

  ngOnInit(): void {
    console.log(this.activatedRoute.snapshot.params);
    this.getUserId();
    this.getUserList();
  }

  getUserId(): void {
    this.userService.userId$.subscribe((userId) => {
      console.log(userId);
      this.getCheckinListData();
    });
  }

  getUserList(): void {
    this.userList$ = this.firestore.collection<UserInfo>('user').valueChanges();
  }

  getCheckinListData(): void {
    this.checkinList$ = this.checkinListQuery$.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(() =>
        this.firestore
          .collection<Checkin>('checkin', (ref) => {
            if (this.mode) {
              let finalQuery = ref
                .where('type', '==', 1)
                .orderBy('time', 'desc')
              if (this.selectedUserId) {
                console.log(this.selectedUserId);
                finalQuery = finalQuery.where(
                  'userId',
                  '==',
                  this.selectedUserId
                );
              }
              if (this.selectedDate) {
                const { startOfDay, endOfDay } = this.dateService.getDayRange(
                  this.selectedDate
                );
                finalQuery = finalQuery
                  .where('time', '>', startOfDay)
                  .where('time', '<', endOfDay);
              }
              return finalQuery;
            } else {
              return ref
                .where('userId', '==', this.userId)
                .where('type', '==', 1)
                .limit(65)
                .orderBy('time', 'desc');
            }
          })
          .valueChanges()
      )
    );
  }

  changeMode(tab: any): void {
    this.mode = tab.tabTitle === '看看大家';
    this.checkinListQuery$.next(this.mode);
  }

  changeUser(userId: string) {
    this.checkinListQuery$.next(userId);
  }
  changeDate() {
    this.checkinListQuery$.next(this.selectedDate);
  }

  clearFilter() {
    this.selectedDate = null;
    this.selectedUserId = null;
    this.checkinListQuery$.next();
  }
}

getCheckinListData ,就是使用同样的原理去查询,只不过条件比较多一点,不是只有单一条件,所以看起来比较复杂,因为查询的条件有:

  • 查询自己或查询他人
  • 查询的日期
  • 查询他人的特定使用者

有三个查询条件综合查询起来,所以看起来会比较头昏眼花一点,不过只要搞懂 rxjs 的资料流的概念之後,就会一点也不复杂罗!


<<:  Day 28:Diffie–Hellman演算法

>>:  网页变形-30天学会HTML+CSS,制作精美网站

【Day 15】- 汇率什麽的。爬! (实战汇率爬虫 on chrome)

前情提要 前一篇带各位实作了爬取 Ubuntu ISO 映像档的爬虫,并存在 JSON 档。 开始之...

Day - 20 终於剩下十天之需求篇

不知不觉就剩下十天了。 其实我要做的东西很单纯,大概是一个input[text] 可以输入名称,加上...

[Day 17]从零开始学习 JS 的连续-30 Days---AJAX--方法介绍

AJAX--方法介绍 JavaScript 原生写法 XMLHttpRequest : 物件的方式来...

Day1 Open-Match 简介

在众多游戏类型中,对战游戏类型游戏占有很重要的一席之地。不论是手机游戏市场,还是以电脑为主的竞技游戏...

【Day 26】C String - Practice 1

前言 即使我们看完了 Cstring 的影片,却还是对於他蛮模糊的吧!我自己是写了一些题目後才对 C...