【问题标题】:Subject not reacting to filtered datas in Angular 8主题对 Angular 8 中的过滤数据没有反应
【发布时间】:2020-07-20 15:33:50
【问题描述】:

首先,我不是前端开发人员。我更专注于后端开发。但是,我必须用 Angular 做一个应用程序(我选择了 Angular 8)。 在之前的工作中,我使用了 Angular。但这是我第一次必须从头开始创建 Angular 应用程序。

我必须读取、添加、修改和删除对象(称为订阅)。这一切都很好。

除非我想用表单过滤...

为了更详细的代码,我的页面 HTML 是这样构造的:

subscription.component.html

<div id="subscription-filter">
    <app-subscription-filter></app-subscription-filter>
</div>

<div id="subscription-view">
    <app-subscription-view></app-subscription-view>
</div>

我遇到的问题是 app-subscription-filter 和 app-subscription-view

订阅过滤部分:

subscription-filter.component.html

<div class="row col-12">
    <div class="row col-11">
        <label class="col-1" for="selectCRNA">{{filterLabel}} </label>
        <select class="col-11 form-control" [(ngModel)]="selectedCRNA">
            <option *ngFor="let crna of filterSelection">
               {{crna.name}}
            </option>
        </select>
    </div>
    <div class="col-1">
            <button type="submit" class="btn"><i class="fas fa-search fa-lg" (click)="filterOnCRNAOnly()"></i></button>
    </div>
</div>

...

subscription-filter.component.ts

import { Component, OnInit, ViewChild } from '@angular/core';
import { Observable } from 'rxjs';

import { SubscriptionService } from '../../shared/service/subscription.service';


@Component({
  selector: 'app-subscription-filter',
  templateUrl: './subscription-filter.component.html',
  styleUrls: ['./subscription-filter.component.css']
})
export class SubscriptionFilterComponent implements OnInit {

    filterLabel: string;
    filterSelection: any[];
    selectedCRNA: string;
    selectedCRNALabel: string;

    addSubscriptionForm : FormGroup;

    @ViewChild('closebutton', {static: false}) closebutton;

    constructor (protected subscriptionService: SubscriptionService) {

    }

    ngOnInit() {
        this.filterLabel = "Filtrer sur le tableau de résultat :";
        this.filterSelection = [
            { name: "Tous CRNA", value: "All" },
            { name: "CRNA NORD", value: "CRNA NORD" },
            { name: "CRNA SUD", value: "CRNA SUD" },
            { name: "CRNA EST", value: "CRNA EST" },
            { name: "CRNA OUEST", value: "CRNA OUEST" },
            { name: "CRNA SUD-OUEST", value: "CRNA SUD-OUEST" }
        ];

    }

    /**
     * Method to filter on CRNA selected
     */
    filterOnCRNAOnly() {
        console.log(this.selectedCRNA);
        this.subscriptionService.onlyCRNAFilterForSubject(this.selectedCRNA);
        this.selectedCRNALabel = this.selectedCRNA;
    }
}

...

订阅视图部分:

subscription-view.html

<table class="table table-responsive table-hover" *ngIf="!!subscriptions || isLoadingResults">
    <thead>
        <tr>
            <th *ngFor='let col of tableHeaders'>
                {{col.header}}
            </th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor='let sub of (subscriptions)'>
            <td scope='row'>{{sub.status}}</td>
            <td>{{sub.region}}</td>
            <td>{{sub.provider}}</td>
            <td>{{sub.host}}</td>
            <td>{{sub.consumer}}</td>
            <td>{{sub.alias}}</td>
            <td>{{sub.filters}}</td>
            <td>
                <i class="fas fa-play mx-1" data-toggle="tooltip" title="Start subscription" (click)="startSubscription(sub, sub.id)"></i>
                <i class="fas fa-times mx-1" data-toggle="tooltip" title="Stop subscription" (click)="stopSubscription(sub, sub.id)"></i>
                <i class="fas fa-trash mx-1" data-toggle="tooltip" title="Delete subscription" (click)="deleteSubscription(sub.id)"></i>
            </td>
            <td></td>
        </tr>
    </tbody>
</table>

subscription-component.ts

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

import { SubscriptionModel } from '../../shared/model/subscription.model';

import { SubscriptionService } from '../../shared/service/subscription.service';

@Component({
  selector: 'app-subscription-view',
  templateUrl: './subscription-view.component.html',
  styleUrls: ['./subscription-view.component.less']
})
export class SubscriptionViewComponent implements OnInit {

    subscriptions: SubscriptionModel[] = [];
    tableHeaders: any[];

    isLoadingResults = true;

    copiedSubscription: SubscriptionModel;

    constructor(protected subscriptionService: SubscriptionService) { }

    ngOnInit() {
        this.tableHeaders = [
            {field: 'status', header: 'Status'},
            {field: 'region', header: 'Region'},
            {field: 'provider', header: 'Fournisseur'},
            {field: 'host', header: 'Bus'},
            {field: 'consumer', header: 'Consommateur'},

            {field: 'alias', header: 'Alias'},
            {field: 'filters', header: 'Abonnement'},
            {field: '', header: 'Actions'},
            {field: '', header: 'Selections'}
        ];
        this.copiedSubscription = new SubscriptionModel();
        this.loadAll();
    }

    /**
     * Method to load all subscriptions
     */
    loadAll() {
       this.subscriptionService.initializeSubscriptions().subscribe((res: any) => {
            this.subscriptions = res;
            this.isLoadingResults = false;
       })
    }

    /**
     * Method to start a subscription
     * @param sub 
     * @param id 
     */
    startSubscription(sub: SubscriptionModel, id: string) {
        if (sub.status !== "OK") {
           this.subscriptionService.changeSubscriptionStatus(id, "ON");
        }
    }

    /**
     * Method to stop a subscription
     * @param sub 
     * @param id 
     */
    stopSubscription(sub: SubscriptionModel, id: string) {
        if (sub.status === "OK") {
            this.subscriptionService.changeSubscriptionStatus(id, "Off");
        }
    }

    /**
     * Method to delete a subscription
     * @param id 
     */
    deleteSubscription(id: string) {
        this.subscriptionService.deleteSubscription(id);
    }

}

我(目前)没有任何服务器调用。我所有的数据都是用 JSON 文件模拟的。 而必须显示的数据是test$;

subscription.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';

import { Subject, Observable, of } from 'rxjs';
import { tap, filter, map, catchError } from 'rxjs/operators';

import { History } from '../model/history.model';
import { SubscriptionModel } from '../model/subscription.model';
import { Hosting } from '../model/hosting.model';


@Injectable({
  providedIn: 'root'
})
export class SubscriptionService {

    mockHostingUrl: string = 'assets/jsontests/hostmockdata.json';
    mockSubscribeUrl: string = 'assets/jsontests/subscriptionsmockdata.json';

    private test$: Subject<SubscriptionModel[]> = new Subject<SubscriptionModel[]>();

    private subsTest: SubscriptionModel[] = [];

    copiedSub: SubscriptionModel;

    crnaSelected: string = "Tous CRNA";

    constructor(private http: HttpClient) { }

    private handleError<T>(operation = 'operation', result?: T) {
        return (error: any): Observable<T> => {

            // TODO: send the error to remote logging infrastructure
            console.error(error); // log to console instead

            // Let the app keep running by returning an empty result.
            return of(result as T);
        };
    }

    /**
     * Method to initialize subscriptions
     */
    initializeSubscriptions() : Observable<SubscriptionModel[]> {
        return this.http.get<SubscriptionModel[]>(this.mockSubscribeUrl).pipe(tap((subs => {
            this.subsTest = subs;
            this.test$.next(this.subsTest);
        })));
    }

    /**
     * Method for adding a new subscription
     * @param sub 
     */
    addSubscription(sub: SubscriptionModel) {
        this.subsTest.push(sub);
        this.test$.next(this.subsTest);
    }

    /**
     * Method for changing subscription's status
     * @param id 
     * @param changeStatus 
     */
    changeSubscriptionStatus(id: string, changeStatus: string) {
        this.subsTest.find(element => {
            if (element.id === id) {
                element.status = changeStatus;
            }
        });

        this.test$.next(this.subsTest);
    }


    /**
     * Method to delete a subscription
     * @param id 
     */
    deleteSubscription(id: string) {
        this.subsTest.splice(this.subsTest.findIndex(element => element.id === id), 1);
        this.test$.next(this.subsTest);
    }

    /**
     * Method where there is the problem. It must filter and sending 
     * @param crnaSelected 
     */
    onlyCRNAFilterForSubject(crnaSelected: string) {
        console.log("dans onlyCRNAFilter");
        this.crnaSelected = crnaSelected;
        if (crnaSelected !== "Tous CRNA") {
            /*
            var temp = this.subsTest.filter(element => {
                element.region.includes(crnaSelected)
            });
            */
            console.log(this.subsTest);
            var temp: SubscriptionModel[] = [];
            this.subsTest.filter(
                element => {
                    console.log("---");
                    console.log(element);
                    if (element.region.includes(crnaSelected)) {
                        temp.push(element);
                        console.log("dedans!");
                    }
                }
            );
            console.log("apres fonction");
            console.log(temp);
            this.test$.next(temp);
        } else {
            console.log(this.subsTest);
            this.test$.next(this.subsTest);
        }
    }

}

当我尝试过滤我的表格时,我确实有正确的数据,但我的 HTML 没有用正确的数据刷新

Logger for debug

我必须承认我不知道该怎么做了......所以,有点帮助将不胜感激。

提前致谢。

(对不起,我的英语不是我的母语)

【问题讨论】:

    标签: angular filter types module subject


    【解决方案1】:

    更新,此解决方案仅在两个组件不同时渲染时才有效

    在订阅视图组件中,您只在服务中订阅初始化方法

    所以如果服务中的 onlyCRNAFilterForSubject 方法发生了一些变化,你不会意识到它

    我们可以在服务中添加一个布尔属性来指示我们是否处于过滤模式,并在我们调用服务时在订阅过滤器组件中设置这个属性

    因此,如果我们处于过滤模式,我们可以订阅 test$ observable,而不是一直订阅 initialize 方法

    所以在服务中我们需要定义一个像这样的新布尔属性

    isFilteringMode: Boolean = false; // set it initailly to false, to get all the data once the component is loaded at the first time
    

    在订阅过滤器组件中,一旦选择了某些 CRNA,我们需要将此属性设置为 true

    filterOnCRNAOnly() {
        console.log(this.selectedCRNA);
        this.subscriptionService.isFilteringMode = true; // enable the filtering mode
        this.subscriptionService.onlyCRNAFilterForSubject(this.selectedCRNA);
        this.selectedCRNALabel = this.selectedCRNA;
    }
    

    in subscription view component, in initialization mode, we will subscribe to the service function that returns the whole array, and when some CRNA is selected (filtering mode), we can subscribe to the test$ observable,

    所以在订阅视图组件中,它会是这样的

    ngOnInit() {
        this.tableHeaders = [
            { field: 'status', header: 'Status' },
            { field: 'region', header: 'Region' },
            { field: 'provider', header: 'Fournisseur' },
            { field: 'host', header: 'Bus' },
            { field: 'consumer', header: 'Consommateur' },
    
            { field: 'alias', header: 'Alias' },
            { field: 'filters', header: 'Abonnement' },
            { field: '', header: 'Actions' },
            { field: '', header: 'Selections' }
        ];
        this.copiedSubscription = new SubscriptionModel();
        this.loadAll();
    }
    
    /**
     * Method to load all subscriptions
     */
    loadAll() {
        if (this.subscriptionService.isFilteringMode) {
            // we are in the filtering mode
            // here we will subscribe to test$ observable to get the filtered data
            this.subscriptionService.test$.subscribe((res: any) => {
                this.subscriptions = res;
                this.isLoadingResults = false;
            });
    
        } else {
            // we are not in the filtering mode
            // so get all the data
            this.subscriptionService.initializeSubscriptions().subscribe((res: any) => {
                this.subscriptions = res;
                this.isLoadingResults = false;
            })
        }
    }
    

    更新,这里有一个和你的类似的小项目

    这里我们有一个列表组件,它列出了一些用户,以及一个过滤器组件,用于选择一些状态来过滤列表组件中的候选人

    我们可以使用BehaviorSubject将选中状态从过滤器组件传递到列表组件

    BehaviorSubject 在这种情况下非常有用,因为它保存了一个初始值,我们不需要等到订阅它来获取值

    在这里你可以找到区别Subject vs BehaviorSubject

    这是文件

    app.component.html

    <div class="row container">
      <div class=" row col-md-12">
        <div id="subscription-filter">
          <app-candidates-filter></app-candidates-filter>
        </div>
    
      </div>
    
      <div class="row col-md-12" style="margin-top: 30px;">
        <div id="subscription-view">
          <app-candidates-list></app-candidates-list>
        </div>
    
      </div>
    </div>
    

    candidates-list.component.ts

    import { Component, OnInit } from '@angular/core';
    
    import { SharedService } from '../shared.service';
    import { User } from '../user.model';
    
    @Component({
      selector: 'app-candidates-list',
      templateUrl: './candidates-list.component.html',
      styleUrls: ['./candidates-list.component.css']
    })
    export class CandidatesListComponent implements OnInit {
    
      originalCandidatesList: User[];
      candidatesList: User[];
    
      constructor(private sharedService: SharedService) { }
    
      ngOnInit() {
        console.log('in candidates list component');
    
        this.sharedService.selectedStatusObs.subscribe((selectedStatus: number) => { // subscribe to the selected status first
          console.log(selectedStatus);
          if (!selectedStatus) {
            // no status has been selected
    
            console.log('no status selected');
    
            this.sharedService.getAllCandidatesV2().subscribe((res: User[]) => { // get all the users from the service
              // console.log(res);
              // console.log(typeof(res));
              this.originalCandidatesList = res;
              this.candidatesList = res;
            });
    
          } else {
            // some status has been selected
            console.log('some status selected >>> ', this.sharedService.statuses);
    
            const selectedStatusObj = this.sharedService.statuses.find(item => item.code === +selectedStatus);
    
            console.log('selectedStatusObj >>> ', selectedStatusObj);
    
            // just getting the selected status candidates
            this.candidatesList = this.originalCandidatesList.filter(item => item.status === selectedStatusObj.name);
          }
        });
    
      }
    
    }
    

    candidates-list.component.html

    <table class="table table-responsive table-hover">
      <thead>
        <tr>
          <th>First Name</th>
          <th>Last Name</th>
          <th>Email Address</th>
          <th>Age</th>
          <th>Status</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor='let can of candidatesList'>
          <td>{{ can.firstName }}</td>
          <td>{{ can.lastName }}</td>
          <td>{{ can.emailAddress }}</td>
          <td>{{ can.age }} years</td>
          <td>{{ can.status }}</td>
        </tr>
      </tbody>
    </table>
    

    candidates-filter.component.ts

    import { Component, OnInit } from '@angular/core';
    
    import { SharedService } from '../shared.service';
    
    @Component({
      selector: 'app-candidates-filter',
      templateUrl: './candidates-filter.component.html',
      styleUrls: ['./candidates-filter.component.css']
    })
    export class CandidatesFilterComponent implements OnInit {
      statuses = [];
      selectedStatus: number;
    
      constructor(private sharedService: SharedService) { }
    
      ngOnInit() {
        console.log('in candidates filter component');
        this.statuses = this.sharedService.statuses;
      }
    
      filterCandidates() {
        console.log(this.selectedStatus);
        this.sharedService.selectedStatusObs.next(this.selectedStatus);
      };
    
      resetFilters() {
        // emil null to the selectedStatus Observable
        this.sharedService.selectedStatusObs.next(null);
      }
    
    }
    

    candidates-filter.component.html

    <div class="row col-xs-12">
      <div class="row col-xs-8">
          <label class="col-xs-3">Select Status </label>
          <select class="col-xs-9 form-control" [(ngModel)]="selectedStatus">
              <option *ngFor="let status of statuses" [value]="status.code">
                 {{ status.name }}
              </option>
          </select>
      </div>
      <div class="col-xs-4" style="margin-top: 25px;">
        <button type="submit" class="btn btn-primary" (click)="filterCandidates()">Search</button>
        <button type="submit" class="btn btn-default" (click)="resetFilters()">Reset</button>
      </div>
    </div>
    

    shared.service.ts

    import { Injectable } from '@angular/core';
    import { BehaviorSubject, of } from 'rxjs';
    import { User } from './user.model';
    
    @Injectable({ providedIn: 'root' })
    
    export class SharedService {
    
      selectedStatusObs = new BehaviorSubject<number>(null); // this is the observable to watch the selected status in the filter component
    
      candidates: User[] = [ // a list of users
        {
          firstName: 'Thierry',
          lastName: 'Henry',
          emailAddress: 'henry@test.com',
          age: 1,
          status: 'Active'
        },
        {
          firstName: 'Alexis',
          lastName: 'Sanchez',
          emailAddress: 'sanchez@test.com',
          age: 28,
          status: 'Idle'
        },
        {
          firstName: 'Denis',
          lastName: 'Bergkamp',
          emailAddress: 'Bbrgkamp@test.com',
          age: 29,
          status: 'Active'
        },
        {
          firstName: 'Jerman',
          lastName: 'Defoe',
          emailAddress: 'defoe@test.com',
          age: 22,
          status: 'Active'
        },
        {
          firstName: 'Kun',
          lastName: 'Aguero',
          emailAddress: 'aguero@test.com',
          age: 25,
          status: 'Offline'
        },
      ];
    
      statuses = [
        {
          code: 1,
          name: 'Active'
        },
        {
          code: 2,
          name: 'Offline'
        },
        {
          code: 3,
          name: 'Idle'
        }
      ]
    
      getAllCandidatesV2() {
        return of(this.candidates); // trying to mock the HTTP request via this 'of' observable
      }
    
    }
    

    user.model.ts

    export class User {
      constructor(
        public firstName: String,
        public lastName: String,
        public emailAddress: String,
        public age: Number,
        public status: String
      ) { }
    }
    

    【讨论】:

    • 您好 Mohammed,您的建议是正确的。另外,感谢您的解释,它非常有用:) 只是一件事,我觉得这很令人不安(但在 Angular 中可能是正常的)。我仍然必须订阅 loadAll() 中的初始化。否则,我一开始没有数据......你知道为什么吗? loadAll() { this.subscriptionService.test$.subscribe(...); this.subscriptionService.getSubscriptions().subscribe(); }
    • 啊是的,你是对的,好像这个组件没有任何过滤就打开了,它应该返回所有数据,我们应该订阅 test$ observable 只有在一些过滤存在的情况下,我刚刚更新了我的答案,你现在可以检查一下吗?
    • 我刚测试过,还是不行。我想这是因为我只在页面中时才调用 loadAll() ,一开始。但是,当我更改过滤器时,我的组件视图不知道 test$ 已更改。
    • 你设置了 this.subscriptionService.isFilteringMode = true;在订阅过滤器组件中调用此方法 filterOnCRNAOnly?
    • 是的,我做到了。这就是为什么我认为这是因为 loadAll() 在 ngOnInit() 之后不再调用视图没有更新。
    猜你喜欢
    • 2016-09-25
    • 1970-01-01
    • 2020-03-26
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 2019-02-07
    • 2021-09-13
    • 2021-03-26
    相关资源
    最近更新 更多