├── docs ├── index.html ├── images │ ├── logitech.png │ ├── material-canvas.png │ ├── ribbon-canvas.png │ ├── ribbon-printing.png │ └── material-printing.png ├── printing.md ├── shortcuts.md ├── logitech.md └── features.md ├── .travis.yml ├── .gitignore ├── pictures ├── author.png ├── 2requests.png ├── rxjs-logo.png └── qr_code_github.jpg ├── .editorconfig ├── package.json ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── custom ├── styles.css └── scripts.js ├── gulpfile.js └── index.html /docs/index.html: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | prepared 4 | archive.zip 5 | .DS_Store 6 | .publish 7 | -------------------------------------------------------------------------------- /pictures/author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/pictures/author.png -------------------------------------------------------------------------------- /pictures/2requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/pictures/2requests.png -------------------------------------------------------------------------------- /pictures/rxjs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/pictures/rxjs-logo.png -------------------------------------------------------------------------------- /docs/images/logitech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/docs/images/logitech.png -------------------------------------------------------------------------------- /pictures/qr_code_github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/pictures/qr_code_github.jpg -------------------------------------------------------------------------------- /docs/images/material-canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/docs/images/material-canvas.png -------------------------------------------------------------------------------- /docs/images/ribbon-canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/docs/images/ribbon-canvas.png -------------------------------------------------------------------------------- /docs/images/ribbon-printing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/docs/images/ribbon-printing.png -------------------------------------------------------------------------------- /docs/images/material-printing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalexeev239/rxjs-subjects/HEAD/docs/images/material-printing.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | 12 | [{bower.json,package.json,.travis.yml}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /docs/printing.md: -------------------------------------------------------------------------------- 1 | # Printing 2 | 3 | You can export your presentation to PDF by sending it to print in list mode in Chrome and Opera desktop browsers. 4 | 5 | 1. Press `Cmd P` or `Ctrl P` 6 | 2. Select PDF in targets list 7 | 3. Save resulted file 8 | 9 | ![Printing dialog](images/ribbon-printing.png) 10 | 11 | ## Other options 12 | 13 | You could also try [Prince](http://princexml.com), [wkhtmltopdf](http://code.google.com/p/wkhtmltopdf) and such console utilities for printing, although not complete modern CSS features support and complicated configuration make them less convenient. 14 | -------------------------------------------------------------------------------- /docs/shortcuts.md: -------------------------------------------------------------------------------- 1 | # Keyboard Shortcuts 2 | 3 | You can navigate between slides, start and stop presentation using keyboard shortcuts. 4 | 5 | - [Start and Stop](#start-and-stop) 6 | - [Forward](#forward) 7 | - [Backward](#backward) 8 | - [First and Last](#first-and-last) 9 | 10 | ## Start and Stop 11 | 12 | - Start from the current slide: `Cmd Enter`, `Shift F5`, `Cmd Option P` 13 | - Start from the first slide `Cmd Shift Enter` 14 | - Stop: `Esc` key 15 | 16 | ## Forward 17 | 18 | - `Right` and `Down` arrows 19 | - `Page Down` key or `Fn Down` on Mac 20 | - `N` for “next”, `J` or `L` keys 21 | - `Enter` key 22 | - `Space` key during presentation 23 | 24 | ## Backward 25 | 26 | - `Left` and `Up` arrows 27 | - `Page Up` key or `Fn Up` on Mac 28 | - `P` for “previous”, `K` or `H` keys, like in vim 29 | - `Shift Enter` key 30 | - `Shift Space` key during presentation 31 | 32 | ## First and Last 33 | 34 | - First slide: `Home` key or `Fn Left` on Mac 35 | - Last slide: `End` key or `Fn Right` on Mac 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shower", 3 | "description": "RxJS lecture powered by shower", 4 | "version": "2.3.0", 5 | "author": { 6 | "name": "Amdrey Alexeev", 7 | "url": "http://aalexeev.ru" 8 | }, 9 | "dependencies": { 10 | "rxjs": "^5.5.6", 11 | "shower-core": "^2.1.0", 12 | "tinkoff-shower": "git+git@github.com:lekzd/tinkoff-shower.git" 13 | }, 14 | "devDependencies": { 15 | "browser-sync": "^2.18.12", 16 | "del": "^3.0.0", 17 | "fs": "0.0.2", 18 | "gulp": "^3.9.1", 19 | "gulp-gh-pages": "^0.5.4", 20 | "gulp-rename": "^1.2.2", 21 | "gulp-replace": "^0.6.1", 22 | "gulp-rsync": "0.0.8", 23 | "gulp-zip": "^4.0.0", 24 | "merge-stream": "^1.0.0", 25 | "path-exists-cli": "^1.0.0", 26 | "run-sequence": "^2.1.0" 27 | }, 28 | "scripts": { 29 | "start": "gulp", 30 | "prepare": "gulp prepare", 31 | "archive": "gulp archive", 32 | "publish": "gulp publish", 33 | "test": "npm run prepare && ls prepared && npm run archive && path-exists archive.zip" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/logitech.md: -------------------------------------------------------------------------------- 1 | # Logitech Spotlight 2 | 3 | There’s a brand new [Logitech Spotlight](http://www.logitech.com/en-us/product/spotlight-presentation-remote) presentation remote, which is already fully compatible with Shower: you can switch between slides, hover and click your presentation. But you can make the most of it by enabling two additional shortcuts in Logitech Presentation application, available for macOS and Windows. 4 | 5 | 1. [Download](http://support.logitech.com/en_us/software/logi-presentation) and install an app. 6 | 2. Go to the main menu and locate additional shortcuts options. 7 | 3. Change _Hold Next Button_ to `Cmd Enter` for macOS or `Shift F5` for Windows (just press it). 8 | 4. Change _Hold Back Button_ to `Esc` (just press it). 9 | 10 | Logitech Presenter application 11 | 12 | You might have to enable accessibility access on macOS in order to use this features. App will ask you once it’s needed. 13 | 14 | Now when all is set you can: 15 | 16 | - Start your presentation by holding next button: from the first slide (if none selected) or from the current slide (if there’s one). 17 | - Exit full screen mode by holding back button. It might be useful when you need to find a certain slide and navigate the list. 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Shower 2 | 3 | You’re always welcome to contribute! Before contributing to Shower, please read through unified [issues list](https://github.com/shower/shower/issues) to see open bugs and feature requests. If you have any feature to add to Shower or found a bug and want to fix it, please make sure you [file an issue](https://github.com/shower/shower/issues/new) first. 4 | 5 | ## Process 6 | 7 | Fork needed repository, create a branch for each feature or issue you’re dealing with and start making changes. Make sure you code doesn’t break tests if there are any. Once you’re finished, send pull request back to original repository: one feature or bug fix — one pull request, please don’t combine non-relevant changes into one pull request. Supply clear description of your changes and a link to existing issue. 8 | 9 | ## Code style 10 | 11 | Please preserve existing code style while contributing to Shower and be ready for code review by Shower maintainers. It’s strongly recommended to install [EditorConfig](http://editorconfig.org) extension to your editor and validate your JavaScript changes using [JSHint](http://jshint.com/). 12 | 13 | ## Language 14 | 15 | English is the main language for Shower project. All discussions and commit messages should be in English, no matter if it’s good or bad. The second language of Shower is Russian. Official Shower themes are always compatible with Cyrillic and Russian typography. All documentation to Shower is always localized to Russian. 16 | 17 | --- 18 | Licensed under [MIT License](LICENSE.md). 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright © 2010–2015 Vadim Makeev, http://pepelsbey.net/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | --- 12 | 13 | # Лицензия MIT 14 | 15 | Copyright © 2010–2015 Вадим Макеев, http://pepelsbey.net/ 16 | 17 | Данная лицензия разрешает лицам, получившим копию данного программного обеспечения и сопутствующей документации (в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно использовать Программное Обеспечение без ограничений, включая неограниченное право на использование, копирование, изменение, добавление, публикацию, распространение, сублицензирование и/или продажу копий Программного Обеспечения, также как и лицам, которым предоставляется данное Программное Обеспечение, при соблюдении следующих условий: 18 | 19 | Указанное выше уведомление об авторском праве и данные условия должны быть включены во все копии или значимые части данного Программного Обеспечения. 20 | 21 | ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxJS Subjects 2 | 3 | ## **Важно: пожалуйста, оцените выступлениe** 4 | 5 | Анкета на 5 минут: [https://goo.gl/forms/kkG0QSOviO6yOvRn1](https://goo.gl/forms/kkG0QSOviO6yOvRn1) 6 | 7 | 8 | ## Материалы 9 | 10 | 1. [Видео](https://youtu.be/KFV7IgCU68Q?t=4m20s) 11 | 2. Презентация [https://aalexeev239.github.io/rxjs-subjects/](https://aalexeev239.github.io/rxjs-subjects/) 12 | 3. Пример кода [https://github.com/aalexeev239/rxjs-subjects-example](https://github.com/aalexeev239/rxjs-subjects-example) 13 | 14 | ## Cсылки 15 | 16 | ### Observable 17 | 18 | - Введение в документацию [http://reactivex.io/rxjs/manual/overview.html](http://reactivex.io/rxjs/manual/overview.html) 19 | - Понимание паттернов, из которых состоит Observable [http://anasfirdousi.com/understanding-observable-patterns-behind-observables-rxjs-rx.html](http://anasfirdousi.com/understanding-observable-patterns-behind-observables-rxjs-rx.html) 20 | 21 | ### Subjects и multicast 22 | - Типы subject-ов [https://medium.com/@poudanen/%D0%BF%D0%BE%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5-rxjava-subject-publish-replay-behavior-%D0%B8-async-subject-35ad50cd1064](https://medium.com/@poudanen/%D0%BF%D0%BE%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5-rxjava-subject-publish-replay-behavior-%D0%B8-async-subject-35ad50cd1064) 23 | - Hot vs Cold Observables [https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339](https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339) 24 | - RxJS: Understanding the publish and share Operators [https://blog.angularindepth.com/rxjs-understanding-the-publish-and-share-operators-16ea2f446635](https://blog.angularindepth.com/rxjs-understanding-the-publish-and-share-operators-16ea2f446635) 25 | - Документация multicast оператора [http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-multicast](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-multicast) 26 | 27 | ### Egghead 28 | 29 | - Use Higher Order Observables in RxJS Effectively [https://egghead.io/courses/use-higher-order-observables-in-rxjs-effectively](https://egghead.io/courses/use-higher-order-observables-in-rxjs-effectively) 30 | - Save time avoiding common mistakes using RxJS [https://egghead.io/courses/save-time-avoiding-common-mistakes-using-rxjs](https://egghead.io/courses/save-time-avoiding-common-mistakes-using-rxjs) 31 | - RxJS Subjects and Multicasting Operators [https://egghead.io/courses/rxjs-subjects-and-multicasting-operators](https://egghead.io/courses/rxjs-subjects-and-multicasting-operators) 32 | 33 | ### Не попали в категории 34 | - Rx Visualizer – песочница по визуализации Observable [https://rxviz.com/](https://rxviz.com/) 35 | - RxJS: Don’t Unsubscribe [https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87](https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87) 36 | -------------------------------------------------------------------------------- /custom/styles.css: -------------------------------------------------------------------------------- 1 | .shower { 2 | font-size: 20px; 3 | } 4 | 5 | .slide .shout { 6 | font-size: 100px; 7 | } 8 | 9 | .slide small { 10 | margin-top: 10px; 11 | display: block; 12 | font-size: 14px; 13 | } 14 | 15 | .slide p { 16 | font-size: 18px; 17 | margin: 0; 18 | } 19 | 20 | .slide .slide-header { 21 | margin-bottom: 30px; 22 | } 23 | 24 | .slide-description { 25 | min-height: 70px; 26 | position: relative; 27 | padding-right: 35px; 28 | } 29 | 30 | .slide-description.big { 31 | min-height: 175px; 32 | } 33 | 34 | .intro { 35 | text-align: center; 36 | } 37 | 38 | .playground { 39 | padding: 10px 0; 40 | min-height: 10px; 41 | } 42 | 43 | .text-hot { 44 | color: #C0504D; 45 | } 46 | 47 | .text-cold { 48 | color: #4F81BD; 49 | } 50 | 51 | .text-muted { 52 | color: #777; 53 | } 54 | 55 | .relative-container { 56 | position: relative; 57 | } 58 | 59 | .relative-image { 60 | position: absolute; 61 | box-shadow: 0 0 0 2px #434343; 62 | } 63 | 64 | .code-special { 65 | position: absolute; 66 | top: 0; 67 | right: 0; 68 | font-size: 11px; 69 | padding: 10px; 70 | padding-left: 35px; 71 | padding-bottom: 0; 72 | border: 1px solid #787878; 73 | opacity: .7; 74 | } 75 | 76 | .btn { 77 | font-size: 20px; 78 | padding: .3em .7em; 79 | background: #ffcf0f; 80 | color: #000000; 81 | border: none; 82 | cursor: pointer; 83 | transition: background .2s, box-shadow .2s; 84 | } 85 | 86 | .btn:hover { 87 | background: #f5c400; 88 | } 89 | 90 | .btn:active { 91 | background: #dbaf00; 92 | box-shadow: inset 0 0 .1em rgba(0, 0, 0, 0.5); 93 | } 94 | 95 | .btn:focus { 96 | outline: none; 97 | } 98 | 99 | .btn[disabled] { 100 | background: #c29b00; 101 | opacity: .7; 102 | cursor: default; 103 | } 104 | 105 | .btn.hidden { 106 | display: none; 107 | } 108 | 109 | .btn-play { 110 | position: absolute; 111 | right: 0; 112 | top: 0; 113 | } 114 | 115 | .timeline { 116 | color: red; 117 | margin: 15px 0; 118 | height: 4px; 119 | background-color: currentColor; 120 | position: relative; 121 | z-index: 0; 122 | transition: width linear 6s; 123 | width: 0; 124 | } 125 | 126 | .hidden { 127 | display: none !important; 128 | } 129 | 130 | .button { 131 | margin: 20px 0; 132 | display: inline-block; 133 | padding: 8px 16px; 134 | min-width: 120px; 135 | color: #009D91; 136 | border: 4px solid; 137 | font-size: 20px; 138 | font-weight: bold; 139 | text-transform: uppercase; 140 | border-radius: 8px; 141 | } 142 | 143 | .button:active { 144 | color: #FB000D; 145 | } 146 | 147 | .button:focus { 148 | outline: none; 149 | } 150 | 151 | .button .button-text, 152 | .button:active .button-text-active { 153 | display: inline; 154 | } 155 | 156 | .button .button-text-active, 157 | .button:active .button-text { 158 | display: none; 159 | } 160 | 161 | .timeline + .timeline { 162 | margin-top: 40px; 163 | } 164 | 165 | .entry { 166 | background-color: #ffffff; 167 | position: absolute; 168 | top: -14px; 169 | margin-left: -12px; 170 | left: 0; 171 | display: inline-block; 172 | vertical-align: top; 173 | box-sizing: border-box; 174 | width: 30px; 175 | height: 30px; 176 | line-height: 30px; 177 | font-size: 13px; 178 | box-sizing: border-box; 179 | box-shadow: 0 0 0 2px currentColor; 180 | text-align: center; 181 | border-radius: 50%; 182 | z-index: 1; 183 | } 184 | 185 | .entry.shift { 186 | top: -6px; 187 | z-index: 2; 188 | background: rgba(255, 255, 255, 0.6); 189 | } 190 | 191 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const del = require('del'); 2 | const fs = require('fs'); 3 | const gulp = require('gulp'); 4 | const merge = require('merge-stream'); 5 | const rename = require('gulp-rename'); 6 | const replace = require('gulp-replace'); 7 | const rsync = require('gulp-rsync'); 8 | const sequence = require('run-sequence'); 9 | const zip = require('gulp-zip'); 10 | const pages = require('gulp-gh-pages'); 11 | const browserSync = require('browser-sync').create(); 12 | 13 | gulp.task('prepare', () => { 14 | 15 | const shower = gulp.src([ 16 | '**', 17 | '!docs{,/**}', 18 | '!node_modules{,/**}', 19 | '!prepared{,/**}', 20 | '!utils{,/**}', 21 | '!CONTRIBUTING.md', 22 | '!LICENSE.md', 23 | '!README.md', 24 | '!gulpfile.js', 25 | '!package.json', 26 | '!package-lock.json' 27 | ]) 28 | .pipe(replace( 29 | /()/g, 30 | '$1shower/themes/tinkoff$3/$4', {skipBinary: true} 31 | )) 32 | .pipe(replace( 33 | /()/g, 34 | '$1shower/themes/$3/$4', {skipBinary: true} 35 | )) 36 | .pipe(replace( 37 | /( 105 | 106 | 107 |
108 |

Observable

109 |
110 |

111 | Observable 112 | – наблюдаемая сущность, доставляет изменения подписчикам. 113 |

114 |
115 | 116 |
 117 | 				const stream$ = Rx.Observable.create((observer) => {
 118 | 				    observer.next(1);
 119 | 				    setTimeout(() => { observer.next(2);},    1000);
 120 | 				    setTimeout(() => { observer.next(3);},    2000);
 121 | 				    setTimeout(() => { observer.complete();}, 3000);
 122 | 				});
 123 | 				 
 124 | 				const subscribtion = stream$.subscribe({
 125 | 				    next: (v) => logger.textContent += ' ' + v,
 126 | 				    complete: () => logger.textContent += ' complete!'
 127 | 				});
 128 | 		
129 |
130 |
131 | 132 |
133 |

Observable

134 |
135 |

Функция создания определяет, наблюдатели (подписчики) будет получать значения.

136 |
137 | 138 |
 139 | 			const stream$ = Rx.Observable.create((observer) => {
 140 | 			    observer.next(1);
 141 | 			    setTimeout(() => { observer.next(2);},    1000);
 142 | 			    setTimeout(() => { observer.next(3);},    2000);
 143 | 			    setTimeout(() => { observer.complete();}, 3000);
 144 | 				});
 145 | 				 
 146 | 				const subscribtion = stream$.subscribe({
 147 | 			    next: (v) => logger.textContent += ' ' + v,
 148 | 			    complete: () => logger.textContent += ' complete!'
 149 | 				});
 150 | 		
151 |
152 |
153 | 154 |
155 |

Observable

156 |
157 |

158 | Observer 159 | – сам наблюдатель, состоящий из функций-обработчиков next/error/complete 160 |

161 |

Их можно перечислить через запятую или задать как объект.

162 |
163 | 164 |
 165 | 			const stream$ = Rx.Observable.create((observer) => {
 166 | 				    observer.next(1);
 167 | 				    setTimeout(() => { observer.next(2);},    1000);
 168 | 				    setTimeout(() => { observer.next(3);},    2000);
 169 | 				    setTimeout(() => { observer.complete();}, 3000);
 170 | 				});
 171 | 				 
 172 | 				const subscribtion = stream$.subscribe({
 173 | 			    next: (v) => logger.textContent += ' ' + v,
 174 | 			    complete: () => logger.textContent += ' complete!'
 175 | 				});
 176 | 		
177 |
178 |
179 | 180 | 181 |
182 |

Observable

183 |
184 |

185 | Subscribtion 186 | – подписка. Обычно используется для вызова 187 | unsubscribe 188 | . 189 |

190 |
191 | 192 |
 193 | 				const stream$ = Rx.Observable.create((observer) => {
 194 | 				    observer.next(1);
 195 | 				    setTimeout(() => { observer.next(2);},    1000);
 196 | 				    setTimeout(() => { observer.next(3);},    2000);
 197 | 				    setTimeout(() => { observer.complete();}, 3000);
 198 | 				});
 199 | 				 
 200 | 			const subscribtion = stream$.subscribe({
 201 | 				    next: (v) => logger.textContent += ' ' + v,
 202 | 				    complete: () => logger.textContent += ' complete!'
 203 | 				});
 204 | 			subscribtion.unsubscribe();
 205 | 		
206 |
207 |
208 | 209 |
210 |

Observable

211 |
212 |

На каждую подписку создается отдельный поток значений

213 |
214 | 215 |
216 | 217 |
 218 | 				const stream$ = Rx.Observable.interval(500)
 219 | 				    .take(5)
 220 | 				    .map(() => Math.random().toString().substr(2, 3)); // 0.12345 => 123
 221 | 				 
 222 | 				stream$.subscribe(/* ... */);
 223 | 				stream$.subscribe(/* ... */);
 224 | 				setTimeout(() => {
 225 | 				    stream$.subscribe(/* ... */);
 226 | 				}, 800);
 227 | 		
228 |
229 | 243 |
244 | 245 |
246 |

Observable

247 |
248 |

Однако есть Observable, распределяюшие события по всем подписчикам

249 | 250 |
251 | 252 |
253 | 254 |
 255 | 				const buttonElement = document.getElementById('click-button');
 256 | 				const START = new Date();
 257 | 				const stream$ = Rx.Observable.fromEvent(buttonElement, 'click')
 258 | 				    .map(() => ((new Date() - START) / 1000).toFixed(2));
 259 | 				 
 260 | 				stream$.subscribe(/* ... */);
 261 | 				setTimeout(() => {stream$.subscribe(/* ... */);}, 1000);
 262 | 				setTimeout(() => {stream$.subscribe(/* ... */);}, 2000);
 263 | 		
264 |
265 | 289 |
290 | 291 | 292 |
293 |

Observable

294 |
295 |

Эти 2 типа Observable называются холодными и горячими. 296 |

297 |
298 |

Холодные создают независимые потоки под каждую подписку.

299 |
300 |

Горячие разделяют поток друг с другом

301 |
302 |
303 | 304 |
305 |

Observable

306 |
307 |

Ну и что?

308 |

Иногда надо подогреть холодный 309 | Observable. Например, чтобы не делать 2 запроса:

310 |
311 |
312 |
313 | 314 |
 315 | 				const request$ = this.http.get('https://api.github.com/search/repositories?q=rxjs');
 316 | 				 
 317 | 				request$
 318 | 				 .map(data => data.items.map(repo => repo.name))
 319 | 				 .subscribe(v => console.log(v));
 320 | 				 
 321 | 				request$
 322 | 				 .map(data => data.items.filter(repo => repo.name === 'rxjs')[0])
 323 | 				 .map(repo => repo.score)
 324 | 				 .subscribe(v => console.log(v));
 325 | 
326 |
327 |
328 |
329 | 330 |
331 |

Observable

332 |
333 |

Ну и что?

334 |

Иногда надо подогреть холодный 335 | Observable. Например, чтобы не делать 2 запроса:

336 |
337 |
338 |
339 | 340 |
 341 | 				const request$ = this.http.get('https://api.github.com/search/repositories?q=rxjs');
 342 | 				 
 343 | 				request$
 344 | 				 .map(data => data.items.map(repo => repo.name))
 345 | 				 .subscribe(v => console.log(v));
 346 | 				 
 347 | 				request$
 348 | 				 .map(data => data.items.filter(repo => repo.name === 'rxjs')[0])
 349 | 				 .map(repo => repo.score)
 350 | 				 .subscribe(v => console.log(v));
 351 | 		
352 |
353 | 2 requests 355 |
356 |
357 | 358 |
359 |

SUBJECT

360 |
361 | 362 |
363 |

Subject

364 |
365 |

Объединение Observable и Observer. Передает значение всем подписчикам

366 |
367 | 368 |
369 |
370 | 371 |
 372 | 				const subject = new Rx.Subject();
 373 | 				setSubjectTick(subject);
 374 | 				 
 375 | 				subject.subscribe();
 376 | 				setTimeout(() => subject.subscribe(/* ... */), 1000);
 377 | 				setTimeout(() => subject.subscribe(/* ... */), 2000);
 378 | 		
379 |
380 |
381 |
 382 | 					function setSubjectTick(subject) {
 383 | 					    subject.next(0);
 384 | 					    setTimeout(() => subject.next(1), 500);
 385 | 					    setTimeout(() => subject.next(2), 1000);
 386 | 					    setTimeout(() => subject.next(3), 1500);
 387 | 					    setTimeout(() => subject.next(4), 2000);
 388 | 					    setTimeout(() => subject.next(5), 2500);
 389 | 					    setTimeout(() => subject.complete(), 3000);
 390 | 					}
 391 | 
 392 | 			
393 |
394 |
395 | 408 |
409 | 410 |
411 |

Subject

412 |
413 |

Объединение 414 | Observable 415 | и Observer. Передает значение всем подписчикам 416 |

417 |
418 |
419 | 420 |
 421 | 				const subject = new Rx.Subject();
 422 | 				setSubjectTick(subject);
 423 | 				 
 424 | 			subject.subscribe();
 425 | 			setTimeout(() => subject.subscribe(/* ... */), 1000);
 426 | 			setTimeout(() => subject.subscribe(/* ... */), 2000);
 427 | 		
428 |
429 |
430 |
 431 | 					function setSubjectTick(subject) {
 432 | 					    subject.next(0);
 433 | 					    setTimeout(() => subject.next(1), 500);
 434 | 					    setTimeout(() => subject.next(2), 1000);
 435 | 					    setTimeout(() => subject.next(3), 1500);
 436 | 					    setTimeout(() => subject.next(4), 2000);
 437 | 					    setTimeout(() => subject.next(5), 2500);
 438 | 					    setTimeout(() => subject.complete(), 3000);
 439 | 					}
 440 | 
 441 | 			
442 |
443 |
444 |
445 | 446 |
447 |

Subject

448 |
449 |

Объединение 450 | Observable 451 | и 452 | Observer 453 | . Передает значение всем подписчикам 454 |

455 |
456 |
457 | 458 |
 459 | 				const subject = new Rx.Subject();
 460 | 				setSubjectTick(subject);
 461 | 				 
 462 | 			subject.subscribe();
 463 | 			setTimeout(() => subject.subscribe(/* ... */), 1000);
 464 | 			setTimeout(() => subject.subscribe(/* ... */), 2000);
 465 | 		
466 |
467 |
468 |
 469 | 					function setSubjectTick(subject) {
 470 | 				    subject.next(0);
 471 | 				    setTimeout(() => subject.next(1), 500);
 472 | 				    setTimeout(() => subject.next(2), 1000);
 473 | 				    setTimeout(() => subject.next(3), 1500);
 474 | 				    setTimeout(() => subject.next(4), 2000);
 475 | 				    setTimeout(() => subject.next(5), 2500);
 476 | 				    setTimeout(() => subject.complete(), 3000);
 477 | 					}
 478 | 
 479 | 			
480 |
481 |
482 |
483 | 484 |
485 |

Subject

486 |
487 |

Пример

488 |
489 | 490 |
 491 | 				const aalexeev = new Subject();
 492 | 				 
 493 | 				for (let i = 0; i < meetupListeners; i++) {
 494 | 				   meetupListeners[i] = aalexeev.subscribe(/* ... */);
 495 | 				}
 496 | 				 
 497 | 				aalexeev.next('Я рассказываю этот доклад');
 498 | 				aalexeev.next('Произношу какую-то мысль — передаю значение через .next');
 499 | 		
500 |
501 |
502 | 503 |
504 |

Subject

505 |
506 |

Пример

507 |
508 | 509 |
 510 | 				const aalexeev = new Subject();
 511 | 				 
 512 | 			for (let i = 0; i < meetupListeners; i++) {
 513 | 			   meetupListeners[i] = aalexeev.subscribe(/* ... */);
 514 | 			}
 515 | 				 
 516 | 				aalexeev.next('Я рассказываю этот доклад');
 517 | 				aalexeev.next('Произношу какую-то мысль — передаю значение через .next');
 518 | 		
519 |
520 |
521 | 522 |
523 |

Subject

524 |
525 |

Пример

526 |
527 | 528 |
 529 | 				const aalexeev = new Subject();
 530 | 				 
 531 | 				for (let i = 0; i < meetupListeners; i++) {
 532 | 				   meetupListeners[i] = aalexeev.subscribe(/* ... */);
 533 | 				}
 534 | 				 
 535 | 			aalexeev.next('Я рассказываю этот доклад');
 536 | 			aalexeev.next('Произношу какую-то мысль — передаю значение через .next');
 537 | 		
538 |
539 |
540 | 541 |
542 |

Subject

543 |
544 |

Пример

545 |
546 | 547 |
 548 | 				aalexeev.next('Если кто-то опоздал и пришел только сейчас');
 549 | 				aalexeev.next('он не получит все прошлые значения');
 550 | 				 
 551 | 			const newListener = aalexeev.subscribe(/* ... */);
 552 | 				meetupListeners.push(newListener);
 553 | 				 
 554 | 				aalexeev.next('но будет получать все будущие');
 555 | 		
556 |
557 |
558 | 559 |
560 |

Subject

561 |
562 |

Пример

563 |
564 | 565 |
 566 | 				const leaving = Math.floor(Math.random() * meetupListeners); // кто-то
 567 | 				meetupListeners[leaving].unsubscribe();
 568 | 				meetupListeners.splice(leaving, 1);
 569 | 				 
 570 | 				aalexeev.next('Кто только что ушел, не услышит самого главного');
 571 | 
 572 | 		
573 |
574 |
575 | 576 |
577 |

Subject

578 |
579 |

Пример

580 |
581 | 582 |
 583 | 				aalexeev.error('Внимание! В одном из помещений обнаружено задымление. Просьба покинуть помещение!')
 584 | 				 
 585 | 				aalexeev.next('Как же так!'); // это никто не услышит
 586 | 				/* __________________ */
 587 | 				aalexeev.next('Спасибо всем, кто пришел');
 588 | 				aalexeev.complete();
 589 | 				 
 590 | 				aalexeev.next('Кстати, а вот еще...'); // это никто не услышит
 591 | 		
592 |
593 |
594 | 595 |
596 |

Subject

597 |
598 |

Subject не завершает генерацию значений.

599 |
600 | 601 |
602 |
603 | 604 |
 605 | 				const subject = new Rx.Subject();
 606 | 				const subscribtion = subject.subscribe(/* ... */);
 607 | 				 
 608 | 				setSubjectTick(subject)
 609 | 				setTimeout(() => { subscribtion.unsubscribe(); }, 1000);
 610 | 				setTimeout(() => { subject.subscribe(/* ... */); }, 1500);
 611 | 		
612 |
613 |
614 |
 615 | 					function setSubjectTick(subject) {
 616 | 					    subject.next(0);
 617 | 					    setTimeout(() => subject.next(1), 500);
 618 | 					    setTimeout(() => subject.next(2), 1000);
 619 | 					    setTimeout(() => subject.next(3), 1500);
 620 | 					    setTimeout(() => subject.next(4), 2000);
 621 | 					    setTimeout(() => subject.next(5), 2500);
 622 | 					    setTimeout(() => subject.complete(), 3000);
 623 | 					}
 624 | 
 625 | 			
626 |
627 |
628 | 646 |
647 | 648 |
649 |

РАЗНОВИДНОСТИ SUBJECT

650 |
651 | 652 |
653 |

Behaviour Subject

654 |
655 |

Всегда хранит текущее значение. При инициализации требует начальное значение.

656 |

При подписке наблюдатель незамедлительно получит текущее значение.

657 |
658 | 659 |
660 |
661 | 662 |
 663 | 				const subject = new Rx.BehaviorSubject('abc');
 664 | 				const subscribtion = subject.subscribe(/* ... */);
 665 | 				setSubjectTick(subject);
 666 | 				 
 667 | 				setTimeout(() => {subscribtion.unsubscribe();}, 1000);
 668 | 				setTimeout(() => {subject.subscribe(/* ... */);}, 1200);
 669 | 		
670 |
671 |
672 |
 673 | 					function setSubjectTick(subject) {
 674 | 					    setTimeout(() => subject.next(1), 500);
 675 | 					    setTimeout(() => subject.next(2), 1000);
 676 | 					    setTimeout(() => subject.next(3), 1500);
 677 | 					    setTimeout(() => subject.next(4), 2000);
 678 | 					    setTimeout(() => subject.next(5), 2500);
 679 | 					    setTimeout(() => subject.complete(), 3000);
 680 | 					}
 681 | 			
682 |
683 |
684 | 702 |
703 | 704 |
705 |

Replay Subject

706 |
707 |

Cохраняет буфер произошедших событий.

708 |

При подписке наблюдатель незамедлительно получит текущий буфер.

709 |

Нет начального значения, как в Behaviour.

710 |
711 | 712 |
713 |
714 | 715 |
 716 | 				const subject = new Rx.ReplaySubject(2);
 717 | 				setSubjectTick(subject);
 718 | 				 
 719 | 				subject.subscribe();
 720 | 				 
 721 | 				setTimeout(() => {
 722 | 				   subject.subscribe(/* ... */);
 723 | 				}, 1200);
 724 | 
 725 | 		
726 |
727 |
728 |
 729 | 					function setSubjectTick(subject) {
 730 | 					    subject.next(1)
 731 | 					    setTimeout(() => subject.next(1), 500);
 732 | 					    setTimeout(() => subject.next(2), 1000);
 733 | 					    setTimeout(() => subject.next(3), 1500);
 734 | 					    setTimeout(() => subject.next(4), 2000);
 735 | 					    setTimeout(() => subject.next(5), 2500);
 736 | 					    setTimeout(() => subject.complete(), 3000);
 737 | 					}
 738 | 			
739 |
740 |
741 | 755 |
756 | 757 |
758 |

Async Subject

759 |
760 |

Передает только последнее значение и только после завершения.

761 |

Похож на Promise.

762 |
763 | 764 |
765 |
766 | 767 |
 768 | 				const subject = new Rx.AsyncSubject();
 769 | 				 
 770 | 				setSubjectTick(subject);
 771 | 				 
 772 | 				subject.subscribe(/* ... */);
 773 | 				setTimeout(() => { subject.subscribe(/* ... */); }, 1000);
 774 | 				setTimeout(() => { subject.subscribe(/* ... */); }, 4000);
 775 | 
 776 | 		
777 |
778 |
779 |
 780 | 					function setSubjectTick(subject) {
 781 | 					    setTimeout(() => subject.next(1), 500);
 782 | 					    setTimeout(() => subject.next(2), 1000);
 783 | 					    setTimeout(() => subject.next(3), 1500);
 784 | 					    setTimeout(() => subject.next(4), 2000);
 785 | 					    setTimeout(() => subject.next(5), 2500);
 786 | 					    setTimeout(() => subject.complete(), 3000);
 787 | 					}
 788 | 			
789 |
790 |
791 | 805 |
806 | 807 |
808 |

Итого

809 |
    810 |
  1. Subject
  2. 811 | 812 | 813 | 814 |
815 |
816 | 817 |
818 |

Subject

819 |
820 |

Вернемся к задаче разделения значений между несколькими подписчиками.

821 |

С помощью Subject-ов это можно решить вот так:

822 |
823 | 824 |
825 |
826 | 827 |
 828 | 			const subject = new Rx.Subject();
 829 | 				Rx.Observable.interval(500)
 830 | 				   .take(5)
 831 | 				   .map(() => Math.random().toString().substr(2, 3))
 832 | 			   .subscribe(subject);
 833 | 				 
 834 | 			subject.subscribe(/* ... */);
 835 | 			subject.subscribe(/* ... */);
 836 | 			setTimeout(() => {subject.subscribe(/* ... */);}, 800);
 837 | 		
838 |
839 |
840 | 856 |
857 | 858 |
859 |

Multicasting

860 |
861 |

Оператор multicast позволяет не выделять промежуточный Subject, а создать сразу “правильный” 862 | Observable.

863 |
864 | 865 |
866 |
867 | 868 |
 869 | 				const connectable$ = Rx.Observable.interval(500)
 870 | 				   .take(5)
 871 | 				   .map(() => Math.random().toString().substr(2, 3))
 872 | 				   .multicast(new Rx.Subject());
 873 | 				 
 874 | 				setTimeout(() => {connectable$.subscribe(/* ... */);}, 500);
 875 | 				setTimeout(() => {connectable$.subscribe(/* ... */);}, 1000);
 876 | 				setTimeout(() => {connectable$.subscribe(/* ... */);}, 1500);
 877 | 				connectable$.connect();
 878 | 
 879 | 		
880 |
881 |
882 | 899 |
900 | 901 |
902 |

Multicasting

903 |
904 |

multicast(new Rx.Subject()) -> publish()

905 |

multicast(new Rx.BehaviorSubject(value)) -> publishBehavior(value)

906 |

multicast(new Rx.ReplaySubject(buffer)) -> publishReplay(buffer)

907 |

multicast(new Rx.AsyncSubject()) -> publishLast()

908 |
909 |
910 | 911 |
912 |

Multicasting

913 |
914 |

Запустить получивщийся Observable нужно вызовом connect.

915 |
916 |
917 | 918 |
 919 | 				const connectable$ = Rx.Observable.interval(500)
 920 | 				   .take(5)
 921 | 				   .map(() => Math.random().toString().substr(2, 3))
 922 | 				   .publish();
 923 | 				 
 924 | 				setTimeout(() => {connectable$.subscribe(/* ... */);}, 500);
 925 | 				setTimeout(() => {connectable$.subscribe(/* ... */);}, 1000);
 926 | 				setTimeout(() => {connectable$.subscribe(/* ... */);}, 1500);
 927 | 				connectable$.connect();
 928 | 
 929 | 		
930 |
931 |
932 |
933 | 934 |
935 |

Multicasting

936 |
937 |

Запустить получивщийся Observable нужно вызовом connect.

938 |
939 | 940 |
941 |
942 | 943 |
 944 | 				const connectable$ = Rx.Observable.interval(500)
 945 | 				   .take(5)
 946 | 				   .map(() => Math.random().toString().substr(2, 3))
 947 | 				   .publish();
 948 | 				 
 949 | 				setTimeout(() => { connectable$.connect(); }, 800);
 950 | 				 
 951 | 				setTimeout(() => { connectable$.subscribe(/* ... */); }, 500);
 952 | 				setTimeout(() => { connectable$.subscribe(/* ... */); }, 1000);
 953 | 				setTimeout(() => { connectable$.subscribe(/* ... */); }, 1500);
 954 | 		
955 |
956 |
957 | 976 |
977 | 978 |
979 |

Multicasting

980 |
981 |

Что не так с этим кодом?

982 |
983 |
984 | 985 |
 986 | 				const connectable$ = Rx.Observable.interval(500)
 987 | 				   .take(5)
 988 | 				   .map(() => Math.random().toString().substr(2, 3))
 989 | 				   .publish();
 990 | 				 
 991 | 				setTimeout(() => { connectable$.connect(); }, 1000);
 992 | 		
993 |
994 |
995 |
996 | 997 |
998 |

Multicasting

999 |
1000 |

Используйте refCount для автоматического обновления подписки.

1001 |

0 -> 1 — старт генерации значений

1002 |

1 -> 0 — отмена

1003 |
1004 |
1005 | 1006 |
1007 | 				const stream$ = Rx.Observable.interval(TICK_MS)
1008 | 				   .take(5)
1009 | 				   .publish()
1010 | 				   .refCount();
1011 | 		
1012 |
1013 |
1014 |
1015 | 1016 |
1017 |

Multicasting

1018 |
1019 |

Используйте refCount для автоматического обновления подписки.

1020 |
1021 | 1022 |
1023 |
1024 | 1025 |
1026 | 				const stream$ = Rx.Observable.interval(TICK_MS)
1027 | 				   .take(5)
1028 | 				   .publish()
1029 | 				   .refCount();
1030 | 				const subscription1 = stream$.subscribe();
1031 | 				let subscription2;
1032 | 				 
1033 | 				setTimeout(() => {subscription2 = stream$.subscribe(); }, 1100);
1034 | 				setTimeout(() => {subscription1.unsubscribe()}, 1100);
1035 | 				setTimeout(() => {subscription2.unsubscribe()}, 2100);
1036 | 				setTimeout(() => {stream$.subscribe()}, 2100);
1037 | 
1038 | 		
1039 |
1040 |
1041 | 1063 |
1064 | 1065 |
1066 |

Multicasting

1067 |
1068 |

publish().refCount() -> share()

1069 |
1070 |
1071 | 1072 |
1073 | 				const stream$ = Rx.Observable.interval(TICK_MS)
1074 | 				   .take(5)
1075 | 				   // .publish()
1076 | 				   // .refCount();
1077 | 				   .share();
1078 | 		
1079 |
1080 |
1081 |
1082 | 1083 |
1084 |

Примеры

1085 |
1086 | 1087 |
1088 |

Примеры

1089 |

rxjs-subjects-example 1090 |

1091 |
    1092 |
  1. unsubscribe
  2. 1093 | 1094 | 1095 | 1096 | 1097 |
1098 |
1099 | 1100 |
1101 |

Спасибо!

1102 |

github.com/aalexeev239/rxjs-subjects 1104 |

1105 |

goo.gl/3J55dE

1107 | https://github.com/aalexeev239/rxjs-subjects 1108 |
1109 | 1110 | 1113 | 1114 |
1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | --------------------------------------------------------------------------------