├── .editorconfig ├── .gitignore ├── AvoidReactiveProgramming.md ├── ProblemsWithReactiveProgrammingInAngular.md ├── README.md ├── angular.json ├── avoid-leverage-observables.png ├── avoid-observables-cover.png ├── browserslist ├── draft └── handling-conditions.component.ts ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── ex1-http.png ├── ex2-route-params.png ├── ex3-store.png ├── ex4-store-and-http.png ├── karma.conf.js ├── mix-styles.png ├── package-lock.json ├── package.json ├── projects └── ng-re │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── core │ │ │ ├── get-property-subject.ts │ │ │ ├── invalid_pipe_argument_error.ts │ │ │ ├── isZoneLess.ts │ │ │ └── state-default.ts │ │ ├── hook$ │ │ │ ├── hook$.decorator.ts │ │ │ └── operators │ │ │ │ └── selectChange.ts │ │ ├── host-listener$ │ │ │ └── host-listener$.decorator.ts │ │ ├── input$ │ │ │ └── input$.decorator.ts │ │ ├── let │ │ │ └── let.directive.ts │ │ ├── local-state │ │ │ ├── local-state.ts │ │ │ └── operators │ │ │ │ └── selectSlice.ts │ │ ├── ng-re.module.ts │ │ └── push$ │ │ │ ├── async$.pipe.ts │ │ │ ├── operators │ │ │ └── detectChanges.ts │ │ │ ├── push$.pipe.spec.ts │ │ │ └── push$.pipe.ts │ ├── public-api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── src ├── app │ ├── app.component.ts │ ├── app.module.ts │ ├── app.routes.ts │ └── components │ │ ├── avoid-reactivity-container │ │ ├── avoid-reactivity-container.component.ts │ │ ├── avoid-reactivity-rx-subscription.component.ts │ │ ├── avoid-reactivity-subscription.component.ts │ │ ├── avoid-reactivity-timing.component.ts │ │ ├── index.ts │ │ ├── routes.ts │ │ └── tick.service.ts │ │ ├── from-view-event-container │ │ ├── from-view-event-container.component.ts │ │ ├── from-view-event.component.ts │ │ ├── index.ts │ │ └── routes.ts │ │ ├── hook$-container │ │ ├── dummy.service.ts │ │ ├── full-example-container.component.ts │ │ ├── full-exmple.component.ts │ │ ├── hook$-container.component.ts │ │ ├── index.ts │ │ ├── routes.ts │ │ ├── select-change-container.component.ts │ │ ├── select-change.component.ts │ │ └── service-life-cycle-contaier.component.ts │ │ ├── host-listener-container │ │ ├── host-listener-container.component.ts │ │ ├── host-listener.component.ts │ │ ├── index.ts │ │ └── routes.ts │ │ ├── input-container │ │ ├── index.ts │ │ ├── input-container.component.ts │ │ ├── input.component.ts │ │ ├── input2.component.ts │ │ └── routes.ts │ │ ├── let-directive-container │ │ ├── full-example.component.ts │ │ ├── index.ts │ │ ├── let-directive-container.component.ts │ │ ├── observable-channels.component.ts │ │ ├── routes.ts │ │ ├── supported-syntax.component.ts │ │ └── value.component.ts │ │ ├── local-state-container │ │ ├── creation-and-clean-up │ │ │ └── creation-and-clean-up-container.component.ts │ │ ├── early-producer │ │ │ ├── early-producer-container.component.ts │ │ │ └── example.service.ts │ │ ├── full-example-container │ │ │ ├── child-local-state-container.component.ts │ │ │ ├── components │ │ │ │ ├── display-compoent-state.interface.ts │ │ │ │ ├── options-state.ts │ │ │ │ ├── options.component.ts │ │ │ │ └── table.component.ts │ │ │ ├── full-example-container.component.ts │ │ │ ├── local-state-container2.component.ts │ │ │ ├── map-to-Attendees-with-selection-filtered.ts │ │ │ └── services │ │ │ │ ├── local-state-component.facade.ts │ │ │ │ └── ng-rx-store.service.ts │ │ ├── index.ts │ │ ├── late-subscribers │ │ │ ├── example.service.ts │ │ │ ├── late-subscriber.component.ts │ │ │ └── late-subscribers-container.component.ts │ │ ├── local-state-container.component.ts │ │ ├── ng-for │ │ │ └── ng-for-container.component.ts │ │ ├── placeholder-content │ │ │ └── placeholder-content-container.component.ts │ │ ├── random.ts │ │ ├── routes.ts │ │ └── sharing-a-reference │ │ │ ├── sharing-a-reference-container.component.ts │ │ │ └── sharing-a-reference.component.ts │ │ ├── push-pipe-container │ │ ├── index.ts │ │ ├── push-pipe-channels.component.ts │ │ ├── push-pipe-container.component.ts │ │ ├── push-pipe.component.ts │ │ └── routes.ts │ │ └── star-rating │ │ ├── index.ts │ │ ├── routes.ts │ │ ├── star-rating-config.interface.ts │ │ ├── star-rating-container.component.ts │ │ ├── star-rating.component.ts │ │ └── star.component.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /AvoidReactiveProgramming.md: -------------------------------------------------------------------------------- 1 | # How to Avoid Observables in Angular 2 | 3 |  4 | 5 | Angular is an object-oriented framework. 6 | Even if there are a lot of things imperative some services and therefore also some third party libs, are reactive. 7 | This is great because it provides both approaches in one framework, which is at the moment a more or less unique thing. 8 | 9 | As reactive programming is hard for an imperative thinking mind, many people try to avoid reactive programming. 10 | This article will help you to understand how to avoid it and why it is even here at all. 11 | 12 | 13 | 14 | - [Comparing Basic Usecases](#comparing-basic-usecases) 15 | * [Retrieving data over HTTP](#retrieving-data-over-http) 16 | * [Retrieving values provided by Angular](#retrieving-values-provided-by-angular) 17 | * [Retrieving values provided by third parties](#retrieving-values-provided-by-third-parties) 18 | - [Patterns to avoid observables](#patterns-to-avoid-observables) 19 | * [Where to subscribe](#where-to-subscribe) 20 | * [Make it even easier](#make-it-even-easier) 21 | - [The reason for reactive programming](#the-reason-for-reactive-programming) 22 | * [Comparing composition](#comparing-composition) 23 | - [Summary](#summary) 24 | 25 | 26 | 27 | If you **DON'T** want to use a reactive approach in your component you 28 | should subscribe as soon as possible to the stream you want to get rid of and do the following things: 29 | - subscribe to a stream and assign incoming values to a component property 30 | - if necessary, unsubscribe the stream as soon as the component gets destroyed 31 | 32 | To elaborate with some more practical things we start with a part of angular that provides reactivity and try to avoid it. 33 | 34 | ## Comparing Basic Usecases 35 | 36 | In this section, we will get a good overview of some of the scenarios we get in touch with reactive programming in Angular. 37 | 38 | We will take a look at: 39 | - Reactive services provided by Angular 40 | - Cold and Hot Observables 41 | - Subscription handling 42 | 43 | And see the reactive and imperative approach in comparison. 44 | 45 | ### Retrieving values from cold observables 46 | 47 | Let's solve a very primitive example first. 48 | Retrieving data over HTTP and render it. 49 | 50 |  51 | 52 | 53 | We start with the reactive approach and then try to convert it into an imperative approach. 54 | 55 | **Leveraging Reactive Programming** 56 | ```typescript 57 | import { Component } from '@angular/core'; 58 | import { HttpClient } from '@angular/common/http'; 59 | import { map } from 'rxjs/operators'; 60 | 61 | @Component({ 62 | selector: 'example1-rx', 63 | template: ` 64 |
{{val1 | json}}19 |
{{(value$ | push$ | json) || 'undefined'}}
` 8 | }) 9 | class BasicTestsComponent { 10 | value$: any = new ReplaySubject(1); 11 | } 12 | 13 | @Component({ 14 | template: `{{(value$ | push$:onlyNewRef)?.value}}
` 15 | }) 16 | class FlagsTestsComponent { 17 | 18 | initialValue = ''; 19 | // forwardOnlyNewReferences option 20 | onlyNewRef = true; 21 | value$ = new ReplaySubject<{ value: string }>(1); 22 | 23 | 24 | constructor() { 25 | const a = {value: this.initialValue}; 26 | this.value$.next(a); 27 | a.value = 'mutation'; 28 | this.value$.next(a); 29 | } 30 | } 31 | 32 | describe('PushPipe', () => { 33 | 34 | let basicTestsComponent: BasicTestsComponent; 35 | let basicTestsFixture: ComponentFixtureLast received output:
9 |10 | {{clicks$ | async | json}} 11 |12 | 16 |
Last output emission:
10 |11 | {{interval$ | async$ | json}} 12 |13 | ` 14 | }) 15 | export class FromViewEventComponent { 16 | interval$ = interval(1000).pipe(share()); 17 | @Output() out = this.interval$; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/from-view-event-container/index.ts: -------------------------------------------------------------------------------- 1 | import {FromViewEventContainerComponent} from './from-view-event-container.component'; 2 | import {FromViewEventComponent} from './from-view-event.component'; 3 | 4 | 5 | export * from './routes'; 6 | export const FROM_VIEW_EVENT$_DECLARATIONS = [ 7 | FromViewEventContainerComponent, 8 | FromViewEventComponent 9 | ]; 10 | -------------------------------------------------------------------------------- /src/app/components/from-view-event-container/routes.ts: -------------------------------------------------------------------------------- 1 | import {FromViewEventContainerComponent} from './from-view-event-container.component'; 2 | 3 | export const FROM_VIEW_EVENT$_ROUTES = [ 4 | { 5 | path: '', 6 | component: FromViewEventContainerComponent 7 | } 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/components/hook$-container/dummy.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, OnDestroy} from '@angular/core'; 2 | import {Hook$} from 'ng-re'; 3 | import {Observable} from 'rxjs'; 4 | 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class DummyService implements OnDestroy { 10 | 11 | @Hook$('onDestroy') onDestroy$: Observable
10 | Container state$: 11 |
12 |13 | {{state$ | async$ | json}} 14 |15 |
state:
10 |{{onChanges$ | async | json}}11 | `, 12 | changeDetection: ChangeDetectionStrategy.OnPush 13 | }) 14 | /*implements OnChanges, DoCheck, OnInit, AfterViewInit, 15 | AfterViewChecked, AfterContentInit, AfterContentChecked, OnDestroy */ 16 | export class FullExampleComponent { 17 | 18 | @Hook$('doCheck') doCheck$: Observable
state$:
9 |{{state$ |async | json}}10 | `, 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class SelectChangeComponent implements OnChanges, AfterViewInit { 14 | 15 | public state = 0; 16 | @Hook$('onChanges') 17 | onChanges$; 18 | 19 | @Input() 20 | value: { value: number }; 21 | state$ = this.onChanges$.pipe(selectChange('value')); 22 | 23 | constructor() { 24 | 25 | } 26 | 27 | // @TODO remove after fixed reactive hooks 28 | ngOnChanges(changes: SimpleChanges): void { 29 | } 30 | 31 | ngAfterViewInit(): void { 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/app/components/hook$-container/service-life-cycle-contaier.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterContentChecked, 3 | AfterContentInit, 4 | AfterViewChecked, 5 | AfterViewInit, 6 | ChangeDetectionStrategy, 7 | Component, 8 | DoCheck, 9 | Input, 10 | OnChanges, 11 | OnDestroy, 12 | OnInit, 13 | SimpleChanges 14 | } from '@angular/core'; 15 | import {Hook$} from 'ng-re'; 16 | import {Observable} from 'rxjs'; 17 | import {DummyService} from './dummy.service'; 18 | 19 | @Component({ 20 | selector: 'app-service-life-cycle', 21 | template: ` 22 |
12 | Num clicks: 13 |
14 |15 | {{numClicks$ | async | json}} 16 |17 | `, 18 | changeDetection: ChangeDetectionStrategy.OnPush 19 | }) 20 | export class HostListenerComponent { 21 | 22 | @HostListener$('click') 23 | hostClick$; 24 | 25 | numClicks$ = this.hostClick$.pipe(scan(a => ++a, 0)); 26 | 27 | constructor(public injector: Injector) { 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/components/host-listener-container/index.ts: -------------------------------------------------------------------------------- 1 | import {HostListenerContainerComponent} from './host-listener-container.component'; 2 | import {HostListenerComponent} from './host-listener.component'; 3 | 4 | export * from './routes'; 5 | export const HOST_LISTENER$_DECLARATIONS = [ 6 | HostListenerContainerComponent, 7 | HostListenerComponent 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/components/host-listener-container/routes.ts: -------------------------------------------------------------------------------- 1 | import {HostListenerContainerComponent} from './host-listener-container.component'; 2 | 3 | export const HOST_LISTENER$_ROUTES = [ 4 | { 5 | path: '', 6 | component: HostListenerContainerComponent 7 | } 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/components/input-container/index.ts: -------------------------------------------------------------------------------- 1 | import {InputContainerComponent} from './input-container.component'; 2 | import {InputComponent} from './input.component'; 3 | import {Input2Component} from './input2.component'; 4 | 5 | export * from './routes'; 6 | export const INPUT$_DECLARATIONS = [ 7 | InputContainerComponent, 8 | InputComponent, 9 | Input2Component 10 | ]; 11 | -------------------------------------------------------------------------------- /src/app/components/input-container/input-container.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {interval, Observable, timer} from 'rxjs'; 3 | import {map, share, take} from 'rxjs/operators'; 4 | 5 | @Component({ 6 | selector: 'app-input-container', 7 | template: ` 8 |
10 | value in container: {{state$ | push$ | json}} 11 | value in container: {{state2$ | push$ | json}} 12 |13 | 14 |
9 | state$: {{state$ | async | json}} 10 |11 | `, 12 | changeDetection: ChangeDetectionStrategy.OnPush 13 | }) 14 | export class InputComponent { 15 | 16 | @Input$() 17 | @Input('state') 18 | state$; 19 | 20 | constructor() { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/input-container/input2.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; 2 | import {Input$} from 'ng-re'; 3 | import {merge, Subject} from 'rxjs'; 4 | import {map, scan, shareReplay} from 'rxjs/operators'; 5 | 6 | 7 | @Component({ 8 | selector: 'app-input2', 9 | template: ` 10 |
12 | state$: {{state$ | async | json}}15 |
13 | state2$: {{state2$ | async | json}} 14 |
Component composition
16 |17 | viewModelA$: {{viewModelA$ | async | json}} 18 |19 | `, 20 | changeDetection: ChangeDetectionStrategy.OnPush 21 | }) 22 | export class Input2Component { 23 | command$$ = new Subject(); 24 | 25 | @Input$() 26 | @Input('state') 27 | state$; 28 | 29 | @Input$() 30 | @Input('state2') 31 | state2$; 32 | 33 | viewModelA$ = merge( 34 | this.command$$, 35 | this.state$.pipe(map(state => ({state}))), 36 | this.state2$.pipe(map(state2 => ({state2}))), 37 | ) 38 | .pipe( 39 | scan((st, sl) => ({...st, ...sl}), {}) 40 | ); 41 | 42 | 43 | constructor() { 44 | console.log('CTRO2 input child', this.state$); 45 | const initialState = { 46 | state: null, 47 | state2: [], 48 | otherSlice: {} 49 | }; 50 | setTimeout(() => { 51 | console.log('intialState', initialState); 52 | this.command$$.next(initialState); 53 | }, 1000); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/app/components/input-container/routes.ts: -------------------------------------------------------------------------------- 1 | import {InputContainerComponent} from './input-container.component'; 2 | 3 | export const INPUT$_ROUTES = [ 4 | { 5 | path: '', 6 | component: InputContainerComponent, 7 | } 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/components/let-directive-container/full-example.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {combineLatest, interval, Observable} from 'rxjs'; 3 | import {filter, map, share, take} from 'rxjs/operators'; 4 | 5 | @Component({ 6 | selector: 'app-let-directive-full-example', 7 | template: ` 8 |
next:
16 |{{(val1 | json) || 'undefined'}}17 |
error:
18 |{{(error | json) || 'undefined'}}19 |
complete:
20 |{{(complete | json) || 'undefined'}}21 |
10 | Binding with as syntax *ngrxLet="val1$ as o"
11 |
{{(o | json) || 'undefined'}}15 |
19 | Binding composed object *ngrxLet="combinedInComponent$ as o"
20 |
{{o | json}}24 |
28 | Binding an object of single values *ngrxLet="combinedInComponent$ as o; val1 as val1; val2 as val2"
29 |
{{val1 | json}}33 |
{{val2 | json}}34 |
39 | Binding an object of single values *ngrxLet="combinedInComponent$; let val1 = val1; let val2 = val2"
40 |
{{val1 | json}}44 |
{{val2 | json}}45 |
50 | Use animationFrameScheduler*ngrxLet="val1$ as o; useAf:true"
51 |
{{(o | json) || 'undefined'}}55 |
state$: {{localState.state$ | push$ | json}}13 |
num$: {{num$ | push$ | json}}14 | `, 15 | changeDetection: ChangeDetectionStrategy.OnPush, 16 | providers: [ 17 | LocalStateService 18 | ] 19 | }) 20 | export class CreationAndCleanUpContainerComponent { 21 | 22 | num$ = this.localState.state$ 23 | .pipe(selectSlice(s => s.num)); 24 | 25 | constructor(public localState: LocalStateService) { 26 | this.localState.setSlices({num: 777}); 27 | } 28 | 29 | setNum() { 30 | this.localState.setSlices({num: 3}); 31 | } 32 | 33 | deleteNum() { 34 | this.localState.setSlices({num: undefined}); 35 | } 36 | 37 | setRandomState() { 38 | this.localState 39 | .connectSlices({['num' + Math.random()]: interval(500).pipe(take(10))}); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/components/local-state-container/early-producer/early-producer-container.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {Subject} from 'rxjs'; 3 | import {share, shareReplay} from 'rxjs/operators'; 4 | import {LocalStateService} from './example.service'; 5 | 6 | @Component({ 7 | selector: 'app-early-producer-container', 8 | template: ` 9 |
fromLocalState$
11 |{{fromLocalState$ | async | json}}12 |
v
13 |{{v | async | json}}14 | `, 15 | changeDetection: ChangeDetectionStrategy.OnPush 16 | }) 17 | export class EarlyProducerContainerComponent { 18 | 19 | notUnderControl = new Subject(); 20 | v = this.notUnderControl 21 | .pipe( 22 | shareReplay(1) 23 | ); 24 | private localState = new LocalStateService(); 25 | fromLocalState$ = this.localState.state$; 26 | 27 | constructor() { 28 | this.notUnderControl.subscribe(value => { 29 | this.localState.set({value}); 30 | 31 | this.localState.set({timestamp: Date.now()}); 32 | }); 33 | 34 | this.fromLocalState$.subscribe(console.log); 35 | 36 | this.notUnderControl.next(1); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/app/components/local-state-container/early-producer/example.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {ReplaySubject} from 'rxjs'; 3 | import {scan, shareReplay} from 'rxjs/operators'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class LocalStateService { 9 | private command = new ReplaySubject(1); 10 | state$ = this.command.asObservable() 11 | .pipe(scan((a, c) => ({...a, ...c}), {}), 12 | // shareReplay(1) 13 | ); 14 | 15 | constructor() { 16 | this.state$.subscribe(console.log); 17 | } 18 | 19 | set(command) { 20 | this.command.next(command); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/components/local-state-container/full-example-container/child-local-state-container.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; 2 | import {combineLatest, Subject} from 'rxjs'; 3 | import {map, startWith, withLatestFrom} from 'rxjs/operators'; 4 | import {Input$, LocalStateService, selectSlice} from 'ng-re'; 5 | import {mapToAttendeesWithSelectionFiltered} from './map-to-Attendees-with-selection-filtered'; 6 | import {LocalStateComponentFacade} from './services/local-state-component.facade'; 7 | 8 | @Component({ 9 | selector: 'app-child-local-state-container', 10 | template: ` 11 |
12 | {{heading}} 13 | | 14 | 15 | 16 |
---|
19 | {{row[key]}} 20 | | 21 |
default$:
10 |{{default$ | async | json}}11 |
replayed$
12 |{{replayed$ | async | json}}13 |
fromLocalState$
14 |{{fromLocalState$ | async | json}}15 | `, 16 | changeDetection: ChangeDetectionStrategy.OnPush, 17 | }) 18 | export class LateSubscriberComponent { 19 | 20 | private localState = new LocalStateService(); 21 | 22 | default$ = new Subject(); 23 | replayed$ = new ReplaySubject(1); 24 | fromLocalState$ = this.localState.state$; 25 | 26 | @Input() 27 | set state(value) { 28 | this.default$.next({value}); 29 | this.replayed$.next({value}); 30 | 31 | this.localState.set({value}); 32 | this.localState.set({timestamp: Date.now()}); 33 | } 34 | 35 | constructor() { 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/components/local-state-container/late-subscribers/late-subscribers-container.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {of} from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-late-subscribers-container', 6 | template: ` 7 |
state$:
8 |{{num$ | async | json}}9 |
{{v | async | json}}16 | `, 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | providers: [LocalStateService] 19 | }) 20 | export class NgForContainerComponent { 21 | 22 | buttons$ = this.localState.state$.pipe(selectSlice(s => s.buttons)); 23 | 24 | constructor(private localState: LocalStateService) { 25 | this.localState.setSlices({ 26 | buttons: [1, 2, 3, 4, 5] 27 | }); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/app/components/local-state-container/placeholder-content/placeholder-content-container.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {of, Subject} from 'rxjs'; 3 | import {delay} from 'rxjs/operators'; 4 | 5 | @Component({ 6 | selector: 'app-placeholder-content-container', 7 | template: ` 8 |
Placeholder Content Container
9 |formGroupModel$:
8 |{{formGroupModel$ | async | json}}9 |
formValue$:
10 |{{formValue$ | async | json}}11 |
default$:
12 | 18 | `, 19 | changeDetection: ChangeDetectionStrategy.OnPush, 20 | }) 21 | export class SharingAReferenceComponent { 22 | state$ = new ReplaySubject(1); 23 | formGroup$: Observable10 | value: 11 |
12 |13 |15 | `, 16 | changeDetection: ChangeDetectionStrategy.OnPush 17 | }) 18 | export class PushPipeChannelsComponent { 19 | value$ = interval(1000) 20 | .pipe( 21 | map((v, i) => { 22 | if (i === 4) { 23 | throw new Error('asdfsda'); 24 | } 25 | return v; 26 | }) 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/components/push-pipe-container/push-pipe-container.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {timer} from 'rxjs'; 3 | import {shareReplay} from 'rxjs/operators'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-push-pipe-container', 8 | template: ` 9 |value: {{value}}14 |
primitiveInterval$ | push:
11 |{{primitiveInterval$ | push$ | json}}12 |
8 | value: 9 |
10 |11 | {{value | json}} 12 |13 | `, 14 | changeDetection: ChangeDetectionStrategy.OnPush 15 | }) 16 | export class PushPipeComponent { 17 | @Input() value; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/push-pipe-container/routes.ts: -------------------------------------------------------------------------------- 1 | import {PushPipeChannelsComponent} from './push-pipe-channels.component'; 2 | import {PushPipeContainerComponent} from './push-pipe-container.component'; 3 | 4 | export const PUSH$_ROUTES = [ 5 | { 6 | path: '', 7 | component: PushPipeContainerComponent, 8 | children: [ 9 | { 10 | path: 'pipe-channels', 11 | component: PushPipeChannelsComponent 12 | } 13 | ] 14 | } 15 | ]; 16 | -------------------------------------------------------------------------------- /src/app/components/star-rating/index.ts: -------------------------------------------------------------------------------- 1 | import {StarRatingContainerComponent} from './star-rating-container.component'; 2 | import {StarRatingComponent} from './star-rating.component'; 3 | import {StarComponent} from './star.component'; 4 | 5 | export * from './routes'; 6 | export const STAR_RATING_DECLARATIONS = [ 7 | StarRatingContainerComponent, 8 | StarRatingComponent, 9 | StarComponent 10 | ]; 11 | -------------------------------------------------------------------------------- /src/app/components/star-rating/routes.ts: -------------------------------------------------------------------------------- 1 | import {StarRatingContainerComponent} from './star-rating-container.component'; 2 | 3 | export const STAR_RATING_ROUTES = [ 4 | { 5 | path: '', 6 | component: StarRatingContainerComponent, 7 | children: [ 8 | 9 | ] 10 | } 11 | ]; 12 | -------------------------------------------------------------------------------- /src/app/components/star-rating/star-rating-config.interface.ts: -------------------------------------------------------------------------------- 1 | export type starRatingSizes = 'small' | 'medium' | 'large'; 2 | export type starRatingColor = 'default' | 'negative' | 'ok' | 'positive'; 3 | export type starRatingSpeed = 'immediately' | 'noticeable' | 'slow'; 4 | export type starRatingLabelPosition = 'left' | 'right' | 'top' | 'bottom'; 5 | export type starRatingStarTypes = 'svg' | 'icon' | 'custom-icon'; 6 | export type starRatingStarSpace = 'no' | 'between' | 'around'; 7 | export type starRatingDirection = 'rtl' | 'ltr'; 8 | 9 | export class StarRatingConfig { 10 | // binding defaults 11 | numOfStars?: number; 12 | size?: starRatingSizes; 13 | speed?: starRatingSpeed; 14 | labelPosition?: starRatingLabelPosition; 15 | starType?: starRatingStarTypes; 16 | staticColor: starRatingColor; 17 | getColor?: ( 18 | rating: number, 19 | numOfStars: number, 20 | staticColor?: starRatingColor 21 | ) => starRatingColor; 22 | getHalfStarVisible?: (rating: number) => boolean; 23 | // statics 24 | classEmpty?: string; 25 | classHalf?: string; 26 | classFilled?: string; 27 | assetsPath?: string; 28 | } 29 | -------------------------------------------------------------------------------- /src/app/components/star-rating/star-rating-container.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'app-star-rating-container', 6 | template: ` 7 |