两个组件中使用的共享服务

Shared service used in two component

我有一个使用数据库的服务,我可以在其中 get/add/delete 产品。 我在 2 个组件中使用这个 products.service.ts,因为我需要在我的应用程序的 2 个地方获取产品,

因此我想将我的服务切换到 observable/subject 这样我就可以订阅它并且它会在两个地方同步。

问题是,我现在正在努力进行 2 天的转换。如果有人可以帮助我,我将不胜感激!

这就像我在每个组件中使用不同的产品“状态”,因为当我在一个组件上添加产品时,它只更新这个组件,而第二个仍然是“旧”组件。

这是我目前的产品服务。

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Product } from '../products/product.model';
import { Observable, Subject } from 'rxjs';
import { map } from 'rxjs/operators'

const BASE_URL = 'http://localhost:8001/api/';

@Injectable({
  providedIn: 'root'
})
export class ProductsService {
  private model = '';
  constructor(private http: HttpClient) { }

  all(sellerId, token): Observable<Product[]> {
  this.model = 'products'
    return this.http.get(this.getUrlById(sellerId), {headers: {'Authorization' : `Bearer ${token}`}})
    .pipe(map((products:Product[]) => products))
  }

  create(product, token) {
    this.model = 'product'
    return this.http.post(this.getUrl(), product, {headers: {'Authorization' : `Bearer ${token}`}});
  }

  update(product, token) {
    this.model = 'product'
    return this.http.put(this.getUrlById(product.id), product, {headers: {'Authorization' : `Bearer ${token}`}});
  }

  delete(productId) {
    return this.http.delete(this.getUrlById(productId));
  }

  private getUrl() {
    return `${BASE_URL}${this.model}`;
  }

  private getUrlById(id) {
    return `${this.getUrl()}/${id}`;
  }
}

目前我将 all 函数添加为可观察对象,但我尝试的其他任何方法都不起作用,所以我将其保持原样。一般来说,我是 rxjs 和 angular 的新手(在我以前的作品中一直使用 React)

非常感谢那些会回复的人!!

这是products.component.ts

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AppSettings } from '../../app.settings';
import { Settings } from '../../app.settings.model';
import { Product } from './product.model';
import { ProductsService } from '../../shared/products.services';
import { ProductDialogComponent } from './product-dialog/product-dialog.component';
import { AngularFirestore } from '@angular/fire/firestore';
import { Router } from '@angular/router';
import { CookieService } from 'ngx-cookie-service';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.scss'],
  encapsulation: ViewEncapsulation.None,
  providers: [ ProductsService ]  
})

export class ProductsComponent implements OnInit {
    public products: Product[] = null;
    public searchText: string;
    public page:any;
    public settings: Settings;
    public firestore: any;
    public token = this.cookieService.get('token')
    public id = this.cookieService.get('id')
    constructor(public appSettings:AppSettings, 
                public dialog: MatDialog,
                public productsService:ProductsService,
                public router:Router,
                firestore: AngularFirestore,
                private cookieService: CookieService){
        this.settings = this.appSettings.settings; 
        this.firestore = firestore;
    }

    ngOnInit() {
        if (this.token) {
            this.getProducts()
        }
    }

    public getProducts(): void {
        if(this.id) {
            this.productsService.all(this.id, this.token)
            .subscribe(products => this.products = products)
        }
    }

    public createNewVersion(product:Product) {
        if(this.token) {
            this.productsService.createNewVersion(product.id, this.token)
            .subscribe(result => {
                this.getProducts()
            })
        }
    }
    
    public deleteProduct(product:Product){
        console.log(product.id)
    //    this.productsService.delete(product.id);
    }

    public openProductDialog(product){
        let dialogRef = this.dialog.open(ProductDialogComponent, {
            data: product
        });
        dialogRef.afterClosed().subscribe(result => {
            if(result) {
                console.log('worked')
                this.getProducts()
            }
        })
    }

    public callModulePage(productId) {
        this.router.navigate(['/modules'], {state: {data: productId}})
    }

}

这是显示产品的 sidenav 组件

import { Component, OnInit, Input, Output, ViewEncapsulation, EventEmitter } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { ProductsService } from 'src/app/shared/products.services';
import { AppSettings } from '../../../../app.settings';
import { Settings } from '../../../../app.settings.model';
import { MenuService } from '../menu.service';
import { MatDialog } from '@angular/material/dialog';
import { ProductDialogComponent } from '../../../../pages/products/product-dialog/product-dialog.component';
import { Product } from '../../../../pages/products/product.model'
import { ModulesService } from '../../../../pages/modules/modules.service'
import { CookieService } from 'ngx-cookie-service'

@Component({
  selector: 'app-vertical-menu',
  templateUrl: './vertical-menu.component.html',
  styleUrls: ['./vertical-menu.component.scss'],
  encapsulation: ViewEncapsulation.None,
  providers: [ MenuService, ProductsService, ModulesService]
})
export class VerticalMenuComponent implements OnInit {
  public settings: Settings;
  public products: Product[] = null;
  public productID = null
  public modules = {};
  private token = this.cookieService.get('token')
  private id = this.cookieService.get('id')
  public isModuleOpen = {};
  constructor(public appSettings:AppSettings, public menuService:MenuService, public router:Router, public productsService:ProductsService, private cookieService: CookieService, private modulesService:ModulesService, public dialog: MatDialog) { 
    this.settings = this.appSettings.settings;
  }

  ngOnInit() {
    // this.parentMenu = this.menuItems.filter(item => item.parentId == this.menuParentId);  
    if(this.token) {
     this.refreshProducts()
    }    
  }

  redirectTo(uri, moduleId, moduleName, product) {
    this.router.navigateByUrl('/', {skipLocationChange: true}).then(() =>
    this.router.navigate([uri], {state: {data: {moduleId, moduleName,product}}}));
  }

  showModules(productId) {
    this.modulesService.getModules(productId)
      .subscribe(m => {
        this.modules[productId] = m
        if(this.modules[productId].length > 0) {
          if(this.isModuleOpen[productId]) {
            this.isModuleOpen[productId] = false
          }else {
            this.isModuleOpen[productId] = true
          }
        }
      })
  }
  
  public openProductDialog(product){
    let dialogRef = this.dialog.open(ProductDialogComponent, {
        data: product
    });
    dialogRef.afterClosed().subscribe(result => {
        if(result) {
          this.refreshProducts()
        }
    })
  }
  public refreshProducts(){
    this.productsService.all(this.id, this.token)
    .subscribe(res => this.products = res)
  }
 }

两个组件中都有一个按钮可以打开添加产品对话框组件,这里是具有创建功能的对话框组件

import { Component, OnInit, Inject, EventEmitter, Output } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { FormGroup, FormBuilder, Validators, NgForm} from '@angular/forms';
import { Product } from '../product.model';
import { ProductsService } from '../../../shared/productss.services'
import { CookieService } from 'ngx-cookie-service';

@Component({
  selector: 'app-product-dialog',
  templateUrl: './product-dialog.component.html',
  styleUrls: ['./product-dialog.component.scss'],
})
export class ProductDialogComponent implements OnInit {
  public form:FormGroup;
  public passwordHide:boolean = true;
  public token= this.cookieService.get('token')
  constructor(public dialogRef: MatDialogRef<ProductDialogComponent>,
              @Inject(MAT_DIALOG_DATA) public product: Product,
              public fb: FormBuilder,
              private productsService: ProductsService,
              private cookieService: CookieService) {
    this.form = this.fb.group({
      id: null,
      name: [null],
      shortname:[null],
      description: [null],
      overview: [null],
    });
  }

  ngOnInit() {
    if(this.product){
      this.form.setValue(
        {
          id: this.product.id,
          name: this.product.name,
          shortname: this.product.shortname,
          description: this.product.description,
          overview: this.product.overview
        }
      );
    } 
    else{
      this.product = new Product();
    } 
  }

  addProduct(product) {
    if(this.product.id) {
      if(this.token) {
        this.productsService.update(product, this.token)
        .subscribe(result => {
          console.log(result)
        })
      }
    } else {
      if(this.token) {
      this.productsService.create(product, this.token)
      .subscribe(result => console.log(result));
      }
    }

  }

  close(): void {
    this.dialogRef.close();
  }

}

试试这个:

import { HttpHeaders } from '@angular/common/http';

    
create(product, token) {
    const httpOptions = {
      headers: new HttpHeaders({
        'Content-Type':  'application/json',
        Authorization: `Bearer ${token}`
      })
    }
    return this.http.post(this.getUrl() + "/create", product, httpOptions).pipe(
        map(response => {
           return response;
        })
    );
}

让我们看看你想要实现的逻辑。 2 个组件总是看到一个新的产品列表,即使从这些组件中的 1 个组件调用更改也是如此。您还希望在单个服务中抑制该功能。

您的实现的问题是每个组件都订阅了不同的 Observable,每次调用 all() 方法时都会创建该 Observable。该方法带来了一个 Observable,该 Observable 携带该时间线上产品的最新信息,然后结束。另一个组件之前订阅了另一个过去时间线的另一个可观察对象,该时间线返回并结束。

然而,您可以让两个组件不断地听取您的 Subscription。然后,当 1 个组件请求新的产品列表时,您使用 http 检索它,而不是返回一个新的 Observable(另一个组件不知道),您发出一个新值两个组件都监听相同的 Subscription

转化为以下内容

export class ProductsService {
  private model = '';

  //keep that Subscription as a reference here
  getAllProdObserv :  BehaviorSubject<Product[]> = new BehaviorSubject<Product[]>([]); 

  constructor(private http: HttpClient) { }

  //When this method get's called it will create an observable and when that observable is ready to be opened it will emit the value by your public subject
  all(sellerId, token): Observable<Product[]> {
  this.model = 'products'

    this.http.get(this.getUrlById(sellerId), {headers: {'Authorization' : `Bearer ${token}`}})
    .pipe(map((products:Product[]) => products)).subscribe( (products) => this.getAllProdObserv.next(products));

  }

这样你的其他 2 个组件将始终监听你的 Subject 并查看它发出的值。

然后在 SideNav

export class VerticalMenuComponent implements OnInit {
  public settings: Settings;
  public products: Product[] = null;
  public productID = null
  public modules = {};
  private token = this.cookieService.get('token')
  private id = this.cookieService.get('id')
  public isModuleOpen = {};
  constructor(public appSettings:AppSettings, public menuService:MenuService, public router:Router, public productsService:ProductsService, private cookieService: CookieService, private modulesService:ModulesService, public dialog: MatDialog) { 
    this.settings = this.appSettings.settings;
  }

  ngOnInit() {
    //You subscribe on the same Subject that will always exist but each time will emit the value of a fresh list
    this.productsService.getAllProdObserv.subscribe(res => this.products = res);
  }

然后在products.component.ts

export class ProductsComponent implements OnInit {
    public products: Product[] = null;
    public searchText: string;
    public page:any;
    public settings: Settings;
    public firestore: any;
    public token = this.cookieService.get('token')
    public id = this.cookieService.get('id')
    constructor(public appSettings:AppSettings, 
                public dialog: MatDialog,
                public productsService:ProductsService,
                public router:Router,
                firestore: AngularFirestore,
                private cookieService: CookieService){
        this.settings = this.appSettings.settings; 
        this.firestore = firestore;
    }

    ngOnInit() {
        //Again you subscribe on the same Subject that will always exist but each time will emit the value of a fresh list
        this.productsService.getAllProdObserv.subscribe(products => this.products = products)
    }

所以现在 products.component.tssidenav.component.ts 将始终侦听单个可观察对象(实际上是 Subject),并且每个发出的事件都将呈现给两个组件。

每次调用方法 all 时,您的服务都会发出新的产品列表。

然后在 products.component.ts getProducts() 只会让您的服务发出新鲜产品的新价值,这两个组件将不断收听。

export class ProductsComponent implements OnInit {

    ngOnInit() {
       this.productsService.getAllProdObserv.subscribe(products => this.products = products)
    }

    public getProducts(): void {
        if(this.id) {
            this.productsService.all(this.id, this.token);
        }
    }

    public createNewVersion(product:Product) {
        if(this.token) {
            this.productsService.createNewVersion(product.id, this.token)
            .subscribe(result => {
                this.getProducts()
            })
        }
    }

    public openProductDialog(product){
        let dialogRef = this.dialog.open(ProductDialogComponent, {
            data: product
        });
        dialogRef.afterClosed().subscribe(result => {
            if(result) {
                console.log('worked')
                this.getProducts()
            }
        })
    }
}

在你的 sideNav 组件中也一样

export class VerticalMenuComponent implements OnInit {
  public settings: Settings;
  public products: Product[] = null;
  public productID = null
  public modules = {};
  private token = this.cookieService.get('token')
  private id = this.cookieService.get('id')
  public isModuleOpen = {};
  constructor(public appSettings:AppSettings, public menuService:MenuService, public router:Router, public productsService:ProductsService, private cookieService: CookieService, private modulesService:ModulesService, public dialog: MatDialog) { 
    this.settings = this.appSettings.settings;
  }

  ngOnInit() {
    // this.parentMenu = this.menuItems.filter(item => item.parentId == this.menuParentId);  
    if(this.token) {
     this.productsService.getAllProdObserv.subscribe(res => this.products = res);
    }    
  }

  redirectTo(uri, moduleId, moduleName, product) {
    this.router.navigateByUrl('/', {skipLocationChange: true}).then(() =>
    this.router.navigate([uri], {state: {data: {moduleId, moduleName,product}}}));
  }

  showModules(productId) {
    this.modulesService.getModules(productId)
      .subscribe(m => {
        this.modules[productId] = m
        if(this.modules[productId].length > 0) {
          if(this.isModuleOpen[productId]) {
            this.isModuleOpen[productId] = false
          }else {
            this.isModuleOpen[productId] = true
          }
        }
      })
  }
  
  public openProductDialog(product){
    let dialogRef = this.dialog.open(ProductDialogComponent, {
        data: product
    });
    dialogRef.afterClosed().subscribe(result => {
        if(result) {
          this.refreshProducts()
        }
    })
  }
  public refreshProducts(){
    this.productsService.all(this.id, this.token);
  }
 }

编辑:只是为面对此线程的 OP 添加一些信息,并查看下面的长篇讨论。这里的答案足以解决问题。我们在讨论中发现的问题是,该服务并未在整个应用程序中注册为单例,而是针对提供商标签中的不同模块和不同组件再次注册。修复后,行为符合预期。