问题描述
我有一个使用数据库的服务,我可以在其中获取/添加/删除产品。 我在 2 个组件中使用这个 products.service.ts,因为我需要在我的应用程序的 2 个地方获取产品,
因此,我想将我的服务切换到 observable/subject,以便我可以订阅它,它会在 2 个地方同步。
问题是我现在正在努力转换 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),{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 函数添加为 observable,但我尝试过的其他任何功能都不起作用,因此我将其保留为以前的状态。我是 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,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'],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,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,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)
}
}
两个组件中都有一个按钮打开一个add-product-dialog组件,这里是带有create函数的对话框组件
import { Component,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();
}
}
解决方法
让我们看看你想要实现的逻辑。 2 个组件总是看到新的产品列表,即使从这些组件中的 1 个调用更改。您还想在单个服务中抑制该功能。
您实现的问题在于,每个组件都订阅了每次调用 all()
方法时都会创建的不同 Observable。该方法带来了一个 observable,该 observable 在该时间线上携带产品的最新信息,然后结束。另一个组件之前订阅了另一个返回并结束的过去时间线的另一个 observable。
然而,您可以让这两个组件不断收听您的 Subscription
。然后,当 1 个组件要求提供新的产品列表时,您使用 http 检索它,而不是返回一个新的 Observable(另一个组件不知道),而是发出一个具有相同 {{ 1}} 两个组件都会监听。
翻译成以下内容
Subscription
这样您的其他 2 个组件将始终监听您的 Subject 并查看它发出的值。
然后在SideNav
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));
}
然后在 products.component.ts
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);
}
所以现在 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,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.ts
将始终侦听单个 observable(实际上是 Subject)并且每个发出的事件都将呈现给两个组件。
每次调用方法 sidenav.component.ts
时,您的服务都会发出一个新的产品列表。
然后在 all
上,products.component.ts
只会让您的服务发出新产品的新价值,这两个组件都将持续监听。
getProducts()
在您的 sideNav 组件中也是如此
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()
}
})
}
}
编辑:只是为面对此线程的 OP 添加一些信息,并查看下面的长时间讨论。这里的答案足以解决问题。我们在讨论中遇到的问题发现,该服务并未在整个应用程序中注册为单例,而是为提供者标签中的不同模块和不同组件再次注册。修复后行为符合预期。
,试试这个:
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;
})
);
}