Субъект не реагирует на отфильтрованные данные в Angular 8 - PullRequest
0 голосов
/ 08 апреля 2020

Прежде всего, я не являюсь разработчиком Front-end. Я больше специализируюсь на серверной разработке. Но я должен сделать приложение с Angular (я выбрал Angular 8). В предыдущей работе я использовал Angular. Но я впервые создаю приложение Angular с нуля.

Мне нужно читать, добавлять, изменять и удалять объекты (так называемые подписки). Все это хорошо.

За исключением случаев, когда я буду sh фильтровать с помощью формы ...

Чтобы быть больше в коде, моя страница 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- просмотреть часть:

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 не обновляет sh с правильным data

Logger для отладки

Должен признаться, я не знаю, что делать дальше ... Итак, немного помощи будет благодарен.

Заранее спасибо.

(извините за мой английский sh, это не мой родной язык)

1 Ответ

0 голосов
/ 08 апреля 2020

обновление, это решение работает, только если два компонента не отображаются одновременно

в компоненте представления подписки, вы подписываетесь на метод инициализации только в службе

поэтому, если в методе onlyCRNAFilterForSubject в службе произошли некоторые изменения, вы не будете знать об этом

мы можем добавить логическое свойство к службе, чтобы указать, находимся ли мы в режиме фильтрации или нет, и установите это свойство в компоненте фильтра подписки, когда мы вызываем службу

, поэтому вместо постоянной подписки на метод инициализации мы можем подписаться на тест $ observable, если мы находимся в режиме фильтрации

поэтому в сервисе нам нужно определить новое логическое свойство, подобное этому

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

, а в компоненте фильтра подписки нам нужно установить для этого свойства значение true после выбора какой-либо CRNA

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

в компоненте представления подписки, в режиме инициализации мы подпишемся на сервисную функцию, которая возвращает весь массив, и когда выбран некоторый CRNA (режим фильтрации), мы можем подписаться на тест $ 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>

sources-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);
      }
    });

  }

}

sources-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>

кандидатов-filter.compone nt.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);
  }

}

sources-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
  ) { }
}
...