根据输入值更新 ngif

发布于 2025-01-11 11:31:30 字数 3448 浏览 4 评论 0原文

我正在尝试进行搜索以过滤博客中的帖子
这些帖子是从 ngrx 商店作为可观察的
因此,当用户更改输入的值时,帖子(使用异步管道)将由名为filter-posts的管道更新
如果新数组为空,则应显示一条消息“未找到帖子”
问题是更改输入值后未显示消息
这是代码:

posts.component.html:

<div class="posts">
  <!--search-->
    <input
      [(ngModel)]="searchValue"
      class="p-2 rounded border w-100 bg-secondary text-light" type="search"
      placeholder="Search..." id="search">

  <!--posts-->
  <ng-container *ngIf="(posts | async).length > 0; else noPosts">
    <div class="post" *ngFor="let post of posts | async | filterPosts:searchValue">
      <div class="p-title">
        <a (click)="goTo('blog/posts/' + post.id.toString(), post)">{{post.title}}</a>
      </div>
      <div class="p-image">
        <img [src]="post.imageSrc" [alt]="post.title">
      </div>
      <div class="p-cont">
        {{post.content | slice:0:80}}
      </div>
      <div class="p-category">
        <a *ngFor="let category of post.categories" class="p-1 m-1 text-light fw-bold">{{category}}</a>
      </div>
      <div class="p-date">
        {{post.date}}
      </div>
      <app-author class="p-author d-block" [author]="post.author"></app-author>
    </div>
  </ng-container>
  <ng-template #noPosts>
    <div class="bg-danger w-100">No Posts Found</div>
  </ng-template>

</div>

posts.component.ts:

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Router } from '@angular/router';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Post } from 'src/app/models/post';
import { User } from 'src/app/models/user';
import { setSelectedPostId } from 'src/app/state/posts/actions';
import { getPosts, PostState } from 'src/app/state/posts/reducer';
import { getUserById } from 'src/app/state/users/reducer';

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.scss']
})
export class PostsComponent {

  constructor(private store: Store<PostState>, private router: Router) { }

  search = faSearch;

  searchValue: string = '';

  goTo(path: string, post: Post) {
    this.router.navigate([path]);
    this.setSelectedPost(post.id);
  }

  setSelectedPost(id: string) {
    this.store.dispatch(setSelectedPostId({id}));
  }

  getUser(id: string): Observable<User> {
    return this.store.select(getUserById(id));
  }

  posts: Observable<Post[]> = this.store.select(getPosts);

}

filter-posts.pipe.ts:

import { Pipe, PipeTransform } from '@angular/core';
import { Post } from '../models/post';

@Pipe({
  name: 'filterPosts',
  pure: false
})
export class FilterPostsPipe implements PipeTransform {
  transform(value: Post[], searchValue: string): Post[] {
    return value.filter(
      (post) =>
        post.title.toLowerCase().includes(searchValue.toLowerCase()) ||
        post.content.toLowerCase().includes(searchValue.toLowerCase()) ||
        post.author.name.toLowerCase().includes(searchValue.toLowerCase()) ||
        post.categories.includes(searchValue.toLowerCase())
    );
  }
}

I'm Trying to do a search to filter posts in a blog
the posts are got from the ngrx store as an observable
so when the user changes the value of the input the posts (with async pipe) will be updated by a pipe called filter-posts
and if the new array is empty a message should appear 'no posts found'
the problem is that the message is not shown after changing the input value
this is the code:

posts.component.html:

<div class="posts">
  <!--search-->
    <input
      [(ngModel)]="searchValue"
      class="p-2 rounded border w-100 bg-secondary text-light" type="search"
      placeholder="Search..." id="search">

  <!--posts-->
  <ng-container *ngIf="(posts | async).length > 0; else noPosts">
    <div class="post" *ngFor="let post of posts | async | filterPosts:searchValue">
      <div class="p-title">
        <a (click)="goTo('blog/posts/' + post.id.toString(), post)">{{post.title}}</a>
      </div>
      <div class="p-image">
        <img [src]="post.imageSrc" [alt]="post.title">
      </div>
      <div class="p-cont">
        {{post.content | slice:0:80}}
      </div>
      <div class="p-category">
        <a *ngFor="let category of post.categories" class="p-1 m-1 text-light fw-bold">{{category}}</a>
      </div>
      <div class="p-date">
        {{post.date}}
      </div>
      <app-author class="p-author d-block" [author]="post.author"></app-author>
    </div>
  </ng-container>
  <ng-template #noPosts>
    <div class="bg-danger w-100">No Posts Found</div>
  </ng-template>

</div>

posts.component.ts:

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Router } from '@angular/router';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Post } from 'src/app/models/post';
import { User } from 'src/app/models/user';
import { setSelectedPostId } from 'src/app/state/posts/actions';
import { getPosts, PostState } from 'src/app/state/posts/reducer';
import { getUserById } from 'src/app/state/users/reducer';

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.scss']
})
export class PostsComponent {

  constructor(private store: Store<PostState>, private router: Router) { }

  search = faSearch;

  searchValue: string = '';

  goTo(path: string, post: Post) {
    this.router.navigate([path]);
    this.setSelectedPost(post.id);
  }

  setSelectedPost(id: string) {
    this.store.dispatch(setSelectedPostId({id}));
  }

  getUser(id: string): Observable<User> {
    return this.store.select(getUserById(id));
  }

  posts: Observable<Post[]> = this.store.select(getPosts);

}

filter-posts.pipe.ts:

import { Pipe, PipeTransform } from '@angular/core';
import { Post } from '../models/post';

@Pipe({
  name: 'filterPosts',
  pure: false
})
export class FilterPostsPipe implements PipeTransform {
  transform(value: Post[], searchValue: string): Post[] {
    return value.filter(
      (post) =>
        post.title.toLowerCase().includes(searchValue.toLowerCase()) ||
        post.content.toLowerCase().includes(searchValue.toLowerCase()) ||
        post.author.name.toLowerCase().includes(searchValue.toLowerCase()) ||
        post.categories.includes(searchValue.toLowerCase())
    );
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

橪书 2025-01-18 11:31:30

使用管道进行此类操作最初似乎是一个好主意,但是当您依赖将在每个更改检测周期运行的不纯管道时,应用程序的性能可能会受到影响。

有更简单的方法可以根据另一个值过滤可观察值,我的建议是将搜索输入中的值也视为流。

如果我们不使用 [(ngModel)],而是使用 FormControl 来监听输入的 valueChanges 可观察值,我们可以将流和根据 search 值过滤 posts 值。

你会得到这样的结果

// Create a form control for the search input
searchControl = new FormControl();

..........................

posts: Observable<Post[]> = combineLatest([
  this.store.select(getPosts),
  this.searchControl.valueChanges.pipe( // Listen for the changes of the form control
    debounceTime(250), // dont search for every keystroke
    startWith(''), // start with an empty string to show all posts
  )
]).pipe(
  map(([ posts, searchValue ]) => posts.filter(post => {
    return post.title.toLowerCase().includes(searchValue.toLowerCase()) ||
      post.content.toLowerCase().includes(searchValue.toLowerCase()) ||
      post.author.name.toLowerCase().includes(searchValue.toLowerCase()) ||
      post.categories.includes(searchValue.toLowerCase())
  })
  )
);

并在你的模板中连接表单控件

<input [formControl]="searchControl"
       ...>

这样,就不需要管道,并且当可观察值发生变化时视图应该更新。

The use of a pipe for this kind of operation may seem like a good idea initially, but when you rely on an impure pipe that will run on each change detection cycle, the performance of an app can be compromised.

There are more simple ways to filter an observable value based on another value, and my suggestion is to consider the values from the search input as a stream aswell.

If we instead of using [(ngModel)] use a FormControl to listen to the valueChanges observable of the input, we can combine the streams and filter the posts value based on the search value.

You would en up with something like this

// Create a form control for the search input
searchControl = new FormControl();

..........................

posts: Observable<Post[]> = combineLatest([
  this.store.select(getPosts),
  this.searchControl.valueChanges.pipe( // Listen for the changes of the form control
    debounceTime(250), // dont search for every keystroke
    startWith(''), // start with an empty string to show all posts
  )
]).pipe(
  map(([ posts, searchValue ]) => posts.filter(post => {
    return post.title.toLowerCase().includes(searchValue.toLowerCase()) ||
      post.content.toLowerCase().includes(searchValue.toLowerCase()) ||
      post.author.name.toLowerCase().includes(searchValue.toLowerCase()) ||
      post.categories.includes(searchValue.toLowerCase())
  })
  )
);

And in your template connect the formcontrol

<input [formControl]="searchControl"
       ...>

With this, there is no need for a pipe and the view should update when the observable values change.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文