├── .circleci
└── config.yml
├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── CHANGELOG.md
├── LICENSE
├── README.md
├── karma.conf.js
├── package-dist.json
├── package.json
├── scripts
└── pack.js
├── source
├── index.ts
├── observe-spec.ts
└── observe.ts
├── tsconfig-dist-cjs.json
├── tsconfig-dist-esm2015.json
├── tsconfig-dist-esm5.json
├── tsconfig-dist.json
├── tsconfig.json
├── tslint.json
├── webpack.config.js
├── webpack.config.test.js
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | # https://circleci.com/docs/2.0/docker-image-tags.json
6 | - image: circleci/node:current-browsers
7 | steps:
8 | - checkout
9 | - run:
10 | name: Install Greenkeeper Lockfile
11 | command: |
12 | echo 'export PATH=$(yarn global bin):$PATH' >> $BASH_ENV
13 | source $BASH_ENV
14 | yarn global add greenkeeper-lockfile@1
15 | - run:
16 | name: Update Greenkeeper Lockfile
17 | command: "greenkeeper-lockfile-update"
18 | - restore_cache:
19 | name: Restore Yarn Package Cache
20 | keys:
21 | - yarn-packages-{{ checksum "yarn.lock" }}
22 | - run:
23 | name: Install Packages
24 | command: yarn install
25 | - save_cache:
26 | name: Save Yarn Package Cache
27 | key: yarn-packages-{{ checksum "yarn.lock" }}
28 | paths:
29 | - ~/.cache/yarn
30 | - run:
31 | name: Test
32 | command: yarn test
33 | - run:
34 | name: Upload Greenkeeper Lockfile
35 | command: "greenkeeper-lockfile-upload"
36 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | indent_size = 4
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [cartant] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /bundles
3 | /dist
4 | /node_modules
5 | /temp
6 | yarn-error.log
7 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [2.1.6](https://github.com/cartant/rxjs-observe/compare/v2.1.5...v2.1.6) (2021-05-19)
3 |
4 | ### Fixes
5 |
6 | * Widen RxJS peer range to support version 7. ([f9a7936](https://github.com/cartant/rxjs-observe/commit/f9a7936))
7 |
8 |
9 | ## [2.1.5](https://github.com/cartant/rxjs-observe/compare/v2.1.4...v2.1.5) (2019-10-26)
10 |
11 | ### Fixes
12 |
13 | * Ensure the same function is returned for each property get. ([f9a7936](https://github.com/cartant/rxjs-observe/commit/f9a7936))
14 |
15 |
16 | ## [2.1.4](https://github.com/cartant/rxjs-observe/compare/v2.1.3...v2.1.4) (2019-04-22)
17 |
18 | ### Changes
19 |
20 | * Export the `Observables` type. ([27fd8b3](https://github.com/cartant/rxjs-observe/commit/27fd8b3))
21 |
22 |
23 | ## [2.1.3](https://github.com/cartant/rxjs-observe/compare/v2.1.2...v2.1.3) (2019-04-21)
24 |
25 | ### Changes
26 |
27 | * Lookup callback and target values - in the `get` handler - only once. ([4e73a89](https://github.com/cartant/rxjs-observe/commit/4e73a89))
28 |
29 |
30 | ## [2.1.2](https://github.com/cartant/rxjs-observe/compare/v2.1.1...v2.1.2) (2019-04-21)
31 |
32 | ### Fixes
33 |
34 | * Add callbacks to proxy type. ([e19f466](https://github.com/cartant/rxjs-observe/commit/e19f466))
35 |
36 |
37 | ## [2.1.1](https://github.com/cartant/rxjs-observe/compare/v2.1.0...v2.1.1) (2019-04-21)
38 |
39 | ### Fixes
40 |
41 | * Don't override instance methods with proxy callbacks. ([156439b](https://github.com/cartant/rxjs-observe/commit/156439b))
42 |
43 |
44 | ## [2.1.0](https://github.com/cartant/rxjs-observe/compare/v2.0.0...v2.1.0) (2019-04-21)
45 |
46 | ### Features
47 |
48 | * Add proxy callbacks. ([f2b6bd5](https://github.com/cartant/rxjs-observe/commit/f2b6bd5))
49 |
50 |
51 | ## [2.0.0](https://github.com/cartant/rxjs-observe/compare/v1.0.2...v2.0.0) (2018-11-19)
52 |
53 | ### Breaking Changes
54 |
55 | * Strongly type function parameters - which requires TypeScript 3.0 or later. ([e0c298f](https://github.com/cartant/rxjs-observe/commit/e0c298f))
56 |
57 |
58 | ## [1.0.2](https://github.com/cartant/rxjs-observe/compare/v1.0.1...v1.0.2) (2018-06-15)
59 |
60 | ### Bug Fixes
61 |
62 | * Support `Symbol` properties and methods. ([af889b0](https://github.com/cartant/rxjs-observe/commit/af889b0))
63 |
64 |
65 | ## [1.0.1](https://github.com/cartant/rxjs-observe/compare/v1.0.0...v1.0.1) (2018-06-14)
66 |
67 | ### Bug Fixes
68 |
69 | * Emit notifications after the assignment or call. ([af65aab](https://github.com/cartant/rxjs-observe/commit/af65aab))
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Nicholas Jamieson
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rxjs-observe
2 |
3 | [](https://github.com/cartant/rxjs-observe/blob/master/LICENSE)
4 | [](https://www.npmjs.com/package/rxjs-observe)
5 | [](http://travis-ci.org/cartant/rxjs-observe)
6 | [](https://david-dm.org/cartant/rxjs-observe)
7 | [](https://david-dm.org/cartant/rxjs-observe#info=devDependencies)
8 | [](https://david-dm.org/cartant/rxjs-observe#info=peerDependencies)
9 |
10 | ### What is it?
11 |
12 | It's an `observe` function that can be used to create observable sources for an arbitrary object's property assignements and method calls.
13 |
14 | ### Why might you need it?
15 |
16 | If you need to convert an imperative API to an observable API, you might find this useful.
17 |
18 | ## Install
19 |
20 | Install the package using NPM:
21 |
22 | ```
23 | npm install rxjs-observe --save
24 | ```
25 |
26 | TypeScript 3.0 or later is required, as the type declaration for `observe` uses [generic rest parameters](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#generic-rest-parameters).
27 |
28 | ## Usage
29 |
30 | Pass an object instance to `observe` and receive an `observables` object - that contains observable sources for the object's properties and methods - and a `proxy`:
31 |
32 | ```ts
33 | import { observe } from "rxjs-observe";
34 |
35 | const instance = { name: "Alice" };
36 | const { observables, proxy } = observe(instance);
37 | observables.name.subscribe(name => console.log(name));
38 | proxy.name = "Bob";
39 | ```
40 |
41 | `observe` can be passed optional callbacks that will be implemented in the proxy - the observed instance does not need to implement them - and forwarded to an observable with the same name:
42 |
43 | ```ts
44 | import { callback, observe } from "rxjs-observe";
45 |
46 | const instance = { name: "Alice" };
47 | const { observables, proxy } = observe(instance, {
48 | init: callback()
49 | });
50 | observables.init.subscribe(() => console.log("init"));
51 | proxy.init();
52 | ```
53 |
54 | `observe` can be called inside a constructor and the `proxy` can be returned, as in this Angular component:
55 |
56 | ```ts
57 | import { Component, Input, OnInit, OnDestroy } from "@angular/core";
58 | import { switchMapTo, takeUntil } from "rxjs/operators";
59 | import { callback, observe } from "rxjs-observe";
60 |
61 | @Component({
62 | selector: "some-component",
63 | template: "Some useless component that writes to the console"
64 | })
65 | class SomeComponent {
66 | @Input() public name: string;
67 | constructor() {
68 | const { observables, proxy } = observe(this as SomeComponent, {
69 | ngOnInit: callback(),
70 | ngOnDestroy: callback()
71 | });
72 | observables.ngOnInit.pipe(
73 | switchMapTo(observables.name),
74 | takeUntil(observables.ngOnDestroy)
75 | ).subscribe(value => console.log(value));
76 | return proxy;
77 | }
78 | }
79 | ```
80 |
81 | However, such a component implementation is ... unconventional, so proceed with caution, but ... YOLO.
82 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | process.env.CHROME_BIN = require("puppeteer").executablePath();
4 |
5 | exports = module.exports = function(config) {
6 | config.set({
7 | basePath: "",
8 | browsers: ["ChromeHeadlessNoSandbox"],
9 | colors: true,
10 | concurrency: Infinity,
11 | customLaunchers: {
12 | ChromeHeadlessNoSandbox: {
13 | base: "ChromeHeadless",
14 | flags: ["--no-sandbox"]
15 | }
16 | },
17 | exclude: [],
18 | files: [{ pattern: "source/**/*-spec.ts", watched: false }],
19 | frameworks: ["mocha"],
20 | logLevel: config.LOG_INFO,
21 | mime: {
22 | "text/x-typescript": ["ts"]
23 | },
24 | port: 9876,
25 | preprocessors: {
26 | "source/**/*-spec.ts": ["webpack"]
27 | },
28 | proxies: {},
29 | reporters: ["spec"],
30 | webpack: require("./webpack.config.test")({}),
31 | webpackMiddleware: {
32 | noInfo: true,
33 | stats: "errors-only"
34 | }
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/package-dist.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {},
3 | "es2015": "./esm2015/index.js",
4 | "main": "./index.js",
5 | "module": "./esm5/index.js",
6 | "private": false,
7 | "scripts": {},
8 | "types": "./index.d.ts",
9 | "unpkg": "./bundles/rxjs-observe.min.umd.js"
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Nicholas Jamieson ",
3 | "bugs": {
4 | "url": "https://github.com/cartant/rxjs-observe/issues"
5 | },
6 | "dependencies": {},
7 | "description": "A library for observing an object's property assignments and method calls",
8 | "devDependencies": {
9 | "@cartant/tslint-config": "^2.0.0",
10 | "@cartant/tslint-config-etc": "^2.0.0",
11 | "@cartant/tslint-config-rxjs": "^2.0.0",
12 | "@types/chai": "^4.0.0",
13 | "@types/mocha": "^8.0.0",
14 | "@types/node": "^15.0.0",
15 | "chai": "^4.0.0",
16 | "cpy-cli": "^3.0.0",
17 | "husky": "^6.0.0",
18 | "karma": "^5.0.0",
19 | "karma-chrome-launcher": "^3.1.0",
20 | "karma-mocha": "^2.0.0",
21 | "karma-spec-reporter": "^0.0.32",
22 | "karma-webpack": "^4.0.2",
23 | "lint-staged": "^11.0.0",
24 | "mkdirp": "^1.0.0",
25 | "mocha": "^8.0.0",
26 | "prettier": "^2.1.2",
27 | "puppeteer": "^9.0.0",
28 | "rimraf": "^3.0.0",
29 | "rxjs": "^7.0.0",
30 | "rxjs-tslint-rules": "^4.0.0",
31 | "ts-loader": "^8.0.0",
32 | "ts-node": "^9.0.0",
33 | "tslint": "^6.0.0",
34 | "tslint-etc": "^1.2.0",
35 | "typescript": "~4.2.4",
36 | "webpack": "^4.0.0",
37 | "webpack-cli": "^3.0.0",
38 | "webpack-rxjs-externals": "^2.0.0"
39 | },
40 | "es2015": "./dist/esm2015/index.js",
41 | "homepage": "https://github.com/cartant/rxjs-observe",
42 | "keywords": [
43 | "observable",
44 | "rxjs"
45 | ],
46 | "license": "MIT",
47 | "lint-staged": {
48 | "*.{js,jsx,ts,tsx}": [
49 | "prettier --write"
50 | ]
51 | },
52 | "main": "./dist/index.js",
53 | "module": "./dist/esm5/index.js",
54 | "name": "rxjs-observe",
55 | "optionalDependencies": {},
56 | "peerDependencies": {
57 | "rxjs": "^6.0.0 || ^7.0.0"
58 | },
59 | "private": true,
60 | "publishConfig": {
61 | "tag": "latest"
62 | },
63 | "repository": {
64 | "type": "git",
65 | "url": "https://github.com/cartant/rxjs-observe.git"
66 | },
67 | "scripts": {
68 | "dist": "yarn run dist:build && yarn run dist:copy",
69 | "dist:build": "yarn run dist:clean && yarn run dist:build:cjs && yarn run dist:build:esm2015 && yarn run dist:build:esm5 && yarn run dist:build:bundle",
70 | "dist:build:bundle": "webpack --config webpack.config.js && webpack --config webpack.config.js --env.production",
71 | "dist:build:cjs": "tsc -p tsconfig-dist-cjs.json",
72 | "dist:build:esm2015": "tsc -p tsconfig-dist-esm2015.json",
73 | "dist:build:esm5": "tsc -p tsconfig-dist-esm5.json",
74 | "dist:clean": "rimraf dist && rimraf bundles/rxjs-observe.* && mkdirp bundles",
75 | "dist:copy": "node scripts/pack.js && cpy bundles/rxjs-observe.* dist/bundles/ && cpy CHANGELOG.md LICENSE README.md dist/",
76 | "lint": "tslint --project tsconfig.json source/**/*.ts",
77 | "prepare": "husky install",
78 | "prettier": "prettier --write \"./**/{scripts,source}/**/*.{js,json,ts}\"",
79 | "prettier:ci": "prettier --check \"./**/{scripts,source}/**/*.{js,json,ts}\"",
80 | "test": "yarn run lint && yarn run test:build && yarn run test:mocha && yarn run test:karma",
81 | "test:build": "yarn run test:clean && tsc -p tsconfig.json",
82 | "test:clean": "rimraf build",
83 | "test:karma": "karma start --single-run",
84 | "test:mocha": "mocha build/**/*-spec.js",
85 | "test:watch": "yarn run lint && yarn run test:build && karma start"
86 | },
87 | "types": "./dist/index.d.ts",
88 | "unpkg": "./bundles/rxjs-observe.min.umd.js",
89 | "version": "2.1.6"
90 | }
91 |
--------------------------------------------------------------------------------
/scripts/pack.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license Use of this source code is governed by an MIT-style license that
3 | * can be found in the LICENSE file at https://github.com/cartant/rxjs-observe
4 | */
5 |
6 | "use strict";
7 |
8 | const fs = require("fs");
9 |
10 | const content = Object.assign(
11 | {},
12 | JSON.parse(fs.readFileSync("./package.json")),
13 | JSON.parse(fs.readFileSync("./package-dist.json"))
14 | );
15 | fs.writeFileSync("./dist/package.json", JSON.stringify(content, null, 2));
16 |
--------------------------------------------------------------------------------
/source/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license Use of this source code is governed by an MIT-style license that
3 | * can be found in the LICENSE file at https://github.com/cartant/rxjs-observe
4 | */
5 |
6 | export * from "./observe";
7 |
--------------------------------------------------------------------------------
/source/observe-spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license Use of this source code is governed by an MIT-style license that
3 | * can be found in the LICENSE file at https://github.com/cartant/rxjs-observe
4 | */
5 | /*tslint:disable:no-unused-expression rxjs-no-ignored-subscription rxjs-no-unsafe-scope*/
6 |
7 | import { expect } from "chai";
8 | import { Observable } from "rxjs";
9 | import { callback, observe } from "./observe";
10 |
11 | describe("observe", () => {
12 | describe("outside a constructor", () => {
13 | const job = Symbol("job");
14 | class Person {
15 | [job]: string;
16 | constructor(public age: number, public name: string) {}
17 | greet(greeting: string): string {
18 | return `${greeting}, ${this.name}.`;
19 | }
20 | }
21 |
22 | it("should observe properties", () => {
23 | const person = new Person(32, "Alice");
24 | const { observables, proxy } = observe(person);
25 | const values: (number | string)[] = [];
26 | observables.age.subscribe((value) => values.push(value));
27 | observables.name.subscribe((value) => values.push(value));
28 | expect(values).to.deep.equal([32, "Alice"]);
29 | proxy.age = 42;
30 | proxy.name = "Bob";
31 | expect(values).to.deep.equal([32, "Alice", 42, "Bob"]);
32 | });
33 |
34 | it("should observe functions", () => {
35 | const person = new Person(32, "Alice");
36 | const { observables, proxy } = observe(person);
37 | const calls: any[][] = [];
38 | observables.greet.subscribe((args) => calls.push(args));
39 | expect(calls).to.deep.equal([]);
40 | proxy.greet("Hi");
41 | expect(calls).to.deep.equal([["Hi"]]);
42 | });
43 |
44 | it("should replay properties", () => {
45 | const person = new Person(32, "Alice");
46 | const { observables, proxy } = observe(person);
47 | proxy.age = 42;
48 | proxy.name = "Bob";
49 | const values: (number | string)[] = [];
50 | observables.age.subscribe((value) => values.push(value));
51 | observables.name.subscribe((value) => values.push(value));
52 | expect(values).to.deep.equal([42, "Bob"]);
53 | });
54 |
55 | it("should not replay functions", () => {
56 | const person = new Person(32, "Alice");
57 | const { observables, proxy } = observe(person);
58 | proxy.greet("Hi");
59 | const calls: any[][] = [];
60 | observables.greet.subscribe((args) => calls.push(args));
61 | expect(calls).to.deep.equal([]);
62 | });
63 |
64 | it("should support symbols", () => {
65 | const person = new Person(32, "Alice");
66 | person[job] = "engineer";
67 | const { observables } = observe(person);
68 | observables[job].subscribe((value) => expect(value).to.equal("engineer"));
69 | });
70 |
71 | it("should return the same function for each property get", () => {
72 | const person = new Person(32, "Alice");
73 | const first = person.greet;
74 | const second = person.greet;
75 | expect(first).to.equal(second);
76 | });
77 | });
78 |
79 | describe("inside a constructor", () => {
80 | class Person {
81 | age$: Observable;
82 | name$: Observable;
83 | greet$: Observable<[string]>;
84 | constructor(public age: number, public name: string) {
85 | const { observables, proxy } = observe(this as Person);
86 | this.age$ = observables.age;
87 | this.name$ = observables.name;
88 | this.greet$ = observables.greet;
89 | return proxy;
90 | }
91 | greet(greeting: string): string {
92 | return `${greeting}, ${this.name}.`;
93 | }
94 | }
95 |
96 | it("should observe properties", () => {
97 | const person = new Person(32, "Alice");
98 | expect(person).to.have.property("age$");
99 | expect(person).to.have.property("name$");
100 | const values: (number | string)[] = [];
101 | person.age$.subscribe((value) => values.push(value));
102 | person.name$.subscribe((value) => values.push(value));
103 | expect(values).to.deep.equal([32, "Alice"]);
104 | person.age = 42;
105 | person.name = "Bob";
106 | expect(values).to.deep.equal([32, "Alice", 42, "Bob"]);
107 | });
108 |
109 | it("should observe functions", () => {
110 | const person = new Person(32, "Alice");
111 | expect(person).to.have.property("greet$");
112 | const calls: any[][] = [];
113 | person.greet$.subscribe((args) => calls.push(args));
114 | expect(calls).to.deep.equal([]);
115 | person.greet("Hi");
116 | expect(calls).to.deep.equal([["Hi"]]);
117 | });
118 | });
119 |
120 | describe("added callbacks", () => {
121 | class Component {
122 | onDestroy$: Observable<[]>;
123 | onInit$: Observable<[]>;
124 | constructor() {
125 | const { observables, proxy } = observe(this as Component, {
126 | onDestroy: callback<() => void>(),
127 | onInit: callback<() => void>(),
128 | });
129 | this.onDestroy$ = observables.onDestroy;
130 | this.onInit$ = observables.onInit;
131 | return proxy;
132 | }
133 | }
134 |
135 | it("should observe added methods", () => {
136 | const component = new Component();
137 |
138 | expect(Object.getOwnPropertyNames(component)).to.include("onInit");
139 | expect(component.hasOwnProperty("onInit")).to.be.true;
140 | expect("onInit" in component).to.be.true;
141 | expect(component).to.have.property("onInit");
142 |
143 | expect(Object.getOwnPropertyNames(component)).to.include("onDestroy");
144 | expect(component.hasOwnProperty("onDestroy")).to.be.true;
145 | expect("onDestroy" in component).to.be.true;
146 | expect(component).to.have.property("onDestroy");
147 |
148 | let initialized = false;
149 | let destroyed = false;
150 | component.onInit$.subscribe(() => (initialized = true));
151 | component.onDestroy$.subscribe(() => (destroyed = true));
152 | component["onInit"]();
153 | component["onDestroy"]();
154 | expect(initialized).to.be.true;
155 | expect(destroyed).to.be.true;
156 | });
157 |
158 | it("should return the same function for each property get", () => {
159 | const component = new Component();
160 | const first = component["onInit"];
161 | const second = component["onInit"];
162 | expect(first).to.equal(second);
163 | });
164 |
165 | it("should not override instance methods", () => {
166 | const instance = {
167 | init() {
168 | /*tslint:disable-next-line:no-invalid-this*/
169 | this.initialized = true;
170 | },
171 | initialized: false,
172 | };
173 | const { observables, proxy } = observe(instance, {
174 | init: callback(),
175 | });
176 | let initialized = false;
177 | observables.init.subscribe(() => (initialized = true));
178 | proxy.init();
179 | expect(instance).to.have.property("initialized", true);
180 | expect(initialized).to.be.true;
181 | });
182 | });
183 | });
184 |
--------------------------------------------------------------------------------
/source/observe.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license Use of this source code is governed by an MIT-style license that
3 | * can be found in the LICENSE file at https://github.com/cartant/rxjs-observe
4 | */
5 |
6 | import { BehaviorSubject, Observable, Subject } from "rxjs";
7 |
8 | export type Observables = {
9 | [K in keyof T]: T[K] extends (...args: infer U) => any
10 | ? Observable
11 | : Observable;
12 | } &
13 | {
14 | [K in keyof C]: C[K] extends (...args: infer U) => any
15 | ? Observable
16 | : Observable;
17 | };
18 |
19 | export function observe(
20 | instance: T,
21 | callbacks?: C
22 | ): {
23 | observables: Observables;
24 | proxy: T & C;
25 | } {
26 | const fallbacks: {} = callbacks || {};
27 | const functions = new Map();
28 | const subjects = new Map>();
29 | const proxy = new Proxy(instance, {
30 | get(target: any, name: string | symbol) {
31 | const fallbackValue = fallbacks[name];
32 | const targetValue = target[name];
33 | let value = fallbackValue && !targetValue ? fallbackValue : targetValue;
34 | if (typeof value === "function") {
35 | const functionValue = value;
36 | let functionWrapper = functions.get(functionValue);
37 | if (!functionWrapper) {
38 | functionWrapper = function (this: any, ...args: any[]): any {
39 | const result = functionValue.apply(this, args);
40 | const subject = subjects.get(name);
41 | if (subject) {
42 | subject.next(args);
43 | }
44 | return result;
45 | };
46 | functions.set(functionValue, functionWrapper);
47 | }
48 | value = functionWrapper;
49 | }
50 | return value;
51 | },
52 | getOwnPropertyDescriptor(target: any, name: string | symbol) {
53 | return (
54 | Object.getOwnPropertyDescriptor(target, name) ||
55 | Object.getOwnPropertyDescriptor(fallbacks, name)
56 | );
57 | },
58 | has(target: any, name: string | symbol) {
59 | return name in target || name in fallbacks;
60 | },
61 | ownKeys(target: any) {
62 | return [...Reflect.ownKeys(target), ...Reflect.ownKeys(fallbacks)];
63 | },
64 | set(target: any, name: string | symbol, value: any) {
65 | target[name] = value;
66 | const subject = subjects.get(name);
67 | if (subject) {
68 | subject.next(value);
69 | }
70 | return true;
71 | },
72 | });
73 | return {
74 | observables: new Proxy(
75 | {},
76 | {
77 | get(target: any, name: string | symbol): any {
78 | let subject = subjects.get(name);
79 | if (!subject) {
80 | subject =
81 | typeof instance[name] === "function" ||
82 | typeof fallbacks[name] === "function"
83 | ? new Subject()
84 | : new BehaviorSubject(instance[name]);
85 | subjects.set(name, subject);
86 | }
87 | return subject.asObservable();
88 | },
89 | }
90 | ),
91 | proxy,
92 | };
93 | }
94 |
95 | export function callback(): T {
96 | return (() => {}) as any;
97 | }
98 |
--------------------------------------------------------------------------------
/tsconfig-dist-cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "module": "commonjs",
5 | "outDir": "dist",
6 | "target": "es5"
7 | },
8 | "extends": "./tsconfig-dist.json"
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig-dist-esm2015.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "module": "es2015",
5 | "outDir": "dist/esm2015",
6 | "target": "es2015"
7 | },
8 | "extends": "./tsconfig-dist.json"
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig-dist-esm5.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "module": "es2015",
5 | "outDir": "dist/esm5",
6 | "target": "es5"
7 | },
8 | "extends": "./tsconfig-dist.json"
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig-dist.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": [
3 | "source/**/*-spec.ts"
4 | ],
5 | "extends": "./tsconfig.json"
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "importHelpers": false,
5 | "lib": ["es2017"],
6 | "module": "commonjs",
7 | "moduleResolution": "node",
8 | "noEmitHelpers": false,
9 | "noImplicitAny": true,
10 | "outDir": "build",
11 | "removeComments": true,
12 | "skipLibCheck": true,
13 | "sourceMap": false,
14 | "strict": true,
15 | "suppressImplicitAnyIndexErrors": true,
16 | "target": "es2015"
17 | },
18 | "exclude": [],
19 | "include": [
20 | "source/**/*.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "@cartant/tslint-config",
5 | "@cartant/tslint-config-etc",
6 | "@cartant/tslint-config-rxjs"
7 | ],
8 | "rules": {
9 | "rxjs-no-create": {
10 | "severity": "off"
11 | },
12 | "typedef": {
13 | "options": [
14 | "parameter",
15 | "property-declaration"
16 | ],
17 | "severity": "error"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const path = require("path");
4 | const webpack = require("webpack");
5 | const webpackRxjsExternals = require("webpack-rxjs-externals");
6 |
7 | module.exports = env => {
8 | let filename = "rxjs-observe.umd.js";
9 | let mode = "development";
10 | if (env && env.production) {
11 | filename = "rxjs-observe.min.umd.js";
12 | mode = "production";
13 | }
14 | return {
15 | context: path.join(__dirname, "./"),
16 | entry: {
17 | index: "./source/index.ts"
18 | },
19 | externals: webpackRxjsExternals(),
20 | mode,
21 | module: {
22 | rules: [
23 | {
24 | test: /\.ts$/,
25 | use: {
26 | loader: "ts-loader",
27 | options: {
28 | compilerOptions: {
29 | declaration: false
30 | },
31 | configFile: "tsconfig-dist-cjs.json"
32 | }
33 | }
34 | }
35 | ]
36 | },
37 | output: {
38 | filename,
39 | library: "rxjsObserve",
40 | libraryTarget: "umd",
41 | path: path.resolve(__dirname, "./bundles")
42 | },
43 | resolve: {
44 | extensions: [".ts", ".js"]
45 | }
46 | };
47 | };
48 |
--------------------------------------------------------------------------------
/webpack.config.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const path = require("path");
4 | const webpack = require("webpack");
5 |
6 | module.exports = env => {
7 | return {
8 | context: path.join(__dirname, "./"),
9 | mode: "development",
10 | module: {
11 | rules: [
12 | {
13 | test: /\.ts$/,
14 | use: {
15 | loader: "ts-loader",
16 | options: {
17 | compilerOptions: {
18 | declaration: false
19 | },
20 | configFile: "tsconfig.json"
21 | }
22 | }
23 | }
24 | ]
25 | },
26 | resolve: {
27 | extensions: [".ts", ".js"]
28 | }
29 | };
30 | };
31 |
--------------------------------------------------------------------------------