1

I have been developing an e-commerce app with Angular 14.

I am currently working on a product search feature.

export class ProductListComponent extends ComponentWithLoading implements OnInit {

    public searchCriteria: string;
    public searchText: string;
    
    constructor(
        private route: ActivatedRoute,
        private router: Router,
        private productService: ProductService
      ) {
        super();
    }
      
      
    public searchProducts(
        page?: number,
        pageSize?: number,
        searchCriteria?: string,
        searchText?: string
      ): void {
        this.showLoading();
        this.productService.setSearchParams(this.searchCriteria, this.searchText);
        this.productService
          .searchProducts(page, pageSize, searchCriteria, searchText)
          .pipe(
            take(1),
            tap((response: ApiPagination<ProductBase[]>) => {
              this.products = response.content ?? [];
              this.pageSettings = response.page;
            }),
            delay(250),
            finalize(() => this.hideLoading())
          )
          .subscribe();
      } 
}

<div class="product-search">
  <mat-form-field class="search-box">
    <input matInput placeholder="Search..." [(ngModel)]="searchText">
  </mat-form-field>

  <mat-form-field>
    <mat-select placeholder="Search by" [(ngModel)]="searchCriteria">
      <mat-option value="name">Name</mat-option>
      <mat-option value="role">Category</mat-option>
      <mat-option value="country">Country of origin</mat-option>
    </mat-select>
  </mat-form-field>

  <button mat-raised-button color="primary" (click)="searchProducts(page, pageSize, searchCriteria, searchText)">Search</button>
</div>

In the ProductService I have:

export class ProductService implements BaseService {

  public searchParams: object[];

  constructor(
    private http: HttpClient
  ) {}


  public setSearchParams(searchCriteria: string, searchText: string) {
    this.searchParams = [{
      searchCriteria: searchCriteria,
      searchText: searchText
    }];
    console.log('Search params from the ProductService', this.searchParams);
  }

  public searchProducts(
      pageNumber?: number,
      size?: number,
      searchCriteria?: string,
      searchText?: string,
      ): Observable<ApiPagination<ProductBase[]>> {
    return this.productRepo.searchProducts(pageNumber, size, searchCriteria, searchText);
  }
  
}

The setSearchParams method above successfully returns the search parameters from the search-box.

I also use a pagination library to paginate the products list(s), whether they result from a search or not. Here is the library:

export class Pagination {
  public pageNumber: number;
  public size: number;
  public searchParams: object[];

  constructor(
    pageNumber?: number,
    size?: number,
    searchParams?: object[]
  ) {
    this.pageNumber = pageNumber ?? 0;
    this.size = size ?? 10;
    this.searchParams = [
      {
        searchCriteria: 'name',
        searchText: 'Laptop'
      }
    ];
  }

    public getPaginationParams(): HttpParams {
     let params = new HttpParams();

     if (this.searchParams.length) {
      this.searchParams.forEach(sp => {
        Object.entries(sp).forEach(entry => {
           params = params.append(entry[0], entry[1]);
        });
      });
    }

    params = params.append('page', this.pageNumber);
    params = params.append('size', this.size);

    return params;
  }
}

I use the library in another service:

export class ProductRepository {

    public searchParams: object[];

    constructor(private httpClient: HttpClient, private apiService: ApiService) { }

    public searchProducts(
        pageNumber?: number,
        size?: number,
        searchCriteria?: string,
        searchText?: string
      ): Observable<ApiPagination<ProductBase[]>> {
        const url = 'ProductsSearch';
        const params = new Pagination(
          pageNumber,
          size
        ).getPaginationParams();
        console.log(params);

        return this.httpClient
          .get<ApiPagination<ProductBase[]>>(this.apiService.constructUrl(url), {
              params
            }
          )
          .pipe(catchError(() => of()));
    }

}

The above class takes the params from the library and I understand the necessity. But I need to also pass the searchParams object to the library.

The goal

The goal (necessity) is to make the Pagination library take (the values of) it's search params (searchParams variable) from the ProductService service, instead of the hardcoded "name" and "Laptop".

In other words, I take the search params from ProductListComponent, via the UI and the model, and need to pass it in the pagination library.

The problem

Importing the service in the library not only seems bad software design, but it results in compilation errors.

Question

How can I bring the search params from the ProductService service to the library?

Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252

2 Answers2

1

why you not store your data on an subject like a behaviourSubject, and when u need u subscribe, get the data and unsubscribe

use ur service to create your behaviorSubject and store the data whenever you want, after u only have to subscribe on your service behaviorSubject

on your service do this:

    yourSubject$ = new BehaviorSubject(<unknow>);

on your component that have the data:

    paramsSubject$ = yourservice.yourSubject$
paramsSubject$.next("b");

And whenever you want the data do this:

    paramsSubject$ = yourservice.yourSubject$
paramsSubject$.subscribe(value => {
  console.log("Subscription got", value); 
}); 

i think you can do the same thing as i answered here

Angular: how to share data from custom-component with app-component (app-root)

  • Is that a way to pass a variable from a service to a library or an _alternative_ to doing that? – Razvan Zamfir Feb 07 '23 at 09:10
  • In terms of solid, there is a code smell related to srp. Why are you storing "searchParams" if you don't use it into the service, besides this on every call basically you are sending all the information which is encapsulated within that searcParams, and based on that you could create anytime that object. – Zsolt Balint Feb 07 '23 at 11:00
  • I am not trying to pass the variable to a component, but to a *library*. – Razvan Zamfir Feb 07 '23 at 12:35
  • @ZsoltB I take the search params from ProductListComponent, via the UI and the model, and need to pass it in the pagination library. – Razvan Zamfir Feb 07 '23 at 14:36
  • But what is the point with seachCriteria and searcTex setting it within the service? you are already passing those ones in the component by calling this.productService .searchProducts(page, pageSize, searchCriteria, searchText). Setting it like this : this.productService.setSearchParams(this.searchCriteria, this.searchText), basically are the same value which you passed at the search call. – Zsolt Balint Feb 07 '23 at 19:00
1

Looking at your question and comments, seems like you really want to pass the data to your library. In that case, I'd just use sessionStorage to avoid any circular dependency or compiler error.

  • You can also use localStorage if you want to keep the data beyond the session for better user experience.
public setSearchParams(searchCriteria: string, searchText: string) {
    this.searchParams = [{
      searchCriteria: searchCriteria,
      searchText: searchText
    }];
    console.log('Search params from the ProductService', this.searchParams);
    sessionStorage.setItem('searchParams', JSON.stringify(this.searchParams))
  }

In pagination library:

this.searchParams = JSON.parse(sessionStorage.getItem('searchParams')  as string);
Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
Nehal
  • 13,130
  • 4
  • 43
  • 59
  • It is a nice solution, but isn't it "external" to Angular and RxJs? :) – Razvan Zamfir Feb 07 '23 at 22:17
  • Using Angular doesn't mean LS/SS/cookies are forbidden. They are still at your disposal. It's what you store in them that matters. PII or sensitive data should not be stored in such storage, but harmless data like search params can easily be managed with them. – Nehal Feb 08 '23 at 01:03
  • In this [question](https://stackoverflow.com/q/51536262) I don't see anyone opposing to using LS/SS with Angular. – Nehal Feb 08 '23 at 01:15
  • Importing the `ProductService` into `ProductRepository` results in a `No provider for ProductService!` error and adding it to the `ProductRepository` results in a `Circular dependency` error. – Razvan Zamfir Feb 08 '23 at 08:15
  • Got it! Removed from my answer. – Nehal Feb 08 '23 at 09:01
  • In the service, `console.log(sessionStorage.setItem('searchParams', JSON.stringify(this.searchParams)))` returns `undefined`. – Razvan Zamfir Feb 08 '23 at 11:24
  • That's what it's designed to do. `setItem` doesn't return anything to `console.log`. If you want to check what's stored, use 'getItem', or [check in dev tool of Chrome](https://stackoverflow.com/questions/20170381/what-is-session-storage-in-chrome-developer-tools) – Nehal Feb 08 '23 at 14:43