From 049b7f0cfc8a9aec1f31670d863799d0b7a7318d Mon Sep 17 00:00:00 2001 From: Rupesh Tiwari Date: Fri, 12 Feb 2021 11:36:34 -0500 Subject: [PATCH] Rt message handler decorator (#2) * adding handler * added decorators * refactory * refactory pubsub * update readme * for feature added * refact: fixing * updated code * updated angular * updated readme * updated readme * updated readme * fixing feature module * refact: fixing code * updated example app * added examples * fix: test * doc: fixing readme * doc: updated readme --- README.md | 251 ++++++++++++++++-- package.json | 2 +- .../src/lib/contracts/definitions.ts | 2 +- src/app/app.component.ts | 25 +- 4 files changed, 257 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 88da543..7737ee8 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,76 @@ - # Angular PubSub Angular 11.x implementation of the [publish subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) Pattern. ![GitHub package.json dependency version (subfolder of monorepo)](https://img.shields.io/github/package-json/dependency-version/fullstackmaster1/fsms-angular-pubsub/@angular/core) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/fullstackmaster1/fsms-angular-pubsub/CI%20and%20CD/main?style=flat) ![npm](https://img.shields.io/npm/dw/@fsms/angular-pubsub?style=flat) [![npm version](https://badge.fury.io/js/%40fsms%2Fangular-pubsub.svg)](https://badge.fury.io/js/%40fsms%2Fangular-pubsub) ![GitHub repo size](https://img.shields.io/github/repo-size/fullstackmaster1/FSMS-ANGULAR-PUBSUB) ![GitHub pull requests](https://img.shields.io/github/issues-pr/fullstackmaster1/fsms-angular-pubsub) ![GitHub last commit](https://img.shields.io/github/last-commit/fullstackmaster1/fsms-angular-pubsub) [![dependencies Status](https://status.david-dm.org/gh/FullStackMaster1/fsms-angular-pubsub.svg)](https://david-dm.org/FullStackMaster1/fsms-angular-pubsub) ![GitHub User's stars](https://img.shields.io/github/stars/fullstackmaster1?style=social) ![GitHub Sponsors](https://img.shields.io/github/sponsors/fullstackmaster1?style=social) -## Installing +> By [Rupesh Tiwari](https://rupeshtiwari.com) + +**If you enjoy @fsms/angular-pubsub, please consider [supporting me](https://github.com/sponsors/rupeshtiwari) for years of development (and to unlock rewards!) ❤** + +## Table of Contents + +- [Angular PubSub](#angular-pubsub) + - [Table of Contents](#table-of-contents) + - [Installing @fsms/angular-pubsub](#installing-fsmsangular-pubsub) + - [Definitions](#definitions) + - [Message](#message) + - [Pub sub Service (Angular Service)](#pub-sub-service-angular-service) + - [Using PubSub Service](#using-pubsub-service) + - [Using Angular Service As Message Handler](#using-angular-service-as-message-handler) + - [Contributions](#contributions) + + +## Installing @fsms/angular-pubsub **npm installation** ```shell npm i -S @fsms/angular-pubsub ``` -## Using PubSub For Inline Style + +## Definitions + +You need `Message` class to create your messages and you need `PubsubService` to publish or subscribe messages. +### Message + +`Message` holds `messageType` and optional payload + +```ts +export interface IMessage { + messageType: string; + payload?: any; +} +``` +Example of one message: + +```ts +import { DefineMessage, IMessageSchema, IMessage } from '@fsms/angular-pubsub'; + +@DefineMessage() +export class PlaceOrder implements IMessage { + static messageType = '[Sells] Place Order'; + messageType = PlaceOrder.messageType; + constructor(public payload?: string) {} +} +``` + +### Pub sub Service (Angular Service) + +`pubsubService` is used to publish and subscribe messages. + +```ts +publish(message: V): void; +subscribe({ + messageType, + callback, + error, + complete, + }: SubscribeOptions): PubsubSubscription; +``` + +## Using PubSub Service 1. **Importing PubsubModule in application module**. @@ -21,7 +78,7 @@ Initialize module for root in your angular root module ```ts -import { PubSubModule } from '@fsms/angular-pubsub'; // <= HERE +import { PubSubModule } from '@fsms/angular-pubsub'; 👈 // Importing Angular Pubsub module @NgModule({ declarations: [ @@ -33,7 +90,7 @@ imports: [ BrowserModule, FormsModule, HttpModule, - PubSubModule.forRoot() // <= AND HERE + PubSubModule.forRoot() 👈 // Initiate Pubsub module ], providers: [], bootstrap: [RootComponent] @@ -47,7 +104,7 @@ Go to desired component and subscribe to a message. ```ts import { Component } from '@angular/core'; -import { PubsubService } from '@fsms/angular-pubsub';// <= HERE +import { PubsubService } from '@fsms/angular-pubsub';👈 // Importing Angular Pubsub module @Component({ selector: 'app-root', @@ -57,6 +114,7 @@ import { PubsubService } from '@fsms/angular-pubsub';// <= HERE export class AppComponent { constructor( private pubsubService: PubsubService/* <= HERE */) {} + 👆// Injecting Angular Pubsub Service } ``` 3. **Subscribing to message** @@ -64,15 +122,32 @@ export class AppComponent { In `ngOnInit` method of angular, you can subscribe to the events that you want to react upon. ```ts -ngOnInit(): void { - this.pubsubService.subscribe({ // <= HERE - messageType: PlaceOrderType, +import { PubsubService, PubsubSubscription } from '@fsms/angular-pubsub'; +import { PlaceOrder } from './orders/messages/place-order-message'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'], +}) +export class AppComponent implements OnInit { + subscriptions: PubsubSubscription[] = []; + + constructor(private pubsubService: PubsubService) {} + + ngOnInit(): void { + + this.subscriptions.push( + this.pubsubService.subscribe({ 👈// Subscribing to a message + messageType: PlaceOrder.messageType, callback: (msg) => console.log('received', msg), - }); + }) + ); + } } ``` -4. **Publishing Message** -The `publish` method takes one argument where it expect the `message` object. +4. **Publishing a Message** +The `publish` method takes one argument where it expect the `Message` object. Example: Now on a button click, I want to publish a message with some payload. @@ -93,20 +168,164 @@ export class AppComponent { orderPlaced($event: KeyboardEvent) { $event.preventDefault(); - this.pubsubService.publish(new OrderPlaced('20 Apples'));// <= HERE + this.pubsubService.publish( 👈// Publishing a message + new OrderCreated({ + orderId: new Date().getTime().toString(36), + item: '20 Apples', + }) + ); } } ``` 5. **Unsubscribing Messages** +Keep all subscriptions per component in an array. And On component you must unsubscribe your subscriptions on `ngOnDestroy` event. + +```ts + ngOnDestroy(): void { + this.subscriptions.forEach((s) => s.unsubscribe());👈// Unsubscribing a message + } +``` + +## Using Angular Service As Message Handler + +Convert Angular service to a Message Handler. If to organize your angular code base as Service Oriented Architecture (SOA) way. And you want to create an Angular service that can listen to a Message and react on them just like a N-ServiceBus Message Handlers? + +Then you must use `@RegisterHandler({})` decorator on any Angular Service then it will automatically be registered as message subscriber. This helps us to organize your business logic in services rather in angular components. + +**Message Handler** + +Message handler is a service that can listen to one message or more messages and perform business logic. Message Handler can also publish after handling incoming messages. + +Diagram of a Angular Service as Message Handler called as `ShipOrderService` which listens to `OrderReady` message and process shipping then publishes `OrderShipped` message. + +![](https://i.imgur.com/r60vyT4.png) + + +**Creating Message Handler at Root Module** + +1. First create your message handler at Root (App) Module. + +Example: When Order is Ready Shipping service is starting the shipment process. + +```ts +import { Injectable } from '@angular/core'; +import { + CallbackOptions, + IHandleMessage, + RegisterHandler, +} from '@fsms/angular-pubsub'; +import { OrderReady } from '../messages/order-ready-message'; +import { OrderShipped } from '../messages/order-shipped-message'; + +@Injectable({ providedIn: 'root' }) // Angular Service +@RegisterHandler({ 👈 + messages: [OrderReady],👈 // You can listen to many messages +}) +export class ShipOrderService implements IHandleMessage { + handle({ message, context }: CallbackOptions): void { + console.log('[Shipping] Order Shipped', message); + + context.publish(new OrderShipped(message.payload)); + 👆 // context will have publish method to publish any message from message handler. + } +} +``` + +2. Register your message handler in Root (App) Module. + +Use `PubsubModule.forRoot([])` to register your app message handlers. + +Example: Registering `ShipOrderService` +```ts +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { PubsubModule } from '@fsms/angular-pubsub'; +import { AppComponent } from './app.component'; +import { ShipOrderService } from './services/ship-order.service'; + +@NgModule({ + declarations: [AppComponent], + imports: [ + BrowserModule, + FormsModule, + PubsubModule.forRoot([ // Register App Module level Message Handlers + ShipOrderService, 👈 + ]), + ], + providers: [], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + +**Creating Message Handler at Feature Module Level** + +In order to achieve true service oriented architecture. You must create independent isolated feature modules. The message handlers gives you the power to register your message handlers at feature module level. + +1. First create your message handler at Feature Module. + +Example: `Create Order` Message handler in `Orders` module. + +```ts +import { CallbackOptions, IHandleMessage } from '@fsms/angular-pubsub'; +import { RegisterHandler } from 'projects/fsms-angular-pubsub/src/lib/pubsub-decorator'; +import { OrderCreated } from 'src/app/messages/order-created-message'; +import { PlaceOrder } from '../messages/place-order-message'; + +@RegisterHandler({👈 // Create as Message Handler + messages: [PlaceOrder], +}) +export class CreateOrderService implements IHandleMessage { + constructor() {} + + handle({ message, context }: CallbackOptions): void { + console.log(`[Sales] Order Created`, message); + + context.publish( + new OrderCreated({ + orderId: new Date().getTime().toString(36), + item: message.payload, + }) + ); + } +} + +``` + +2. Register your message handler in Feature Module. + +Use `PubsubModule.forFeature([])` to register your feature message handlers. + +Example: Registering `CreateOrderService` at orders module. + +```ts +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SubmitOrderComponent } from './submit-order/submit-order.component'; +import { CreateOrderService } from './services/create-order.service'; +import { PubsubModule } from '@fsms/angular-pubsub'; + +@NgModule({ + declarations: [SubmitOrderComponent], + imports: [CommonModule, + PubsubModule.forFeature([CreateOrderService]) 👈 // Registering as feature message handler + ], + exports: [SubmitOrderComponent], +}) +export class OrdersModule {} +``` + +## Contributions ---- +Contributions are welcome!🙂 If you find any problems or would like to contribute in any way, feel free to create a pull request/open an issue/send me a message. -**Thank You!** +You can also contribute by becoming an [official sponsor](https://github.com/sponsors/rupeshtiwari) to help keep Angular Pub-Sub well-maintained. 💖 Say 👋 to me! Rupesh Tiwari www.rupeshtiwari.com -✉️ Email Rupesh +✉️ Email Rupesh Tiwari Founder of Fullstack Master diff --git a/package.json b/package.json index a46da56..a047ef3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fsms/angular-pubsub-app", - "version": "1.0.19", + "version": "2.0.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/projects/fsms-angular-pubsub/src/lib/contracts/definitions.ts b/projects/fsms-angular-pubsub/src/lib/contracts/definitions.ts index aa3b678..bffe0a5 100644 --- a/projects/fsms-angular-pubsub/src/lib/contracts/definitions.ts +++ b/projects/fsms-angular-pubsub/src/lib/contracts/definitions.ts @@ -23,5 +23,5 @@ export abstract class Logger { } export interface PubsubSubscription { - unsubscribe(); + unsubscribe(): void; } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index fa595a6..fb95516 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,18 +1,33 @@ -import { Component, OnInit } from '@angular/core'; -import { PubsubService } from '@fsms/angular-pubsub'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { PubsubService, PubsubSubscription } from '@fsms/angular-pubsub'; +import { OrderCreated } from './messages/order-created-message'; import { OrderReady } from './messages/order-ready-message'; import { OrderShipped } from './messages/order-shipped-message'; -import { OrderCreated } from './messages/order-created-message'; +import { PlaceOrder } from './orders/messages/place-order-message'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) -export class AppComponent implements OnInit { +export class AppComponent implements OnInit, OnDestroy { + subscriptions: PubsubSubscription[] = []; + constructor(private pubsubService: PubsubService) {} - ngOnInit(): void {} + ngOnInit(): void { + // HERE >= + this.subscriptions.push( + this.pubsubService.subscribe({ + messageType: PlaceOrder.messageType, + callback: (msg) => console.log('received', msg), + }) + ); + } + + ngOnDestroy(): void { + this.subscriptions.forEach((s) => s.unsubscribe()); + } orderPlaced($event: KeyboardEvent) { $event.preventDefault();