import { Injectable } from '@angular/core';
import {
Observable,
Subject,
BehaviorSubject,
ReplaySubject,
of,
from,
interval,
timer,
forkJoin,
combineLatest,
throwError,
} from 'rxjs';
import {
map,
filter,
tap,
take,
takeUntil,
debounceTime,
distinctUntilChanged,
switchMap,
mergeMap,
catchError,
delay,
scan,
} from 'rxjs/operators';
// ─────────────────────────────────────────────────────────────
// 第1节:Observable 基础
// Observable 是"懒"的 —— 不订阅就不执行
// ─────────────────────────────────────────────────────────────
export function demo01_observable_basics(): void {
console.group('【1】Observable 基础');
// 用 of() 创建一个同步 Observable
const nums$ = of(1, 2, 3, 4, 5);
// subscribe 才会真正执行
nums$.subscribe({
next: (v) => console.log('收到值:', v),
error: (e) => console.error('错误:', e),
complete: () => console.log('完成'),
});
// 用 from() 把数组/Promise 转成 Observable
const arr$ = from(['apple', 'banana', 'cherry']);
arr$.subscribe((v) => console.log('水果:', v));
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第2节:pipe + 基础 operators
// pipe 是"管道",数据依次经过每个 operator
// ─────────────────────────────────────────────────────────────
export function demo02_pipe_operators(): void {
console.group('【2】pipe + 基础 operators');
of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.pipe(
tap((v) => console.log(' tap原始值:', v)), // 只看,不改变
filter((v) => v % 2 === 0), // 只保留偶数
map((v) => v * 100), // 乘以100
take(3), // 只取前3个
)
.subscribe((v) => console.log('最终结果:', v));
// 输出: 200, 400, 600
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第3节:Subject —— 可以手动推送值的 Observable
// ─────────────────────────────────────────────────────────────
export function demo03_subject(): void {
console.group('【3】Subject');
const subject$ = new Subject<string>();
// 可以有多个订阅者(多播)
subject$.subscribe((v) => console.log('订阅者A:', v));
subject$.subscribe((v) => console.log('订阅者B:', v));
// 手动推送值
subject$.next('hello');
subject$.next('world');
// 晚来的订阅者什么都收不到
subject$.subscribe((v) => console.log('订阅者C(晚到):', v));
subject$.next('late message'); // C 能收到,但前两条错过了
subject$.complete();
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第4节:BehaviorSubject —— 记住最后一个值
// Angular Service 跨组件共享状态的标准做法
// ─────────────────────────────────────────────────────────────
@Injectable({ providedIn: 'root' })
export class UserStateService {
// 私有,外部不能直接 next()
private _userName$ = new BehaviorSubject<string>('游客');
// 公开只读视图
readonly userName$ = this._userName$.asObservable();
// 读当前值(不需要订阅)
get currentUserName(): string {
return this._userName$.getValue();
}
login(name: string): void {
this._userName$.next(name);
}
logout(): void {
this._userName$.next('游客');
}
}
export function demo04_behavior_subject(): void {
console.group('【4】BehaviorSubject');
const count$ = new BehaviorSubject<number>(0);
// 新订阅者立即能拿到当前值(0)
count$.subscribe((v) => console.log('计数:', v));
count$.next(1);
count$.next(2);
// 这个晚来的订阅者能立即收到当前值 2
count$.subscribe((v) => console.log('晚来的订阅者收到:', v));
count$.next(3);
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第5节:ReplaySubject —— 重放最近N条记录
// ─────────────────────────────────────────────────────────────
export function demo05_replay_subject(): void {
console.group('【5】ReplaySubject');
// 缓冲最近3条
const replay$ = new ReplaySubject<number>(3);
replay$.next(1);
replay$.next(2);
replay$.next(3);
replay$.next(4);
replay$.next(5);
// 晚来的订阅者能收到最近3条:3, 4, 5
replay$.subscribe((v) => console.log('重放值:', v));
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第6节:switchMap —— 切换流,取消旧请求
// 最典型场景:搜索框输入,只关心最新一次的结果
// ─────────────────────────────────────────────────────────────
function fakeHttpSearch(keyword: string): Observable<string[]> {
console.log(` → 发起请求: "${keyword}"`);
return of([`${keyword}_结果1`, `${keyword}_结果2`]).pipe(
delay(300),
tap(() => console.log(` ← 收到响应: "${keyword}"`)),
);
}
export function demo06_switchMap(): void {
console.group('【6】switchMap(搜索防抖)');
const searchInput$ = new Subject<string>();
searchInput$
.pipe(
debounceTime(200), // 200ms 内只取最后一次输入
distinctUntilChanged(), // 值没变就不发
switchMap((kw) => fakeHttpSearch(kw)), // 切换到新的 HTTP 流,自动取消旧的
)
.subscribe((results) => console.log('搜索结果:', results));
// 模拟快速输入
searchInput$.next('a');
searchInput$.next('an');
searchInput$.next('ang'); // 前两个被 debounce 丢弃
setTimeout(() => searchInput$.next('angu'), 300);
setTimeout(() => searchInput$.next('angul'), 600);
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第7节:mergeMap —— 并行执行多个流
// 场景:同时发多个 HTTP 请求,不互相等待
// ─────────────────────────────────────────────────────────────
function fakeHttpGetUser(id: number): Observable<{ id: number; name: string }> {
return of({ id, name: `User_${id}` }).pipe(delay(100));
}
export function demo07_merge_map(): void {
console.group('【7】mergeMap(并行请求)');
from([1, 2, 3])
.pipe(
mergeMap((id) => fakeHttpGetUser(id)), // 3个请求同时发出
)
.subscribe((user) => console.log('用户:', user));
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第8节:forkJoin —— 等所有请求都完成
// 场景:页面初始化时需要多个接口都返回才能渲染
// ─────────────────────────────────────────────────────────────
export function demo08_fork_join(): void {
console.group('【8】forkJoin(等所有完成)');
forkJoin({
users: fakeHttpGetUser(1),
config: of({ theme: 'dark' }).pipe(delay(200)),
permissions: of(['read', 'write']).pipe(delay(150)),
}).subscribe((result) => {
console.log('所有数据加载完成:', result);
// { users: {id:1, name:'User_1'}, config: {...}, permissions: [...] }
});
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第9节:combineLatest —— 多个流,任一变化就合并最新值
// 场景:表单有多个字段,任意字段变化时重新计算结果
// ─────────────────────────────────────────────────────────────
export function demo09_combine_latest(): void {
console.group('【9】combineLatest(多流合并)');
const price$ = new BehaviorSubject<number>(100);
const quantity$ = new BehaviorSubject<number>(1);
const discount$ = new BehaviorSubject<number>(0);
combineLatest([price$, quantity$, discount$])
.pipe(
map(([price, qty, disc]) => price * qty * (1 - disc / 100)),
)
.subscribe((total) => console.log('总价:', total));
price$.next(200); // → 200 * 1 * 1 = 200
quantity$.next(3); // → 200 * 3 * 1 = 600
discount$.next(10); // → 200 * 3 * 0.9 = 540
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第10节:catchError —— 错误处理
// ─────────────────────────────────────────────────────────────
export function demo10_error_handling(): void {
console.group('【10】catchError(错误处理)');
throwError(() => new Error('网络超时'))
.pipe(
catchError((err) => {
console.error('捕获到错误:', err.message);
return of('使用默认值继续'); // 用默认值替代,流不中断
}),
)
.subscribe((v) => console.log('结果:', v));
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第11节:scan —— 类似 reduce,但每次都 emit 中间值
// 场景:累加器、购物车数量、消息列表追加
// ─────────────────────────────────────────────────────────────
export function demo11_scan(): void {
console.group('【11】scan(累加状态)');
const action$ = new Subject<number>();
action$
.pipe(scan((acc, curr) => acc + curr, 0))
.subscribe((total) => console.log('累计:', total));
action$.next(10); // 10
action$.next(5); // 15
action$.next(-3); // 12
console.groupEnd();
}
// ─────────────────────────────────────────────────────────────
// 第12节:interval + takeUntil —— 定时器 + 手动停止
// ─────────────────────────────────────────────────────────────
export function demo12_interval_takeuntil(): void {
console.group('【12】interval + takeUntil');
const stop$ = new Subject<void>();
interval(500) // 每500ms发一个值
.pipe(
take(10), // 最多10个
takeUntil(stop$), // 或者 stop$ 发出时停止
map((i) => `第${i + 1}次`),
)
.subscribe({
next: (v) => console.log(v),
complete: () => console.log('定时器已停止'),
});
// 2秒后手动停止
setTimeout(() => stop$.next(), 2000);
console.groupEnd();
}