Okay, so here comes my demo app. This is just an example to show you how your app should be structured to work properly. I know that you call web services but it should be no problem for you to adapt it though:
I have a CategoryComponent that subscribes to the CategoryService.
The template
<H2>Category Component</H2>
<div *ngFor="let category of categories; let ndx=index;">
<label>{{category?.name}}</label>
<input type="checkbox" id="cat.{{ndx}}" [(ngModel)]="category.selected" (change)="emitChange()">
</div>
The typescript file
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CategoryService } from '../services/category.service';
import { Category } from '../models/models';
@Component({
moduleId: module.id,
selector: 'pm-category',
templateUrl: 'category.component.html',
styleUrls: ['category.component.scss']
})
export class CategoryComponent implements OnInit, OnDestroy {
private categories: Array<Category> = [];
constructor(private categoryService: CategoryService) {
}
ngOnInit() {
this.categoryService.getCategories().subscribe(list => {
if (list) {
this.categories = list;
}
});
}
ngOnDestroy() { }
private emitChange(): void {
this.categoryService.setCategories(this.categories);
}
}
Whenever a Category gets clicked the service gets updated.
Next I have a ProductsComponent that subscribes to the CategoryService and the ProductsService and contains a child component ProductComponent.
The template
<H2>Products Component</H2>
<div *ngFor="let product of products; let ndx=index;">
<label>{{product?.name}}</label>
<input type="radio" name="productGroup" id="prod.{{ndx}}" value="product" (change)="setSelectedProduct(product)">
</div>
<pm-product [product]="product"></pm-product>
The typescript file
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ProductsService } from '../services/products.service';
import { CategoryService } from '../services/category.service';
import { Product } from '../models/models';
@Component({
moduleId: module.id,
selector: 'pm-products',
templateUrl: 'products.component.html',
styleUrls: ['products.component.scss']
})
export class ProductsComponent implements OnInit, OnDestroy {
public product: Product;
private products: Array<Product> = [];
constructor(
private productsService: ProductsService,
private categoryService: CategoryService
) { }
ngOnInit() {
this.productsService.getProducts().subscribe(list => {
if (list) {
this.products = list;
}
});
this.categoryService.getCategories().subscribe(list => {
const allProducts = this.productsService.getProductsValue();
const filteredProducts: Array<Product> = [];
let tempProducts: Array<Product> = [];
list.forEach(cat => {
tempProducts = allProducts.filter(prod => (prod.categoryId === cat.id) && (cat.selected === true));
if (tempProducts.length > 0) {
tempProducts.forEach(el => {
filteredProducts.push(el);
});
}
});
this.products = filteredProducts;
});
}
ngOnDestroy() { }
private setSelectedProduct(product: Product): void {
this.product = product;
}
}
When you chose a product its details get displayed in the ProductComponent through Input Binding between ProductComponent and ProductsComponent. And when a category gets changed in the CategoryComponent your list of products gets widened or narrowed depending on the selected checkboxes.
Here comes the ProductComponent
The template
<H2>Product Component</H2>
<div>Price: {{product?.details?.price}}</div>
<div>Age: {{product?.details?.age}}</div>
and the typescript file
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { Product } from '../models/models';
@Component({
moduleId: module.id,
selector: 'pm-product',
templateUrl: 'product.component.html',
styleUrls: ['product.component.scss']
})
export class ProductComponent implements OnInit, OnDestroy {
@Input() product: Product;
ngOnInit() { }
ngOnDestroy() { }
}
Please note that I left out all the sass files als they are empty. But you have to have them in order to have your app compile!
Here are both of the services and the model.ts I used.
The CategoryService
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Category } from '../models/models';
@Injectable()
export class CategoryService {
private categories: BehaviorSubject<Array<Category>> = new BehaviorSubject([]);
constructor() {
const list: Array<Category> = [];
const categoryA: Category = new Category('CategoryA', false);
categoryA.id = 1000;
list.push(categoryA);
const categoryB: Category = new Category('CategoryB', false);
categoryB.id = 2000;
list.push(categoryB);
const categoryC: Category = new Category('CategoryC', false);
categoryC.id = 3000;
list.push(categoryC);
this.setCategories(list);
}
getCategories(): Observable<Array<Category>> {
return this.categories.asObservable();
}
getCategoriesValue(): Array<Category> {
return this.categories.getValue();
}
setCategories(val: Array<Category>) {
this.categories.next(val);
}
}
The ProductsService
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Product, Details } from '../models/models';
@Injectable()
export class ProductsService {
private products: BehaviorSubject<Array<Product>> = new BehaviorSubject([]);
constructor() {
const list: Array<Product> = [];
const detailsA: Details = new Details(33, 12);
const productA: Product = new Product('ProductA', detailsA, 1000);
productA.id = 200;
list.push(productA);
const detailsB: Details = new Details(1002, 56);
const productB: Product = new Product('ProductB', detailsB, 1000);
productB.id = 400;
list.push(productB);
const detailsC: Details = new Details(9, 4);
const productC: Product = new Product('ProductC', detailsC, 2000);
productC.id = 600;
list.push(productC);
this.setProducts(list);
}
getProducts(): Observable<Array<Product>> {
return this.products.asObservable();
}
getProductsValue(): Array<Product> {
return this.products.getValue();
}
setProducts(val: Array<Product>) {
this.products.next(val);
}
}
And my models.ts
export class Category {
constructor(
public name: string,
public selected: boolean,
public id?: number
) { }
}
export class Details {
constructor(
public price: number,
public age: number
) { }
}
export class Product {
constructor(
public name: string,
public details: Details,
public categoryId: number,
public id?: number
) { }
}
Finally, my main-page.module.ts, which I imported into the app.module.ts.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { MainPageComponent } from './main-page.component';
import { CategoryComponent } from '../category/category.component';
import { ProductComponent } from '../product/product.component';
import { ProductsComponent } from '../products/products.component';
import { CategoryService } from '../services/category.service';
import { ProductsService } from '../services/products.service';
@NgModule({
imports: [
FormsModule,
BrowserModule,
HttpClientModule,
RouterModule.forRoot([
{ path: 'pm-main-page', component: MainPageComponent },
]),
],
declarations: [
MainPageComponent,
CategoryComponent,
ProductComponent,
ProductsComponent
],
exports: [
MainPageComponent,
CategoryComponent,
ProductComponent,
ProductsComponent
],
providers: [
CategoryService,
ProductsService
]
})
export class MainPageModule {
}
If you put all this together you'll have a small working app which exactly does what you described in your post. I hope you this helps.
PS: it's of course optimizable in case of what happens to already made selections when the category list changes and such, but it's only supposed to be a demo. ;)