在 Angular2 中通信三个组件(parent 与两个孩子)

Communicate Three components(parent with two childs) in Angular2

我正在 Angular 中处理购物车 2.There 应用程序组件(第 3 个)中包含 2 个组件(类别和产品列表)。现在的问题是我无法获得沟通 child components.I 已经尝试了以下两种解决方案..

解决方案 1: 我使用产品组件作为类别组件中的提供者,除了类别 select 之外的所有内容都有效,产品组件的视图不是 updating/refreshing .

解决方案2:我在共享服务中使用了"rxjs/Subject"并将selected类别传递给产品组件但不知道如何调用函数类别 select/change 上产品组件的 (getProducts())。任何帮助都将是高度 appreciated.Thanks

创建一个新服务,例如sibling-data-exchange.service.ts,然后把这段代码放在里面:

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

在您的两个组件中导入并订阅此服务,并添加一个方法来将值存储在服务中。

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: 不要忘记在相应模块中提供新服务!

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

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

由于订阅了 BehaviorSubject,现在您的组件通过服务进行通信。

您说过,您的 CategoryComponent 将一个列表传递给 ProductsComponent,一旦该列表到达,就会调用 getProducts() 方法。嗯,要做到这一点,我真的建议使用上面描述的 service-communication。

在您的 ProductsComponent 中,您必须按如下方式修改订阅:

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

这种变体也是可能的。这里直接使用'value'作为参数。但是因此 getProducts() 的签名必须匹配,例如

getProducts(categories: Array<string>)

然后

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

请注意,此订阅意味着每次都会调用您的方法 CategoryComponent 传入一个新列表。这应该正是您要搜索的内容。

类别(作为子组件)和产品(作为父组件)需要使用@Input()@Output()进行交互。

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

我想,这会解决你的问题。

好的,我的演示应用程序来了。这只是一个示例,向您展示您的应用程序应如何构建才能正常运行。我知道你调用了 web 服务,但你适应它应该没问题:

我有一个订阅了 CategoryService 的 CategoryComponent。

模板

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

打字稿文件

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

}

只要单击类别,服务就会更新。

接下来我有一个 ProductsComponent,它订阅了 CategoryService 和 ProductsService 并包含一个子组件 ProductComponent。

模板

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

打字稿文件

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

}

当您选择一个产品时,它的详细信息会通过 ProductComponent 和 ProductsComponent 之间的输入绑定显示在 ProductComponent 中。当 CategoryComponent 中的类别发生变化时,您的产品列表会根据所选的复选框扩大或缩小。

ProductComponent 来了

模板

<H2>Product Component</H2>
<div>Price: {{product?.details?.price}}</div>
<div>Age: {{product?.details?.age}}</div>

和打字稿文件

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() { }

}

请注意,我遗漏了所有 sass 文件,因为它们是空的。但是您必须拥有它们才能编译您的应用程序!

这是我使用的服务和 model.ts。

类别服务

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

产品服务

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

还有我的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
    ) { }
}

最后,我的 main-page.module.ts,我将其导入到应用程序中。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 {

} 

如果您将所有这些放在一起,您将拥有一个可以正常运行的小型应用程序,它完全符合您在 post 中描述的功能。希望对您有所帮助。

PS:它当然是可以优化的,以防类别列表发生变化时已经做出的选择发生什么,但它只是一个演示。 ;)

关于你今天的问题

我在 javascript 中有一个代码如下 var Output1; window.setInterval(function () { Output1 = Math.random(); },1000); console.log(输出1);并想在外面访问 Output1 但它不起作用。能告诉我在外面访问 Output1 的任何方式吗?

我建议采用以下解决方案:

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>

如果你想监控输出 1(仅限控制台!),只需这样做:

var monitor = window.setInterval(function () { console.log(output1); },500);