├── .gitignore ├── CHANGELOG.md ├── package.json ├── bower.json ├── LICENSE ├── dist └── angular-pubsub.min.js ├── Gruntfile.js ├── .jshintrc ├── .jscsrc ├── README.md └── src └── angular-pubsub.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /demo 3 | .idea 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v1.0.1 4 | - Add notice that repository is no longer maintained. 5 | 6 | ## v1.0.0 7 | 8 | ### Breaking changes 9 | 10 | - Reverse the arguments the `callback` function accepts, in order to allow the usage of `data` argument without the need to also specify the `topic` if not needed. 11 | - Throw exception if `callback` is not a `function` or is not provided at all. 12 | 13 | ### Other updates 14 | - Fix JSDoc annotations. 15 | - Update dev dependancies. 16 | - Provide `npm` scripts to run the tasks. No more need for global dependancies installed (Grunt). 17 | 18 | ## v0.0.1 19 | 20 | Initial release 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-PubSub", 3 | "version": "1.0.1", 4 | "description": "Angular 1.x implementation of the Publish/Subscribe pattern.", 5 | "homepage": "https://github.com/georapbox/angular-PubSub", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:georapbox/angular-PubSub.git" 9 | }, 10 | "scripts": { 11 | "minify": "grunt build" 12 | }, 13 | "license": "(MIT)", 14 | "main": "src/angular-pubsub.js", 15 | "devDependencies": { 16 | "grunt": "~1.0.1", 17 | "grunt-cli": "~1.2.0", 18 | "grunt-contrib-uglify": "~2.0.0", 19 | "grunt-remove-logging": "~0.2.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-PubSub", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/georapbox/angular-PubSub", 5 | "authors": [ 6 | "George Raptis " 7 | ], 8 | "description": "AngularJS implementation of the Publish–Subscribe pattern.", 9 | "main": "src/angular-pubsub.js", 10 | "license": "MIT", 11 | "keywords": [ 12 | "Angular", 13 | "pubsub", 14 | "publish", 15 | "subscribe", 16 | "pattern", 17 | "service" 18 | ], 19 | "ignore": [ 20 | "**/.*", 21 | ".*", 22 | "node_modules" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 George Raptis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/angular-pubsub.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-PubSub 3 | * Angular 1.x implementation of the Publish/Subscribe pattern. 4 | * 5 | * @version 1.0.1 6 | * @homepage https://github.com/georapbox/angular-PubSub 7 | * @repository git@github.com:georapbox/angular-PubSub.git 8 | * @license (MIT) 9 | */ 10 | !function(a){"use strict";a("PubSub",[]).factory("PubSub",["$timeout",function(a){function b(a){return function(){return this[a].apply(this,arguments)}}var c={topics:{},subUid:-1};return c.subscribe=function(a,b,c){var d=this.subUid+=1,e={};if("function"!=typeof b)throw new TypeError("When subscribing for an event, a callback function must be defined.");return this.topics[a]||(this.topics[a]=[]),e.token=d,e.callback=b,e.once=!!c,this.topics[a].push(e),d},c.subscribeOnce=function(a,b){return this.subscribe(a,b,!0)},c.publish=function(b,c){var d,e,f,g,h=this;return!!this.topics[b]&&(a(function(){for(e=h.topics[b],d=e?e.length:0;d;)d-=1,g=e[d].token,f=e[d],f.callback(c,{name:b,token:g}),f.once===!0&&h.unsubscribe(g)},0),!0)},c.unsubscribe=function(a){var b,c,d=!1;for(b in this.topics)if(Object.hasOwnProperty.call(this.topics,b)&&this.topics[b]){for(c=this.topics[b].length;c;){if(c-=1,this.topics[b][c].token===a)return this.topics[b].splice(c,1),a;b===a&&(this.topics[b].splice(c,1),d=!0)}if(d===!0)return a}return!1},c.on=b("subscribe"),c.once=b("subscribeOnce"),c.trigger=b("publish"),c.off=b("unsubscribe"),c}])}(angular.module); 11 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | 5 | uglify: { 6 | options: { 7 | banner: '/**\n * <%= pkg.name %>\n * <%= pkg.description %>\n * \n * @version <%= pkg.version %>\n * @homepage <%= pkg.homepage %>\n * @repository <%= pkg.repository.url %>\n * @license <%= pkg.license %>\n */\n' 8 | }, 9 | build: { 10 | src: 'src/angular-pubsub.js', 11 | dest: 'dist/angular-pubsub.min.js' 12 | } 13 | }, 14 | 15 | removelogging: { 16 | dev: { 17 | src: 'src/angular-pubsub.js', 18 | dest: 'src/angular-pubsub.js', 19 | options: {} 20 | }, 21 | dist: { 22 | src: 'dist/angular-pubsub.min.js', 23 | dest: 'dist/angular-pubsub.min.js', 24 | options: {} 25 | } 26 | } 27 | }); 28 | 29 | // Load plugins. 30 | grunt.loadNpmTasks('grunt-contrib-uglify'); 31 | grunt.loadNpmTasks('grunt-remove-logging'); 32 | 33 | 34 | // Register task(s). 35 | grunt.registerTask( 36 | 'build', 37 | 'Minify to dist folder and remove logs.', 38 | ['uglify', 'removelogging:dist'] 39 | ); 40 | 41 | grunt.registerTask( 42 | 'removeloggingDev', 43 | 'Removes logs from src files.', 44 | ['removelogging:dev'] 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "esversion": 6, 6 | "forin": true, 7 | "freeze": true, 8 | "funcscope": false, 9 | "futurehostile": true, 10 | "globals": { 11 | "angular": false 12 | }, 13 | "iterator": false, 14 | "latedef": true, 15 | "maxcomplexity": false, 16 | "maxdepth": 0, 17 | "maxerr": 100, 18 | "maxparams": 6, 19 | "maxstatements": 0, 20 | "noarg": true, 21 | "nocomma": true, 22 | "nonbsp": true, 23 | "nonew": true, 24 | "notypeof": false, 25 | "predef": [ 26 | "define", 27 | "describe", 28 | "expect", 29 | "it", 30 | "spyOn" 31 | ], 32 | "shadow": false, 33 | "singleGroups": true, 34 | "strict": true, 35 | "undef": true, 36 | "unused": true, 37 | "varstmt": false, 38 | "asi": false, 39 | "boss": false, 40 | "debug": false, 41 | "elision": false, 42 | "eqnull": true, 43 | "evil": false, 44 | "expr": true, 45 | "lastsemic": false, 46 | "loopfunc": false, 47 | "moz": false, 48 | "noyield": false, 49 | "plusplus": true, 50 | "proto": false, 51 | "scripturl": false, 52 | "supernew": false, 53 | "validthis": false, 54 | "withstmt": false, 55 | "browser": true, 56 | "browserify": false, 57 | "couch": false, 58 | "devel": true, 59 | "dojo": false, 60 | "jasmine": false, 61 | "jquery": false, 62 | "mocha": false, 63 | "module": false, 64 | "mootools": false, 65 | "node": false, 66 | "nonstandard": false, 67 | "phantom": false, 68 | "prototypejs": false, 69 | "qunit": false, 70 | "rhino": false, 71 | "shelljs": false, 72 | "typed": false, 73 | "worker": false, 74 | "wsh": false, 75 | "yui": false 76 | } 77 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": [ 3 | "if", 4 | "else", 5 | "for", 6 | "while", 7 | "do", 8 | "try", 9 | "catch" 10 | ], 11 | "validateIndentation": 2, 12 | "maximumLineLength": { 13 | "value": 100, 14 | "allExcept": [ 15 | "comments", 16 | "urlComments", 17 | "regex", 18 | "functionSignature", 19 | "require" 20 | ] 21 | }, 22 | "requireSpacesInForStatement": true, 23 | "requireParenthesesAroundIIFE": true, 24 | "requireLineFeedAtFileEnd": true, 25 | "requireBlocksOnNewline": 1, 26 | "disallowPaddingNewlinesInBlocks": true, 27 | "disallowEmptyBlocks": true, 28 | "requireCommaBeforeLineBreak": true, 29 | "disallowTrailingComma": true, 30 | "requireSpaceAfterLineComment": true, 31 | "disallowMultipleSpaces": { 32 | "allowEOLComments": true 33 | }, 34 | "disallowSpaceAfterObjectKeys": true, 35 | "requireSpaceBeforeObjectValues": true, 36 | "requireTrailingComma": false, 37 | "requireSpaceAfterComma": true, 38 | "requireSpaceBeforeComma": false, 39 | "disallowSpaceAfterComma": false, 40 | "disallowSpaceBeforeComma": true, 41 | "requireSpaceBeforeBinaryOperators": true, 42 | "requireSpaceAfterBinaryOperators": true, 43 | "disallowMixedSpacesAndTabs": true, 44 | "disallowSpacesInsideObjectBrackets": "all", 45 | "disallowSpacesInsideArrayBrackets": "all", 46 | "requireSpacesInConditionalExpression": true, 47 | "requireSpacesInAnonymousFunctionExpression": { 48 | "beforeOpeningRoundBrace": true, 49 | "beforeOpeningCurlyBrace": true, 50 | "allExcept": ["shorthand"] 51 | }, 52 | "requireSpacesInFunctionDeclaration": { 53 | "beforeOpeningCurlyBrace": true 54 | }, 55 | "disallowSpacesInFunctionDeclaration": { 56 | "beforeOpeningRoundBrace": true, 57 | }, 58 | "requireSpaceAfterKeywords": [ 59 | "if", 60 | "else", 61 | "for", 62 | "while", 63 | "do", 64 | "switch", 65 | "case", 66 | "return", 67 | "try", 68 | "catch", 69 | "typeof", 70 | "void" 71 | ], 72 | "disallowSpaceBeforePostfixUnaryOperators": true, 73 | "disallowTrailingWhitespace": true, 74 | "disallowKeywords": ["with"], 75 | "requireDotNotation": true, 76 | "disallowYodaConditions": true, 77 | "disallowNewlineBeforeBlockStatements": true, 78 | "requireParenthesesAroundIIFE": true, 79 | "disallowKeywordsOnNewLine": [ 80 | "else", 81 | "catch" 82 | ], 83 | "disallowMultipleLineBreaks": true, 84 | "disallowMultipleVarDecl": false, 85 | "requirePaddingNewLinesAfterBlocks": false, 86 | "disallowQuotedKeysInObjects": false, 87 | "validateQuoteMarks": { 88 | "mark": "'", 89 | "escape": true, 90 | "ignoreJSX": true 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-PubSub 2 | Angular 1.x implementation of the [Publish–Subscribe](http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) pattern. 3 | 4 | > ### NOTE: This repository is no longer maintained. 5 | > The current repository and the `npm` package are not going anywhere but for future projects, please consider using [PubSub](https://github.com/georapbox/PubSub) package instead. Check the `examples` folder for a working example in Angular 1.x application. 6 | 7 | [![npm version](https://badge.fury.io/js/angular-PubSub.svg)](http://badge.fury.io/js/angular-PubSub) 8 | [![Dependencies](https://david-dm.org/georapbox/angular-PubSub.svg?theme=shields.io)](https://david-dm.org/georapbox/angular-PubSub) 9 | [![devDependency Status](https://david-dm.org/georapbox/angular-PubSub/dev-status.svg)](https://david-dm.org/georapbox/angular-PubSub#info=devDependencies) 10 | 11 | ## Installation 12 | 13 | ### Git installation 14 | 15 | `$ git clone https://github.com/georapbox/angular-PubSub.git` 16 | 17 | ### npm installation 18 | 19 | `$ npm install angular-PubSub --save` 20 | 21 | ### Bower installation 22 | `$ bower install angular.pubsub` 23 | 24 | ## Using PubSub 25 | 26 | ### Register 27 | Include `angular-pubsub.js` in your project after `angular.js` and register to your application: 28 | ```js 29 | var app = angular.module('app', ['PubSub']); 30 | ``` 31 | 32 | ### Inject as dependancey 33 | Inject the service as a dependancy of the application modules, to use it: 34 | ```js 35 | var MyController = app.controller('MyController', ['PubSub', function (PubSub) { 36 | // do your stuff here... 37 | }]); 38 | ``` 39 | 40 | ### Subscribing events 41 | The "listener" is the function to be executed when an event is emitted. 42 | ```js 43 | function listener(data, topic) { 44 | console.log('An event is published.'); 45 | console.log(topic); 46 | console.log(data); 47 | } 48 | 49 | // Subscribe to event 50 | var sub = PubSub.subscribe('event-name', listener); 51 | 52 | // Subscribe to event and execute only one time 53 | var subOnce = PubSub.subscribeOnce('event-name', listener) 54 | ``` 55 | 56 | ### Publishing events 57 | The `publish` method takes two arguments: 58 | 59 | - The first one is the name of the event. 60 | - The second one (optional) is the data we may want to pass along as. We can pass data along using an array or an object as shown below. 61 | ```js 62 | PubSub.publish('event-name', {prop1: value1, prop2: value2}); 63 | ``` 64 | 65 | ### Unsubscribing events 66 | There are two ways to unsubscribe an event: 67 | 68 | - Unsubscribe from a specific topic based on a tokenized reference to the subscription. 69 | ```js 70 | PubSub.unsubscribe(sub); 71 | ``` 72 | - Unsubscribe from a specific topic based on topic name. This way we can unsubscribe all events with the same name. 73 | ```js 74 | PubSub.unsubscribe('event-name'); 75 | ``` 76 | 77 | ## Methods aliases 78 | - `on` - `subscribe` 79 | - `once` - `subscribeOnce` 80 | - `trigger` - `publish` 81 | - `off` - `unsubscribe` 82 | 83 | ## Minify 84 | 85 | ```sh 86 | $ npm run minify 87 | ``` 88 | -------------------------------------------------------------------------------- /src/angular-pubsub.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 'use strict'; 3 | 4 | module('PubSub', []).factory('PubSub', ['$timeout', function ($timeout) { 5 | /** 6 | * Alias a method while keeping the context correct, 7 | * to allow for overwriting of target method. 8 | * 9 | * @private 10 | * @this {PubSub} 11 | * @param {String} fn The name of the target method. 12 | * @return {function} The aliased method. 13 | */ 14 | function alias(fn) { 15 | return function closure () { 16 | return this[fn].apply(this, arguments); 17 | }; 18 | } 19 | 20 | var PubSub = { 21 | topics: {}, // Storage for topics that can be broadcast or listened to. 22 | subUid: -1 // A topic identifier. 23 | }; 24 | 25 | /** 26 | * Subscribe to events of interest with a specific topic name and a 27 | * callback function, to be executed when the topic/event is observed. 28 | * 29 | * @this {PubSub} 30 | * @param {String} topic The topic name. 31 | * @param {function} callback Callback function to execute on event, taking two arguments: 32 | * - {*} data The data passed when publishing an event 33 | * - {Object} topic The topic's info (name & token) 34 | * @param {Boolean} [once=false] Checks if event will be triggered only one time. 35 | * @return {Number} The topic's token. 36 | */ 37 | PubSub.subscribe = function (topic, callback, once) { 38 | var token = this.subUid += 1, 39 | obj = {}; 40 | 41 | if (typeof callback !== 'function') { 42 | throw new TypeError('When subscribing for an event, a callback function must be defined.'); 43 | } 44 | 45 | if (!this.topics[topic]) { 46 | this.topics[topic] = []; 47 | } 48 | 49 | obj.token = token; 50 | obj.callback = callback; 51 | obj.once = !!once; 52 | 53 | this.topics[topic].push(obj); 54 | 55 | return token; 56 | }; 57 | 58 | /** 59 | * Subscribe to events of interest setting a flag 60 | * indicating the event will be published only one time. 61 | * 62 | * @this {PubSub} 63 | * @param {String} topic The topic's name. 64 | * @param {function} callback Callback function to execute on event, taking two arguments: 65 | * - {*} data The data passed when publishing an event 66 | * - {Object} topic The topic's info (name & token) 67 | * @return {Number} The topic's token. 68 | */ 69 | PubSub.subscribeOnce = function (topic, callback) { 70 | return this.subscribe(topic, callback, true); 71 | }; 72 | 73 | /** 74 | * Publish or broadcast events of interest with a specific 75 | * topic name and arguments such as the data to pass along. 76 | * 77 | * @this {PubSub} 78 | * @param {String} topic The topic's name. 79 | * @param {*} [data] The data to be passed. 80 | * @return {Boolean} True if topic exists and event is published; otherwise false. 81 | */ 82 | PubSub.publish = function (topic, data) { 83 | var that = this, 84 | len, subscribers, currentSubscriber, token; 85 | 86 | if (!this.topics[topic]) { 87 | return false; 88 | } 89 | 90 | $timeout(function () { 91 | subscribers = that.topics[topic]; 92 | len = subscribers ? subscribers.length : 0; 93 | 94 | while (len) { 95 | len -= 1; 96 | token = subscribers[len].token; 97 | currentSubscriber = subscribers[len]; 98 | 99 | currentSubscriber.callback(data, { 100 | name: topic, 101 | token: token 102 | }); 103 | 104 | // Unsubscribe from event based on tokenized reference, 105 | // if subscriber's property once is set to true. 106 | if (currentSubscriber.once === true) { 107 | that.unsubscribe(token); 108 | } 109 | } 110 | }, 0); 111 | 112 | return true; 113 | }; 114 | 115 | /** 116 | * Unsubscribe from a specific topic, based on the topic name, 117 | * or based on a tokenized reference to the subscription. 118 | * 119 | * @this {PubSub} 120 | * @param {String|Object} topic Topic's name or subscription referenece. 121 | * @return {Boolean|String} False if `topic` does not match a subscribed event, else the topic's name. 122 | */ 123 | PubSub.unsubscribe = function (topic) { 124 | var tf = false, 125 | prop, len; 126 | 127 | for (prop in this.topics) { 128 | if (Object.hasOwnProperty.call(this.topics, prop)) { 129 | if (this.topics[prop]) { 130 | len = this.topics[prop].length; 131 | 132 | while (len) { 133 | len -= 1; 134 | 135 | // If t is a tokenized reference to the subscription. 136 | // Removes one subscription from the array. 137 | if (this.topics[prop][len].token === topic) { 138 | this.topics[prop].splice(len, 1); 139 | return topic; 140 | } 141 | 142 | // If t is the event type. 143 | // Removes all the subscriptions that match the event type. 144 | if (prop === topic) { 145 | this.topics[prop].splice(len, 1); 146 | tf = true; 147 | } 148 | } 149 | 150 | if (tf === true) { 151 | return topic; 152 | } 153 | } 154 | } 155 | } 156 | 157 | return false; 158 | }; 159 | 160 | // Alias for public methods. 161 | PubSub.on = alias('subscribe'); 162 | PubSub.once = alias('subscribeOnce'); 163 | PubSub.trigger = alias('publish'); 164 | PubSub.off = alias('unsubscribe'); 165 | 166 | return PubSub; 167 | }]); 168 | }(angular.module)); 169 | --------------------------------------------------------------------------------