1

I am developing a simple search screen to learn angular subscription which i find to be very confusing. In my home page page I created two components. One filter components and second search result component.

Here is my filter service

export class FilterService {
    private filtersubject = new Subject<Filter>();
    public currentfilters: Observable<Filter>;

    constructor() {
        this.filtersubject = new BehaviorSubject<Filter>(Filter.getInstance());
        this.currentfilters = this.filtersubject.asObservable();
    }

    getFilters(): Observable<Filter> {
       return this.filtersubject.asObservable();
    }

    sendFilterMessage() {
      this.filtersubject.next(Filter.getInstance());
    }

    clearFilterMesasage() {
       this.filtersubject.next(new Filter());
    }
}

Here is my search result service

export class SearchresultsService {
  public searchresult!: Observable<ResultData[]>;
  private searchresultsubject = new Subject<ResultData[]>();
  private f: Filter = Filter.getInstance();
  subscription: Subscription;
  

  constructor( private httpClient: HttpClient, private filterservice: FilterService ) {
    this.subscription = this.filterservice.currentfilters.subscribe(
    (filter: Filter) => {
      this.f = filter;
      this.searchData();
      this.searchresult = this.searchresultsubject.asObservable();
    });
  }  

  public searchData(): void {
    const url = `${environment.ssoUrl}api/SearchrResult`;
    this.httpClient.get<ResultData[]>(url, { params, responseType : 'json'}).
    subscribe((response: ResultData[]) => {
      this.searchresultsubject.next(response);
    });
  }
 }

in search result i have a link for each record that navigates to details page. There you do some CRUD operation and on Submit navigate back to home page. The problem is that after CRUD operation and navigation back to search screen the subscription never got cleared and retains the previous search options. In search components i have

resetFilterData(formid: number){    
  this.filterservice.clearFilterMesasage();
  this.router.navigate(["details", id]);
}

Also in homecomponents.ts I have

export class HomeComponent implements OnInit, OnDestroy {  
    subscription: any;
    constructor(
        private router: Router,
        public filterSer: FilterService ) {    
        this.subscription = this.filterSer.currentfilters.subscribe();
    }
    ngOnDestroy(): void {
        this.subscription.unsubscribe();
    }           
    ngOnInit() {
       //some code
    }
}

Am I missing something in the code or not doing it right. why did the subscription not reset after navigation. Please advice THanks

enter image description here

DotNetBeginner
  • 412
  • 2
  • 15
  • 34
  • seems over-complicated to me... but filterSer.currentfilters doesn't reset on route change. Your SearchresultsService is a singleton, so it only triggers searchData once. in the constructor, at the beginning of the app. Not at the beginning of your component. You are creating multiple components, which all use the same service. – Random Jan 19 '22 at 20:32
  • @Random I have an application at work. it very complicated so to understand i recreated my app using the same idea to understand the working on it. – DotNetBeginner Jan 19 '22 at 21:04
  • i don't know that will make any differences, but can you move ```this.subscription = ```to ngOnInit ? – Talha Akca Jan 19 '22 at 22:47

1 Answers1

1

As the first comment suggests, this does seem overly complicated. I hope this example may provide a simpler setup in creating a search list with applied filters.

First, let's simplify your search result service. I typically follow the strategy that a service should be as generic as possible. So, all we want to do is request and return the complete list of results.

export class SearchresultsService {
  public searchresult$: Observable<ResultData[]>;

  constructor(private httpClient: HttpClient) {
    const url = `${environment.ssoUrl}api/SearchrResult`;
    this.searchresult$ = this.httpClient.get<ResultData[]>(url, { params, responseType: 'json' });
  }
}

Service files are where you can define observables, but you should try to avoid subscriptions. Leave that to the components that depend on the service's data.

Next, I created a sample list component that manages filters as local state. I took some liberties in assuming how your filter object actually works.

export class ListComponent {
  public searchResults$: Observable<ResultData[]>; 
  private filter = new BehaviorSubject<Filter>(new Filter());

  constructor(private searchService: SearchresultsService) {    
    this.searchResults$ = this.searchService.searchResult$.pipe(
      switchMap(results => this.filter.pipe(
        map(filter => results.filter(result => result[a] === filter[a]))
      ))
    );
  }

  public setFilter(criteria: any) {
    const newFilter = new Filter(criteria);
    this.filter.next(newFilter);
  }
}

Let's go through this step by step:

  1. We declare a BehaviorSubject that initializes with the default empty filter object.
  2. We add the method setFilter() that lets you create and emit a new filter from the same BehaviorSubject.
  3. In our constructor, we define an observable that takes all the data from our service HTTP request.
  4. We use switchMap() to subscribe to our filter BehaviorSubject to get the latest filtered value and apply it.

From here, you can subscribe to the searchResult$ observable in your component's template using the async pipe. This will automatically handle the subscribe and unsubscribe events throughout the component's lifecycle.

<ul class="search-results">
  <li *ngFor="let result of searchResult$ | async">
    <!-- do things with {{result}} -->
  </li>
<ul>

Now, when you click through the CRUD page, the list component will be destroyed along with all subscriptions. When you navigate back, the filter BehaviorSubject will be re-initialized with the default blank filter.

Joshua McCarthy
  • 1,739
  • 1
  • 9
  • 6
  • thanks for this simple example. this is quite easy to understand. The problem is i was asked to assist in a project that looks very much like the example i added in my question and i am very new to angular. I will try to simplify the search screen. I had a question if you don't mind answering. As the first comment above stated "Your SearchresultsService is a singleton, so it only triggers searchData once. in the constructor, at the beginning of the app" What makes a service singleton and how can i avoid making a service singleton. – DotNetBeginner Jan 24 '22 at 19:47
  • 1
    I'm not aware if that's possible, because it should be avoided. Services are where you manage the state of your data that is accessed across the app. It should be a singleton, so every component is accessing the same state. This is why I recommend keeping the filter data inside the component, as data doesn't persist after the component is destroyed (via route change). – Joshua McCarthy Jan 25 '22 at 02:03
  • What happens if the search criteria spans various component. The search page that i have to work on requires me to add 4 different components on the page plus other page specific control. That was the reason behind filter service. what will be the best option for this case. – DotNetBeginner Jan 25 '22 at 19:06
  • 1
    You could wrap those components inside a parent component, then the parent component can declare the filter state, and pass it to the child components via @Input. – Joshua McCarthy Jan 26 '22 at 01:33
  • Thank You @Joshua McCarthy – DotNetBeginner Jan 27 '22 at 19:12