You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Angular中基于Mongoose分批加载10条帖子并保留历史内容

无限滚动加载实现方案(Angular + MongoDB)

你说的这种模式叫无限滚动加载,属于分页的变体——所有内容在同一页面展示,保留已加载内容,完全匹配你的需求。下面分后端接口改造和前端Angular实现两部分说明:

一、后端接口改造(支持分页查询)

当前接口返回全部帖子数据,需要改成支持分页获取,避免一次性加载大量数据影响性能。这里提供两种实现方式:

方式1:基于页码的偏移分页(适合数据量不大的场景)

修改你的Express路由,接收page(页码)和limit(每页条数)参数,用skip()和limit()实现分页:

router.get(
  "/",
  asyncHandler(async (req, res) => {
    // 从请求参数取页码,默认第1页;每页默认10条
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 10;
    const skip = (page - 1) * limit;

    // 查询分页数据,按创建时间倒序
    const posts = await PostModel.find()
      .sort({ createdAt: "descending" })
      .skip(skip)
      .limit(limit);

    // 返回总条数、总页数等信息,方便前端判断是否还有更多数据
    const total = await PostModel.countDocuments();

    res.send({
      posts,
      total,
      currentPage: page,
      totalPages: Math.ceil(total / limit)
    });
  })
);

方式2:基于游标的分页(适合大数据量场景,性能更优)

如果后期帖子数量很多,skip()会因为跳过大量文档导致性能下降,这时可以用游标分页——以上次加载的最后一条帖子的_id(或createdAt)作为标记,下次只查询比这个标记更早的帖子:

router.get(
  "/",
  asyncHandler(async (req, res) => {
    const limit = parseInt(req.query.limit) || 10;
    const lastId = req.query.lastId; // 前端传的最后一条帖子ID

    let query = {};
    if (lastId) {
      // 倒序查询,只取ID小于lastId的帖子(MongoDB的ID包含时间戳,天然支持排序)
      query = { _id: { $lt: lastId } };
    }

    // 多查1条用来判断是否还有更多数据
    const posts = await PostModel.find(query)
      .sort({ createdAt: "descending" })
      .limit(limit + 1);

    const hasMore = posts.length > limit;
    if (hasMore) posts.pop(); // 移除多查的那条

    res.send({
      posts,
      hasMore,
      lastId: posts.length > 0 ? posts[posts.length - 1]._id : null
    });
  })
);

二、前端Angular实现

下面以方式1(偏移分页)为例,写核心代码:

1. 组件逻辑代码

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// 定义帖子类型
interface Post {
  _id: string;
  title: string;
  imageUrl: string;
  createdAt: Date;
}

// 定义接口返回格式
interface PostsResponse {
  posts: Post[];
  total: number;
  currentPage: number;
  totalPages: number;
}

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {
  posts: Post[] = [];
  currentPage = 1;
  totalPages = 1;
  isLoading = false;
  hasMore = true;

  constructor(private http: HttpClient) { }

  ngOnInit(): void {
    // 初始加载第一页数据
    this.loadPosts();
  }

  loadPosts(): void {
    // 加载中或没有更多数据时,不重复触发
    if (this.isLoading || !this.hasMore) return;

    this.isLoading = true;
    this.http.get<PostsResponse>(`/api/posts?page=${this.currentPage}&limit=10`)
      .subscribe({
        next: (res) => {
          // 合并已有数据和新加载的数据
          this.posts = [...this.posts, ...res.posts];
          this.currentPage = res.currentPage;
          this.totalPages = res.totalPages;
          // 判断是否还有更多页
          this.hasMore = this.currentPage < this.totalPages;
          this.isLoading = false;
        },
        error: (err) => {
          console.error('加载帖子失败:', err);
          this.isLoading = false;
        }
      });
  }
}

2. 模板代码(支持按钮触发加载)

<div class="posts-list">
  <!-- 遍历展示帖子 -->
  <div *ngFor="let post of posts" class="post-item">
    <h3>{{ post.title }}</h3>
    <img [src]="post.imageUrl" alt="{{ post.title }}" class="post-image">
  </div>
</div>

<!-- 加载更多按钮 -->
<div class="load-more-wrap">
  <button 
    *ngIf="hasMore" 
    (click)="loadPosts()" 
    [disabled]="isLoading"
  >
    {{ isLoading ? '加载中...' : '加载更多帖子' }}
  </button>
  <!-- 没有更多数据时的提示 -->
  <p *ngIf="!hasMore && posts.length > 0">没有更多帖子了</p>
</div>

可选:实现滚动自动加载

如果不想用按钮,而是滚动到页面底部自动加载,可以在组件里添加滚动监听:

import { AfterViewInit, OnDestroy } from '@angular/core';

// 实现这两个生命周期钩子
export class PostsComponent implements OnInit, AfterViewInit, OnDestroy {
  // ... 其他变量和方法

  private scrollListener?: () => void;

  ngAfterViewInit(): void {
    const postsList = document.querySelector('.posts-list');
    if (!postsList) return;

    this.scrollListener = () => {
      const { scrollTop, scrollHeight, clientHeight } = postsList as HTMLElement;
      // 滚动到距离底部200px时触发加载
      if (scrollTop + clientHeight >= scrollHeight - 200) {
        this.loadPosts();
      }
    };

    postsList.addEventListener('scroll', this.scrollListener);
  }

  ngOnDestroy(): void {
    const postsList = document.querySelector('.posts-list');
    if (postsList && this.scrollListener) {
      postsList.removeEventListener('scroll', this.scrollListener);
    }
  }
}

注意:如果用游标分页,只需要把前端的currentPage换成lastId,请求时传递lastId参数即可,逻辑类似。

内容的提问来源于stack exchange,提问作者Jango

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 21:54:51