Angular 在两个组件中使用的共享服务
我有一个与db合作的服务,在那里我可以获取/添加/删除产品。 我在两个组件中使用这个product.service.ts,因为我需要在我的应用程序中的两个位置获得产品 因此,我想将我的服务切换到observable/subject,这样我就可以订阅它,它将在两个位置同步 问题是,我已经挣扎了两天来做出这样的转变。。如果有人能帮我,我将不胜感激 这就像我在每个组件中使用不同的产品“状态”,因为当我在一个组件上添加产品时,它只更新这个组件,而第二个组件仍然是“旧”组件 这是我目前的产品和服务Angular 在两个组件中使用的共享服务,angular,rxjs,observable,angular-services,Angular,Rxjs,Observable,Angular Services,我有一个与db合作的服务,在那里我可以获取/添加/删除产品。 我在两个组件中使用这个product.service.ts,因为我需要在我的应用程序中的两个位置获得产品 因此,我想将我的服务切换到observable/subject,这样我就可以订阅它,它将在两个位置同步 问题是,我已经挣扎了两天来做出这样的转变。。如果有人能帮我,我将不胜感激 这就像我在每个组件中使用不同的产品“状态”,因为当我在一个组件上添加产品时,它只更新这个组件,而第二个组件仍然是“旧”组件 这是我目前的产品和服务 imp
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}`;
}
}
这是显示产品的sidenav组件
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}})
}
}
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)
}
}
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)
}
两个组件中都有一个按钮,用于打开“添加产品”对话框组件,这里是具有创建功能的对话框组件
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();
}
}
从'@angular/core'导入{Component,OnInit,Inject,EventEmitter,Output};
从“@angular/material/DIALOG”导入{MatDialogRef,MAT_DIALOG_DATA};
从'@angular/forms'导入{FormGroup,FormBuilder,Validators,NgForm};
从“../Product.model”导入{Product};
从“../../../shared/productss.services”导入{ProductsService}
从“ngx cookie服务”导入{CookieService};
@组成部分({
选择器:“应用程序产品对话框”,
templateUrl:'./product dialog.component.html',
样式URL:['./product dialog.component.scss'],
})
导出类ProductDialogComponent实现OnInit{
公共形式:FormGroup;
公共密码隐藏:boolean=true;
public token=this.cookieService.get('token')
构造函数(公共dialogRef:MatDialogRef,
@注入(材料对话框数据)公共产品:产品,
公共fb:FormBuilder,
私人产品服务:产品服务,
私人厨师服务:厨师服务){
this.form=this.fb.group({
id:null,
名称:[null],
shortname:[null],
说明:[null],
概述:[null],
});
}
恩戈尼尼特(){
如果(本产品){
this.form.setValue(
{
id:this.product.id,
名称:this.product.name,
shortname:this.product.shortname,
description:this.product.description,
概述:this.product.overview
}
);
}
否则{
this.product=新产品();
}
}
添加产品(产品){
if(this.product.id){
if(this.token){
this.productsService.update(产品,this.token)
.订阅(结果=>{
console.log(结果)
})
}
}否则{
if(this.token){
this.productsService.create(产品,this.token)
.subscribe(结果=>console.log(结果));
}
}
}
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个组件调用了更改。您还希望在单个服务中抑制该功能 实现的问题在于,每个组件都订阅了一个不同的可观察对象,该对象在每次调用
all()
方法时创建。该方法带来了一个可观察的结果,它携带了该时间线上产品的新信息,然后结束。另一个组件先前订阅了返回并结束的另一个过去时间线的另一个可观测值
但是,您可以让这两个组件不断收听您的订阅
。然后,当一个组件请求一个新的产品列表时,您使用http检索它,但不是返回一个新的可观察值(另一个组件将不知道),而是使用两个组件侦听的相同的订阅
发出一个新值
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()
}
})
}
}
这可以转化为以下内容
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上
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}})
}
}
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)
}
}
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.ts
和sidenav.component.ts
将始终侦听单个可观察对象(实际上是主体),每个发出的事件都将呈现给这两个组件
每次调用方法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()
}
})
}
}
在侧导航组件中也同样如此
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);
}
}
编辑:只需添加一些面向此线程的操作信息,并查看下面的详细讨论。这个答案足以解决这个问题。我们在讨论中遇到的问题是,该服务并没有在整个应用程序中注册为单例,而是在providers标记中为不同的模块和不同的组件再次注册。修复后,行为符合预期。共享html文件以了解所有3个相关组件如何相互关联html文件?制作stackblitz以便我可以共享代码是的html文件有一个sidenav组件,我们可以在其中看到所有产品和产品页面中使用的产品组件,但是这两个html文件并不相关,因为我在这两个文件中都声明了一个products变量,并且都使用了一个。这可能就是它们不同步的原因。您建议只使用此函数替换我的create函数,两个组件都将同步?您还可以为所有请求创建一个通用方法。你能创建一个stackblitz,这样我们就可以很容易地解决你的问题吗?是的,但是这个应用程序使用了一个模板主题,并且使用了一个本地python服务器,所以很难创建stackblitz,我现在正在尝试,我无法在stackblitz上实现。。我想我必须自己搜索,但我真正的难题是如何在两个不同的组件中使用相同的产品变量?因为现在如果你看到pos