-1

I am working on a shopping cart in Angular 2.There are 2 components (category and products listing ) that included in app component(3rd one).Now problem is that I am unable to get communicate both child components.I have tried two solutions as below..

Solution1: I have used products component as provider in category compoment and everything working except on category select, view of products's component is not updating/refreshing.

Solution2: I have use "rxjs/Subject" in shared service and passing selected categories to products component but don't know how to call a function(getProducts()) of product component on category select/change.Any help will be highly appreciated.Thanks

Irphan Khan
  • 75
  • 1
  • 2
  • 8
  • You should read this documentation about component interaction https://angular.io/guide/component-interaction – Javascript Hupp Technologies Mar 21 '18 at 07:12
  • Use a Service. Put a Subject or BehaviorSubject inside this service and let both Child-Components subscribe to it. Then the both of them can communicate and exchange data. –  Mar 21 '18 at 07:16
  • see this https://stackoverflow.com/questions/49387889/passing-data-with-subjects-and-proxies/49388249#49388249 – Vikas Mar 21 '18 at 07:25
  • Use @Input() and @Output() for category and products to interact and place both of them in your app component. You can also use in app component. In this case app component is parent and both(category and products) are child – Amol Bhor Mar 21 '18 at 12:18

4 Answers4

1

You need to use @Input() and @Output() for category(as a child component) and product(as a parent component) to interact.

Parent html- [product.component.html]

<app-filter-categories [categoryResult]="categoryFilterValue" (clicked)="onClickedCategoryFilter($event)">
</app-filter-categories>

<div class="row">
<!--your code will come here for list of products-->
</div>

Parent component - [product.component.ts]

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

@Component({
      moduleId: module.id,
      selector: 'all-products',
      templateUrl: 'all-products.component.html',
      changeDetection: ChangeDetectionStrategy.Default
})

export class AllProductsComponent implements OnInit {

  constructor(private _productsService: ProductsService,,
        private _router: Router) {
  }

  ngOnInit() {
     this.fillAllProducts(0);
  }

  fillAllProducts(pageIndex) {
     this.loadingProgress = true;
     var searchParams = new SearchParametersCategoryModel();
     searchParams.CategoryFilter = this.categoryFilterValue;
     searchParams.PageIndex = pageIndex;

     //TODO - service call
  }

  onClickedCategoryFilter(filterValue: any) {
    this.categoryFilterValue = filterValue;
    this.allProductData$ = [];
    this.currentPageIndex = 0;
    this.fillAllProducts(this.currentPageIndex);
  }

  selectProduct(productId: any, selectedProductCart) {
    //TODO
  }
}

Child html- [category.component.html]

<div class="row">
<!--your code will come here for different type of categories-->
</div>

Child component - [category.component.ts]

// libs
import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import { CategoriesService } from '../service/categories.service';

@Component({
  moduleId: module.id,
  selector: 'app-filter-categories',
  templateUrl: 'category.component.html'
})

export class CategoryFilterComponent implements OnInit, AfterViewInit {
  toggle = true;
  @Input() categoryResult: any = '';
  @Output() clicked = new EventEmitter<any>();

  Category = [];
  SelectedCategoryItems = [];
  categoryFilterList: DictionaryFilter<string> = {};    //here u will add list of selected categories with key-value

  constructor(private _categoriesService : CategoriesService) {
  }

  ngOnInit() {
    //todo - fill categories list
  }

  onClickSelectCategory(searchType: any, event: any): any {
    if (!(event.target.parentElement.className.search('active') > 1)) {
      //this.removeFilterElementWithKey(searchType);
      //above line to add logic for remove deselected data from categoryFilterList
    } else {
       this.categoryFilterList.push(element);
    }

    this.clicked.emit(this.categoryFilterList);
  }
}

I think, this will solve your problem.

Amol Bhor
  • 2,322
  • 1
  • 12
  • 14
1

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. ;)

1

Referring to your today's question

I have a code in javascript as below var Output1; window.setInterval(function () { Output1 = Math.random(); },1000); console.log(Output1); and want to access Output1 outside but its not working. Can please tell me any way I could access Output1 outside ?

I'd suggest the following solution:

var output1;

function setCurrentValue() {output1 = Math.random();}
function sendNumber(){ $.post( "test.php", {rng: output1} ); }

var currInterval = window.setInterval(function () { setCurrentValue(); },1000);

<button onclick="sendNumber();return false;"> Send</button>

And if you want to monitor output1 (for console only!) just do it this way:

var monitor = window.setInterval(function () { console.log(output1); },500);
  • @Irphan Khan: So here is the promised solution. If it works for you, please upvote. Thx in advance. –  Apr 09 '18 at 08:42
  • This is what I want to achieve but unfortunatly, its not working. `var currInterval = window.setInterval(function () { setCurrentValue(); },1000); var monitor = window.setInterval(function () { console.log(rngOutput1); },500); function sendNumber(){ $.post( "test.php", {rng: monitor} ); } ` – Irphan Khan Apr 09 '18 at 09:56
  • I re-edited my post once more, considering your comment. Please, try it this way. Please not, that the monitoring function's purpose was to present you the variable's content in the console. the object monitor returns the id of the Interval-Task and is only useful to stop the interval. -- Post output1 directly. It should work. –  Apr 09 '18 at 11:00
  • :Excellent! Its working .Thank you very much for your time. – Irphan Khan Apr 09 '18 at 11:37
0

Create a new service, for example sibling-data-exchange.service.ts and put this code inside:

import { Observable } from 'rxjs/Rx';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Injectable } from '@angular/core';

@Injectable()
export class SiblingDataExchangeService {
  private info = new BehaviorSubject('information');

  getInfo(): Observable<string> {
    return this.info.asObservable();
  }

  getInfoValue(): string {
    return this.info.getValue();
  }

  setInfo(val: string) {
    this.info.next(val);
  }
}

In both of your components import and subscribe to this service and add a method to store the value in the service.

import { SiblingDataExchangeService } from 'sibling-data-exchange.service';

    constructor(
        private siblingDataExchangeService: SiblingDataExchangeService
    ) { 

        // stay updated whenever your sibling changes value
        this.siblingDataExchangeService.getInfo().subscribe(value => {
            // do something with this value
            this.copyOfValue = value;
        });
    }

    // update value for your sibling
    private communicateValue(value: string): void {
       siblingDataExchangeService.setInfo(value);
    }

PS: Don't forget to PROVIDE the new service in your corresponding module!

import { SiblingDataExchangeService } from 'sibling-data-exchange.service';

@NgModule({
    imports: [
       ...
    ],
    exports: [
       ...
    ],
    declarations: [
       ...
    ],
    providers: [
        SiblingDataExchangeService
    ]

Now your components communicate via a service due to the subscription to the BehaviorSubject.

You said, that your CategoryComponent passes a list to the ProductsComponent and as soon as this list arrives the getProducts()-method is to be called. Well, to do this I really recommend to use the service-communication I described above.

In your ProductsComponent you then have to modify the subscription as follows:

this.siblingDataExchangeService.getInfo().subscribe(value => {
    // pass the value to your local list. (supposed you have one)
    this.categories = value; 

    // then call your method
    this.getProducts(); 
});

This variant is also possible. Here you use 'value' directly as a parameter. But therefor the signature of getProducts() has to match e.g.

getProducts(categories: Array<string>)

And then

this.siblingDataExchangeService.getInfo().subscribe(value => {
    this.getProducts(value); 
});

Please note, that this subscription means, that your method gets called every time CategoryComponent passes in a new list. This ought to be exactly what you are searching for.

  • Thanks for the reply but I want to say, data(i.e selected categories) is passing to products components fine but there is a function "getProducts()" in products component that need to be called on recieing this data from category component. – Irphan Khan Mar 21 '18 at 08:02
  • Please provide some code or a brief sketch of how your components are currently structured. –  Mar 21 '18 at 10:29
  • 1
    Please reread my solution. I extended it considering your comment from this morning. –  Mar 21 '18 at 13:05
  • Thanks for the explaination and i'm very near to solution but there is little confusion.Below are the screenshots of components & services. Kindly check and and suggest a slolution.Thank you very much. https://ibb.co/iyP9hx https://ibb.co/gPG0pc https://ibb.co/bx6N2x – Irphan Khan Mar 22 '18 at 05:40
  • Please, don't get me wrong, but your code looks problematic. For example: If you GET products from a service then name this service method getProducts() instead of just products(). And I still don't understand how your app is supposed to work. Please draw a sketch. What shall happen if one chooses a category? When is getProducts() fired? As I said, don't get me wrong. We all somewhen started from scratch. But I assure you that you'll run into deep trouble with your app, if you do not restructure it now. I need a sketch of how it is intended to work, to be able to help you. –  Mar 22 '18 at 07:45
  • I'll have look on it but it might take some time. I'll let you know a.s.a.p. –  Mar 22 '18 at 16:36
  • Okay, so now, with answer 3, I gave you a working demo app. It contains all you described. You have to adjust it to your needs afterwards but all of your topics are covered. –  Mar 23 '18 at 00:24
  • Thank you very much – Irphan Khan Mar 26 '18 at 09:31
  • My pleasure. Great to hear that. ;o) –  Mar 26 '18 at 10:42
  • I have a code in javascript as below `var Output1; window.setInterval(function () { Output1 = Math.random(); },1000); console.log(Output1);` and want to access `Output1` outside but its not working. Can please tell me any way I could access `Output1` outside ? – Irphan Khan Apr 09 '18 at 04:31
  • Your problem is, that console.log() is processed immediately and only once, but Output1 gets filled after 1 sec for the first time and then repeatedly. I'd suggest to call with a method. function showCurrentValue() {console.log(Output1);} var Output1; window.setInterval(function () { Output1 = Math.random(); showCurrentValue(); },1000); –  Apr 09 '18 at 04:47
  • Thanks for the reply.Its working fine but my problem is that I have to send this number on a api call so I need it as variable.for example: a timer is running from 1,2,3,4,5....and so on and someone hit an api at the moment when number is 5, he should get the value 5 and timer continues and if there is another api call then again it should send the current value.This process need to be repeated. I hope I could explain the senariao ? – Irphan Khan Apr 09 '18 at 05:39
  • if I am doing `console.log(Output1);` it always undefined – Irphan Khan Apr 09 '18 at 05:46