├── .npmignore ├── .gitignore ├── src ├── index.ts └── auto-unsubscribe.ts ├── .travis.yml ├── .all-contributorsrc ├── tsconfig.json ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── issue.yml ├── LICENSE ├── package.json ├── README.md ├── __tests__ └── auto-unsubscribe.spec.ts └── yarn-error.log /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | src 3 | .idea -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | .idea 4 | dist -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { AutoUnsubscribe } from "./auto-unsubscribe"; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | notifications: 6 | email: false 7 | node_js: 8 | - '7' 9 | before_script: 10 | - npm prune 11 | script: 12 | - npm run test 13 | branches: 14 | except: 15 | - /^v\d+\.\d+\.\d+$/ -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "ngx-auto-unsubscribe", 3 | "projectOwner": "Netanel Basal", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "contributors": [ 12 | { 13 | "login": "misaizdaleka", 14 | "name": "misaizdaleka", 15 | "avatar_url": "https://avatars2.githubusercontent.com/u/2690948?v=4", 16 | "profile": "https://github.com/misaizdaleka", 17 | "contributions": [ 18 | "code" 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "sourceMap": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": true, 11 | "outDir": "dist/", 12 | "lib": [ 13 | "dom", 14 | "es2015", 15 | "es2017" 16 | ] 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | "lib", 21 | "__tests__" 22 | ] 23 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2019 Netanel Basal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | 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 copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | 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 16 | OR 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 LIABILITY, 19 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 20 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-auto-unsubscribe", 3 | "version": "3.0.1", 4 | "main": "dist/index.js", 5 | "description": "Class decorator that automatically unsubscribes from observables and events", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "jest", 9 | "test:watch": "jest --watch", 10 | "build": "rm -rf ./dist && tsc", 11 | "prepublish": "rm -rf ./dist && npm test && npm run build" 12 | }, 13 | "jest": { 14 | "moduleFileExtensions": [ 15 | "ts", 16 | "js" 17 | ], 18 | "transform": { 19 | "\\.(ts)$": "/node_modules/ts-jest/preprocessor.js" 20 | }, 21 | "testRegex": "/__tests__/.*\\.(ts|js)$" 22 | }, 23 | "keywords": [ 24 | "Angular unsubscribe", 25 | "Angular decorator unsubscribe", 26 | "Angular unsubscribe observables" 27 | ], 28 | "typings": "./dist/index.d.ts", 29 | "maintainers": [ 30 | "Netanel Basal" 31 | ], 32 | "repository": { 33 | "url": "https://github.com/NetanelBasal/ngx-auto-unsubscribe" 34 | }, 35 | "devDependencies": { 36 | "@types/jest": "^21.1.4", 37 | "jest": "^21.2.1", 38 | "ts-jest": "^21.1.3", 39 | "typescript": "^2.8.3" 40 | } 41 | } -------------------------------------------------------------------------------- /src/auto-unsubscribe.ts: -------------------------------------------------------------------------------- 1 | const isFunction = fn => typeof fn === "function"; 2 | 3 | const doUnsubscribe = subscription => { 4 | subscription && 5 | isFunction(subscription.unsubscribe) && 6 | subscription.unsubscribe(); 7 | }; 8 | 9 | const doUnsubscribeIfArray = subscriptionsArray => { 10 | Array.isArray(subscriptionsArray) && 11 | subscriptionsArray.forEach(doUnsubscribe); 12 | }; 13 | 14 | export function AutoUnsubscribe({ 15 | blackList = [], 16 | arrayName = "", 17 | event = "ngOnDestroy" 18 | } = {}) { 19 | return function(constructor: Function) { 20 | const original = constructor.prototype[event]; 21 | 22 | if (!isFunction(original)) { 23 | throw new Error( 24 | `${ 25 | constructor.name 26 | } is using @AutoUnsubscribe but does not implement ${event}` 27 | ); 28 | } 29 | 30 | constructor.prototype[event] = function() { 31 | isFunction(original) && original.apply(this, arguments); 32 | 33 | if (arrayName) { 34 | doUnsubscribeIfArray(this[arrayName]); 35 | return; 36 | } 37 | 38 | for (let propName in this) { 39 | if (blackList.includes(propName)) continue; 40 | 41 | const property = this[propName]; 42 | doUnsubscribe(property); 43 | } 44 | }; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: [bug, triage] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: what-happened 22 | attributes: 23 | label: What happened? 24 | description: Also tell us, what did you expect to happen? 25 | placeholder: Tell us what you see! 26 | value: "A bug happened!" 27 | validations: 28 | required: true 29 | - type: dropdown 30 | id: version 31 | attributes: 32 | label: Version 33 | description: What version of our software are you running? 34 | options: 35 | - 1.0.2 (Default) 36 | - 1.0.3 (Edge) 37 | validations: 38 | required: true 39 | - type: dropdown 40 | id: browsers 41 | attributes: 42 | label: What browsers are you seeing the problem on? 43 | multiple: true 44 | options: 45 | - Firefox 46 | - Chrome 47 | - Safari 48 | - Microsoft Edge 49 | - type: textarea 50 | id: logs 51 | attributes: 52 | label: Relevant log output 53 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 54 | render: shell 55 | - type: checkboxes 56 | id: terms 57 | attributes: 58 | label: Code of Conduct 59 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) 60 | options: 61 | - label: I agree to follow this project's Code of Conduct 62 | required: true 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Angular - Auto Unsubscribe For Pros 2 | 3 | [![npm](https://img.shields.io/npm/dt/ngx-auto-unsubscribe.svg)]() 4 | [![Build Status](https://semaphoreci.com/api/v1/netanel7799/ngx-auto-unsubscribe/branches/master/badge.svg)](https://semaphoreci.com/netanel7799/ngx-auto-unsubscribe) 5 | [![Build Status](https://travis-ci.org/NetanelBasal/ngx-auto-unsubscribe.svg?branch=master)](https://travis-ci.org/NetanelBasal/ngx-auto-unsubscribe) 6 | [![npm](https://img.shields.io/npm/l/ngx-auto-unsubscribe.svg)]() 7 | [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 8 | 9 | For Angular 9+, use [until-destroy](https://github.com/ngneat/until-destroy) 10 | 11 | #### Class decorator that will automatically unsubscribe from observable subscriptions when the component is destroyed 12 | 13 | ## Installation 14 | 15 | `npm install ngx-auto-unsubscribe --save` 16 | 17 | ## Usage 18 | 19 | ```js 20 | import { AutoUnsubscribe } from "ngx-auto-unsubscribe"; 21 | 22 | @AutoUnsubscribe() 23 | @Component({ 24 | selector: 'inbox' 25 | }) 26 | export class InboxComponent { 27 | one: Subscription; 28 | two: Subscription; 29 | 30 | constructor( private store: Store, private element : ElementRef ) {} 31 | 32 | ngOnInit() { 33 | this.one = store.select("data").subscribe(data => // do something); 34 | this.two = Observable.interval.subscribe(data => // do something); 35 | } 36 | 37 | // This method must be present, even if empty. 38 | ngOnDestroy() { 39 | // We'll throw an error if it doesn't 40 | } 41 | } 42 | ``` 43 | 44 | ### Options 45 | 46 | | Option | Description | Default Value | 47 | | ----------- | ------------------------------------------------------ | ------------- | 48 | | `arrayName` | unsubscribe from subscriptions only in specified array | `''` | 49 | | `blackList` | an array of properties to exclude | `[]` | 50 | | `event` | a name of event callback to execute on | `ngOnDestroy` | 51 | 52 | Note: `blackList` is ignored if `arrayName` is specified. 53 | 54 | ### Similar projects 55 | 56 | You can also use https://github.com/NetanelBasal/ngx-take-until-destroy. 57 | -------------------------------------------------------------------------------- /__tests__/auto-unsubscribe.spec.ts: -------------------------------------------------------------------------------- 1 | import { AutoUnsubscribe } from "../src/auto-unsubscribe"; 2 | 3 | const mockSubscription = { 4 | unsubscribe: jest.fn() 5 | }; 6 | 7 | const mockSubscription2 = { 8 | unsubscribe: jest.fn() 9 | }; 10 | 11 | describe("@AutoUnsubscribe", () => { 12 | afterEach(() => { 13 | jest.resetAllMocks(); 14 | }); 15 | 16 | it("should call unsubscribe on destroy", () => { 17 | @AutoUnsubscribe() 18 | class TodsComponent { 19 | sub = mockSubscription; 20 | ngOnDestroy() {} 21 | } 22 | 23 | new TodsComponent().ngOnDestroy(); 24 | expect(mockSubscription.unsubscribe.mock.calls.length).toBe(1); 25 | }); 26 | 27 | it("should call unsubscribe on custom event callback", () => { 28 | @AutoUnsubscribe({ event: "ionViewDidLeave" }) 29 | class TodsComponent { 30 | sub = mockSubscription; 31 | ngOnDestroy() {} 32 | ionViewDidLeave() {} 33 | } 34 | 35 | const cmp = new TodsComponent(); 36 | 37 | cmp.ngOnDestroy(); 38 | expect(mockSubscription.unsubscribe.mock.calls.length).toBe(0); 39 | 40 | cmp.ionViewDidLeave(); 41 | expect(mockSubscription.unsubscribe.mock.calls.length).toBe(1); 42 | }); 43 | 44 | it("should work with multiple observables", () => { 45 | @AutoUnsubscribe() 46 | class TodsComponent { 47 | sub = mockSubscription; 48 | sub2 = mockSubscription2; 49 | ngOnDestroy() {} 50 | } 51 | 52 | new TodsComponent().ngOnDestroy(); 53 | expect(mockSubscription.unsubscribe.mock.calls.length).toBe(1); 54 | expect(mockSubscription2.unsubscribe.mock.calls.length).toBe(1); 55 | }); 56 | 57 | it("should NOT unsubscribe if property is in blacklist", () => { 58 | @AutoUnsubscribe({ blackList: ["sub"] }) 59 | class TodsComponent { 60 | sub = mockSubscription; 61 | sub2 = mockSubscription2; 62 | ngOnDestroy() {} 63 | } 64 | 65 | new TodsComponent().ngOnDestroy(); 66 | expect(mockSubscription.unsubscribe.mock.calls.length).toBe(0); 67 | expect(mockSubscription2.unsubscribe.mock.calls.length).toBe(1); 68 | }); 69 | 70 | describe("includeArrays", () => { 71 | it("should unsubscribe an array of subscriptions", () => { 72 | @AutoUnsubscribe({ arrayName: "subs" }) 73 | class TodsComponent { 74 | subs = Array(3).fill(mockSubscription); 75 | ngOnDestroy() {} 76 | } 77 | 78 | new TodsComponent().ngOnDestroy(); 79 | expect(mockSubscription.unsubscribe.mock.calls.length).toBe(3); 80 | }); 81 | }); 82 | 83 | describe("arrayName", () => { 84 | beforeEach(() => { 85 | @AutoUnsubscribe({ arrayName: "subscriptions" }) 86 | class TodsComponent { 87 | subscriptions = Array(3).fill(mockSubscription); 88 | subs = Array(3).fill(mockSubscription2); 89 | ngOnDestroy() {} 90 | } 91 | 92 | new TodsComponent().ngOnDestroy(); 93 | }); 94 | 95 | it(`should unsubscribe from subscriptions in specified array`, () => { 96 | expect(mockSubscription.unsubscribe.mock.calls.length).toBe(3); 97 | }); 98 | 99 | it(`should not unsubscribe from subscriptions in other arrays`, () => { 100 | expect(mockSubscription2.unsubscribe.mock.calls.length).toBe(0); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | /usr/local/bin/node /usr/local/bin/yarn c 3 | 4 | PATH: 5 | /Users/netanelbasal/bin:/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/netanelbasal/bin 6 | 7 | Yarn version: 8 | 1.5.0 9 | 10 | Node version: 11 | 9.0.0 12 | 13 | Platform: 14 | darwin x64 15 | 16 | npm manifest: 17 | { 18 | "name": "ngx-auto-unsubscribe", 19 | "version": "2.3.0", 20 | "main": "dist/index.js", 21 | "description": "Class decorator that automatically unsubscribes from observables and events", 22 | "license": "MIT", 23 | "scripts": { 24 | "test": "jest", 25 | "build": "rm -rf ./dist && tsc", 26 | "prepublish": "rm -rf ./dist && npm test && npm run build", 27 | "c": "all-contributors check", 28 | "contributors:generate": "all-contributors generate" 29 | }, 30 | "jest": { 31 | "moduleFileExtensions": [ 32 | "ts", 33 | "js" 34 | ], 35 | "transform": { 36 | "\\.(ts)$": "/node_modules/ts-jest/preprocessor.js" 37 | }, 38 | "testRegex": "/__tests__/.*\\.(ts|js)$" 39 | }, 40 | "keywords": [ 41 | "Angular unsubscribe", 42 | "Angular decorator unsubscribe", 43 | "Angular unsubscribe observables" 44 | ], 45 | "typings": "./dist/index.d.ts", 46 | "maintainers": [ 47 | "Netanel Basal" 48 | ], 49 | "repository": { 50 | "url": "https://github.com/NetanelBasal/ngx-auto-unsubscribe" 51 | }, 52 | "devDependencies": { 53 | "@types/jest": "^21.1.4", 54 | "all-contributors-cli": "^4.11.1", 55 | "jest": "^21.2.1", 56 | "ts-jest": "^21.1.3", 57 | "typescript": "^2.5.3" 58 | } 59 | } 60 | 61 | yarn manifest: 62 | No manifest 63 | 64 | Lockfile: 65 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 66 | # yarn lockfile v1 67 | "@types/chai": 68 | version "3.4.34" 69 | resolved "https://registry.yarnpkg.com/@types/chai/-/chai-3.4.34.tgz#d5335792823bb09cddd5e38c3d211b709183854d" 70 | 71 | rxjs@^5.0.0-rc.1: 72 | version "5.0.0-rc.2" 73 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.0.0-rc.2.tgz#d38206f50eeb1e77b0832a74c1c2adeeed5ec2b7" 74 | dependencies: 75 | symbol-observable "^1.0.1" 76 | 77 | symbol-observable@^1.0.1: 78 | version "1.0.4" 79 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" 80 | 81 | typescript@^2.0.6: 82 | version "2.0.7" 83 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.0.7.tgz#efc39e8822e240d0b741d06ff86708137bcdb5e4" 84 | 85 | Trace: 86 | Error: Command failed. 87 | Exit code: 1 88 | Command: sh 89 | Arguments: -c all-contributors check 90 | Directory: /Users/netanelbasal/www/ngx-auto-unsubscribe 91 | Output: 92 | 93 | at ProcessTermError.MessageError (/usr/local/lib/node_modules/yarn/lib/cli.js:186:110) 94 | at new ProcessTermError (/usr/local/lib/node_modules/yarn/lib/cli.js:226:113) 95 | at ChildProcess. (/usr/local/lib/node_modules/yarn/lib/cli.js:30281:17) 96 | at emitTwo (events.js:135:13) 97 | at ChildProcess.emit (events.js:224:7) 98 | at maybeClose (internal/child_process.js:943:16) 99 | at Process.ChildProcess._handle.onexit (internal/child_process.js:220:5) 100 | --------------------------------------------------------------------------------