product-detail.component.css
<div class="thumbnail">
<button class="btn btn-default btn-lg"
[class.active] = "isWatched"
(click)="watchProduct" >
{{isWatched?'取消关注':'关注'}}
</button>
<label>最新出价:{{currentBid}}元</label>
</div>
product-detail.component.ts
isWatched:boolean = false;
currentBid:number;
省略代码
ngOnInit() {
let productId: string = this.routeInfo.snapshot.params["productId"];
//使用手工订阅的方式
this.productService.getProduct(productId).subscribe(
product => {
this.product = product;
this.currentBid = product.price;
}
);
this.productService.getCommentsForProductId(productId).subscribe(
coments => this.comments = coments
);
}
watchProduct(){
console.log('点击了');
this.isWatched = !this.isWatched;
}
我们在之前的项目中创建过一个WebSocket服务,现在使用它来及时改变商品价格
product-detail.component.ts
constructor(private routeInfo: ActivatedRoute,
private productService: ProductService,
private wsService:WebSocketService
) {
}
省略代码
watchProduct(){
this.isWatched = !this.isWatched;
this.wsService.createObservableSocket("ws://localhost:8085",this.product.id)
.subscribe();
}
改造this.wsService.createObservableSocket方法
web-socket.service.ts
import {Injectable} from '@angular/core';
import {Observable} from "rxjs";
import 'rxjs/Rx';
@Injectable({
providedIn: 'root'
})
export class WebSocketService {
ws: WebSocket;
constructor() {
}
createObservableSocket(url: string,id:number): Observable<any> {
this.ws = new WebSocket(url);
return new Observable<string>(
observer =>{
//什么时候发生下一个元素
this.ws.onmessage = (event) => observer.next(event.data);
//什么时候抛一个异常
this.ws.onerror = (event) => observer.error(event);
//什么时候发出流结束的信号
this.ws.onclose = (event) => observer.complete();
this.ws.onopen =(event)=> this.sendMessage({productId:id})
}
).map(message => JSON.parse(message));
}
sendMessage(message:any){
this.ws.send(JSON.stringify(message));
}
}
修改服务器端代码,看如何把productId放到一个集合中
"use strict";
var express = require("express");
var ws_1 = require('ws');
var app = express();
app.get('/', function (req, res) {
res.send("Hello Express");
});
app.get('/api/products', function (req, res) {
var result = products;
var params = req.query;
if (params.title) {
result = result.filter(function (p) { return p.title.indexOf(params.title) !== -1; });
}
if (params.price && params.price !== 'null' && result.length > 0) {
result = result.filter(function (p) { return p.price <= parseInt(params.price); });
}
if (params.category && params.category !== "-1" && result.length > 0) {
result = result.filter(function (p) { return p.categories.indexOf(params.category) !== -1; });
}
res.json(result);
});
app.get('/api/product/:id', function (req, res) {
res.json(products.find(function (product) { return product.id == req.params.id; }));
});
app.get('/api/product/:id/comments', function (req, res) {
res.json(comments.filter(function (comment) { return comment.productId == req.params.id; }));
});
var server = app.listen(8000, "localhost", function () {
console.log("服务器已启动,地址是:http://localhost:8000");
});
//关注的商品id集合
var subscriptions = new Map();
var wsServer = new ws_1.Server({ port: 8085 });
wsServer.on("connection", function (websocket) {
websocket.send("这个消息是服务器主动推送的");
websocket.on("message", function (message) {
var messageObj = JSON.parse(message);
var productIds = subscription.get(websocket) || [];
subscriptions.set(websocket, productIds.concat([messageObj.productId]));
});
});
//价格集合
var currentBids = new Map();
//每两秒更新价格
setInterval(function () {
products.forEach(function (p) {
var currentBid = currentBids.get(p.id) || p.price;
var newBid = currentBid + Math.random() * 5;
currentBids.set(p.id, newBid);
});
subscriptions.forEach(function (productIds, ws) {
var newBids = productIds.map(function (pid) { return ({
productId: pid,
bid: currentBids.get(pid)
}); });
ws.send(JSON.stringify(newBids));
});
}, 2000);
var Product = (function () {
function Product(id, title, price, rating, desc, categories) {
this.id = id;
this.title = title;
this.price = price;
this.rating = rating;
this.desc = desc;
this.categories = categories;
}
return Product;
}());
exports.Product = Product;
var Comment = (function () {
function Comment(id, productId, timestamp, user, rating, content) {
this.id = id;
this.productId = productId;
this.timestamp = timestamp;
this.user = user;
this.rating = rating;
this.content = content;
}
return Comment;
}());
exports.Comment = Comment;
var products = [
new Product(1, '第一个商品', 1.99, 3.5, "这是第一商品,asdxc奥术大师多撒", ["电子产品", "硬件设备", "其他"]),
new Product(2, '第二个商品', 2.99, 2.5, "这是第二商品,奥术大师多驱蚊器二无", ["硬件设备", "其他"]),
new Product(3, '第三个商品', 3.99, 1.5, "这是第三商品,请问驱蚊器翁群翁", ["电子产品", "硬件设备"]),
new Product(4, '第四个商品', 4.99, 2.0, "这是第四商品,切勿驱蚊器翁", ["电子产品", "其他"]),
new Product(5, '第五个商品', 5.99, 3.5, "这是第五商品,213123123", ["电子产品", "硬件设备", "其他"]),
new Product(6, '第六个商品', 6.99, 4.5, "这是第六商品,啊多少大所多多", ["电子产品", "硬件设备", "其他"])
];
var comments = [
new Comment(1, 1, "2017-02-02 22:22:22", "张三", 3, "东西不错"),
new Comment(2, 2, "2017-03-02 23:22:22", "李四", 4, "东西挺不错"),
new Comment(3, 3, "2017-04-02 24:22:22", "王五", 2, "东西不错"),
new Comment(4, 4, "2017-05-02 25:22:22", "赵六", 1, "东西还不错"),
new Comment(5, 5, "2017-06-02 26:22:22", "哈哈", 3, "东西不错"),
];
web-socket.service.ts
import {Injectable} from '@angular/core';
import {Observable} from "rxjs";
import 'rxjs/Rx';
@Injectable({
providedIn: 'root'
})
export class WebSocketService {
ws: WebSocket;
constructor() {
}
createObservableSocket(url: string,id:number): Observable<any> {
this.ws = new WebSocket(url);
return new Observable<string>(
observer =>{
this.ws.onmessage = (event) => observer.next(event.data);
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
this.ws.onopen =(event)=> this.sendMessage({productId:id})
}
);
}
sendMessage(message:any){
this.ws.send(JSON.stringify(message));
}
}
product-detail.component.ts
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from "@angular/router";
import {Product, ProductService, Comment} from "../shared/product.service";
import {WebSocketService} from "../shared/web-socket.service";
import {any} from "codelyzer/util/function";
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {
product: Product;
comments: Comment[];
newRating: number = 5;
newComment: string = "";
isCommentHidden = true;
isWatched: boolean = false;
currentBid: number;
constructor(private routeInfo: ActivatedRoute,
private productService: ProductService,
private wsService: WebSocketService) {
}
ngOnInit() {
let productId: string = this.routeInfo.snapshot.params["productId"];
this.productService.getProduct(productId).subscribe(
product => {
this.product = product;
this.currentBid = product.price;
}
);
this.productService.getCommentsForProductId(productId).subscribe(
coments => this.comments = coments
);
}
addComment() {
let comment = new Comment(0, this.product.id, new Date().toISOString(), "someone", this.newRating, this.newComment);
this.comments.unshift(comment);
let sum = this.comments.reduce((sum, comment) => sum + comment.rating, 0);
this.product.rating = sum / this.comments.length;
this.newComment = null;
this.newRating = 5;
this.isCommentHidden = true;
}
watchProduct() {
this.isWatched = !this.isWatched;
var _this = this;
let products:any[];
this.wsService.createObservableSocket("ws://localhost:8085", this.product.id)
.subscribe(
function(products1) {
console.log(JSON.parse(products1));
products =JSON.parse(products1);
_this.currentBid = products[0].bid
}
);
}
}
服务端代码
auction_server.js
"use strict";
var express = require("express");
var ws_1 = require('ws');
var app = express();
app.get('/', function (req, res) {
res.send("Hello Express");
});
app.get('/api/products', function (req, res) {
var result = products;
var params = req.query;
console.log(params);
if (params.title) {
result = result.filter(function (p) { return p.title.indexOf(params.title) !== -1; });
}
if (params.price && params.price !== 'null' && result.length > 0) {
result = result.filter(function (p) { return p.price <= parseInt(params.price); });
}
if (params.category && params.category !== "-1" && result.length > 0) {
result = result.filter(function (p) { return p.categories.indexOf(params.category) !== -1; });
}
res.json(result);
});
app.get('/api/product/:id', function (req, res) {
res.json(products.find(function (product) { return product.id == req.params.id; }));
});
app.get('/api/product/:id/comments', function (req, res) {
res.json(comments.filter(function (comment) { return comment.productId == req.params.id; }));
});
var server = app.listen(8000, "localhost", function () {
console.log("服务器已启动,地址是:http://localhost:8000");
});
//关注的商品id集合
var subscriptions = new Map();
var wsServer = new ws_1.Server({ port: 8085 });
wsServer.on("connection", function (websocket) {
// websocket.send("这个消息是服务器主动推送的");
websocket.on("message", function (message) {
var messageObj = JSON.parse(message);
var productIds = subscriptions.get(websocket) || [];
subscriptions.set(websocket, productIds.concat([messageObj.productId]));
});
});
//价格集合
var currentBids = new Map();
//每两秒更新价格
setInterval(function () {
products.forEach(function (p) {
var currentBid = currentBids.get(p.id) || p.price;
var newBid = currentBid + Math.random() * 5;
currentBids.set(p.id, newBid);
});
subscriptions.forEach(function (productIds, ws) {
if (ws.readyState === 1) {
var newBids = productIds.map(function (pid) { return ({
productId: pid,
bid: currentBids.get(pid)
}); });
ws.send(JSON.stringify(newBids));
}
else {
subscriptions.delete(ws);
}
});
}, 2000);
//定时给所有客户端推送消息
// setInterval(() => {
// if (wsServer.clients) {
// wsServer.clients.forEach(client => {
// client.send("这是定时推送");
// })
// }
// }, 2000);
var Product = (function () {
function Product(id, title, price, rating, desc, categories) {
this.id = id;
this.title = title;
this.price = price;
this.rating = rating;
this.desc = desc;
this.categories = categories;
}
return Product;
}());
exports.Product = Product;
var Comment = (function () {
function Comment(id, productId, timestamp, user, rating, content) {
this.id = id;
this.productId = productId;
this.timestamp = timestamp;
this.user = user;
this.rating = rating;
this.content = content;
}
return Comment;
}());
exports.Comment = Comment;
var products = [
new Product(1, '第一个商品', 1.99, 3.5, "这是第一商品,asdxc奥术大师多撒", ["电子产品", "硬件设备", "其他"]),
new Product(2, '第二个商品', 2.99, 2.5, "这是第二商品,奥术大师多驱蚊器二无", ["硬件设备", "其他"]),
new Product(3, '第三个商品', 3.99, 1.5, "这是第三商品,请问驱蚊器翁群翁", ["电子产品", "硬件设备"]),
new Product(4, '第四个商品', 4.99, 2.0, "这是第四商品,切勿驱蚊器翁", ["电子产品", "其他"]),
new Product(5, '第五个商品', 5.99, 3.5, "这是第五商品,213123123", ["电子产品", "硬件设备", "其他"]),
new Product(6, '第六个商品', 6.99, 4.5, "这是第六商品,啊多少大所多多", ["电子产品", "硬件设备", "其他"])
];
var comments = [
new Comment(1, 1, "2017-02-02 22:22:22", "张三", 3, "东西不错"),
new Comment(2, 2, "2017-03-02 23:22:22", "李四", 4, "东西挺不错"),
new Comment(3, 3, "2017-04-02 24:22:22", "王五", 2, "东西不错"),
new Comment(4, 4, "2017-05-02 25:22:22", "赵六", 1, "东西还不错"),
new Comment(5, 5, "2017-06-02 26:22:22", "哈哈", 3, "东西不错"),
];
点击取消关注,让价格不再变化
web-socket.service.ts
createObservableSocket(url: string,id:number): Observable<any> {
this.ws = new WebSocket(url);
return new Observable<string>(
observer =>{
this.ws.onmessage = (event) => observer.next(event.data);
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
this.ws.onopen =(event)=> this.sendMessage({productId:id});
return () => this.ws.close();
}
);
}
product-detail.component.ts
subscription: Subscription;
省略代码
watchProduct() {
var _this = this;
if (_this.subscription) {
_this.subscription.unsubscribe();
_this.isWatched = false;
_this.subscription = null;
} else {
_this.isWatched = true;
let products: any[];
_this.subscription = this.wsService.createObservableSocket("ws://localhost:8085", this.product.id)
.subscribe(
function (products1) {
console.log(JSON.parse(products1));
products = JSON.parse(products1);
_this.currentBid = products[0].bid
}
);
}
}