Skip to content

Commit

Permalink
feat: add @ngrx/component package to platform
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts committed Aug 5, 2019
1 parent 3997cc2 commit 4126aee
Show file tree
Hide file tree
Showing 24 changed files with 776 additions and 1 deletion.
5 changes: 4 additions & 1 deletion build/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export async function copySchematicsCore(config: Config) {
.toString('utf-8');
const pkgConfig = JSON.parse(packageJson);

if (pkgConfig.schematics || pkgConfig['ng-update'].migrations) {
if (
pkgConfig.schematics ||
(pkgConfig['ng-update'] && pkgConfig['ng-update'].migrations)
) {
ncp(
`${modulesDir}/schematics-core`,
`${modulesDir}/${pkg}/schematics-core`,
Expand Down
31 changes: 31 additions & 0 deletions modules/component/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package(default_visibility = ["//visibility:public"])

load("//tools:defaults.bzl", "ng_module", "ng_package")

ng_module(
name = "component",
srcs = glob([
"*.ts",
"src/**/*.ts",
]),
module_name = "@ngrx/component",
deps = [
"@npm//@angular/common",
"@npm//@angular/core",
"@npm//rxjs",
"@npm//typescript",
],
)

ng_package(
name = "npm_package",
srcs = glob(["**/*.externs.js"]) + [
"package.json",
],
entry_point = "modules/component/index.js",
packages = [
],
deps = [
":component",
],
)
3 changes: 3 additions & 0 deletions modules/component/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Change Log

See [CHANGELOG.md](https://github.com/ngrx/platform/blob/master/CHANGELOG.md)
5 changes: 5 additions & 0 deletions modules/component/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# @ngrx/store

The sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.

License: MIT
7 changes: 7 additions & 0 deletions modules/component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* DO NOT EDIT
*
* This file is automatically generated at build
*/

export * from './public_api';
28 changes: 28 additions & 0 deletions modules/component/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@ngrx/component",
"version": "0.8.2-beta.0",
"description": "Reactive Extensions for Angular Components",
"repository": {
"type": "git",
"url": "git+https://github.com/ngrx/platform.git"
},
"keywords": [
"RxJS",
"Angular",
"NgRx",
"Components",
"Angular CLI"
],
"author": "NgRx",
"license": "MIT",
"bugs": {
"url": "https://github.com/ngrx/platform/issues"
},
"homepage": "https://github.com/ngrx/platform#readme",
"peerDependencies": {
"@angular/common": "NG_VERSION",
"@angular/core": "NG_VERSION",
"rxjs": "RXJS_VERSION"
},
"sideEffects": false
}
1 change: 1 addition & 0 deletions modules/component/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/index';
15 changes: 15 additions & 0 deletions modules/component/src/component.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { LetDirective } from './let/let.directive';
import { Async$Pipe } from './push$/async$.pipe';
import { Push$Pipe } from './push$/push$.pipe';

const DECLARATIONS = [LetDirective, Push$Pipe, Async$Pipe];

const EXPORTS = [DECLARATIONS];

@NgModule({
declarations: [DECLARATIONS],
imports: [],
exports: [EXPORTS],
})
export class NgRxComponentModule {}
39 changes: 39 additions & 0 deletions modules/component/src/core/get-property-subject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { AsyncSubject, BehaviorSubject, ReplaySubject, Subject } from 'rxjs';

export type subjectFactory = () => Subject<any>;

export function getSubjectFactory<T>(): subjectFactory {
return () => new Subject<T>();
}

export function behaviourSubjectFactory<T>(init: T): subjectFactory {
return () => new BehaviorSubject(init);
}

export function getReplaySubjectFactory<T>(bufferSize = 1): subjectFactory {
return () => new ReplaySubject<T>(bufferSize);
}

export function getAsyncSubjectFactory<T>(): subjectFactory {
return () => new AsyncSubject<T>();
}

export function getPropertySubject<T = any>(
// tslint:disable-next-line
objInstance: Object | any,
property: PropertyKey,
sFactory: () => Subject<T> = getReplaySubjectFactory(1),
subProperty: PropertyKey = ''
): Subject<T> {
if (subProperty === '') {
return objInstance[property] || (objInstance[property] = sFactory());
} else {
if (!objInstance[property]) {
objInstance[property] = {};
}
return (
objInstance[property][subProperty] ||
(objInstance[property][subProperty] = sFactory())
);
}
}
7 changes: 7 additions & 0 deletions modules/component/src/core/invalid_pipe_argument_error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Type, ɵstringify as stringify } from '@angular/core';

export function invalidInputValueError(type: Type<any>, value: Object) {
return Error(
`invalidInputValueError: '${value}' for directive '${stringify(type)}'`
);
}
1 change: 1 addition & 0 deletions modules/component/src/core/state-default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const STATE_DEFAULT = undefined;
142 changes: 142 additions & 0 deletions modules/component/src/hook$/hook$.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import {
ɵComponentDef as ComponentDef,
ɵNG_COMPONENT_DEF as NG_COMPONENT_DEF,
Type,
} from '@angular/core';
import {
getPropertySubject,
getReplaySubjectFactory,
} from '../core/get-property-subject';
import { EMPTY, Observable, of, pipe, UnaryFunction } from 'rxjs';
import { catchError, take, takeUntil } from 'rxjs/operators';

export enum HookNames {
afterContentChecked = 'afterContentChecked',
afterContentInit = 'afterContentInit',
afterViewChecked = 'afterViewChecked',
afterViewInit = 'afterViewInit',
doCheck = 'doCheck',
onChanges = 'onChanges',
onDestroy = 'onDestroy',
onInit = 'onInit',
}

interface Hooks {
afterContentChecked: string;
afterContentInit: string;
afterViewChecked: string;
afterViewInit: string;
doCheck: string;
onChanges: string;
onDestroy: string;
onInit: string;
}

const hooksWrapped: { [x in keyof Hooks]: boolean } = {
afterContentChecked: false,
afterContentInit: false,
afterViewChecked: false,
afterViewInit: false,
doCheck: false,
onChanges: true,
onDestroy: false,
onInit: false,
};

const singleShotOperators = (
destroy$: Observable<any>
): UnaryFunction<any, any> =>
pipe(
take(1),
catchError(e => of()),
takeUntil(destroy$)
);
const onGoingOperators = (destroy$: Observable<any>): UnaryFunction<any, any> =>
pipe(
catchError(e => EMPTY),
takeUntil(destroy$)
);

const getHooksOperatorsMap = (
destroy$: Observable<any>
): { [x in keyof Hooks]: UnaryFunction<any, any> } => ({
afterContentChecked: singleShotOperators(destroy$),
afterContentInit: singleShotOperators(destroy$),
afterViewChecked: onGoingOperators(destroy$),
afterViewInit: singleShotOperators(destroy$),
doCheck: onGoingOperators(destroy$),
onChanges: onGoingOperators(destroy$),
onDestroy: pipe(
catchError(e => of()),
take(1)
),
onInit: singleShotOperators(destroy$),
});

export function Hook$<T>(hookName: keyof Hooks): PropertyDecorator {
return (
// tslint:disable-next-line
component: Object,
propertyKey: PropertyKey
) => {
const keyUniquePerPrototype = Symbol('@ngrx-hook$');
const subjectFactory = getReplaySubjectFactory(1);

const cDef: ComponentDef<any> = (component as any).constructor[
NG_COMPONENT_DEF
];

let target: any;
let hook;
let originalHook: any;

if (cDef === undefined) {
target = component;
hook = getCompHookName(hookName);
originalHook = target[hook];
} else {
// @TODO I guess this is a miss conception that ngChanges is wrapped in a function.
target = hooksWrapped[hookName] ? component : cDef;
hook = hooksWrapped[hookName] ? getCompHookName(hookName) : hookName;
originalHook = hooksWrapped[hookName]
? (cDef as any)[hook]
: (component as any)[hook];
}

target[hook] = function(args: any) {
getPropertySubject<T>(
this,
keyUniquePerPrototype,
subjectFactory,
hookName
).next(args);
// tslint:disable-next-line:no-unused-expression
originalHook && originalHook.call(component, args);
};

const propertyKeyDescriptor: TypedPropertyDescriptor<Observable<T>> = {
get() {
const destroy$ = getPropertySubject<T>(
this,
keyUniquePerPrototype,
subjectFactory,
HookNames.onDestroy
).asObservable();
const hookOperators = getHooksOperatorsMap(destroy$)[hookName];
return getPropertySubject<T>(
this,
keyUniquePerPrototype,
subjectFactory,
hookName
)
.asObservable()
.pipe(hookOperators);
},
};
Object.defineProperty(target, propertyKey, propertyKeyDescriptor);
};
}

function getCompHookName(hookName: string): string {
return 'ng' + hookName[0].toUpperCase() + hookName.slice(1);
}
12 changes: 12 additions & 0 deletions modules/component/src/hook$/operators/selectChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SimpleChanges } from '@angular/core';
import { pipe, UnaryFunction, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';

export function selectChange<T>(
prop: string
): UnaryFunction<Observable<SimpleChanges>, Observable<T>> {
return pipe(
map((change: SimpleChanges) => change[prop].currentValue),
distinctUntilChanged()
);
}
18 changes: 18 additions & 0 deletions modules/component/src/host-listener$/host-listener$.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ElementRef } from '@angular/core';
import { fromEvent } from 'rxjs';

export function HostListener$<T>(eventName: string): PropertyDecorator {
return (
// tslint:disable-next-line
target: Object,
propertyKey: string | symbol
) => {
Object.defineProperty(target, propertyKey, {
get() {
// @TODO investigate @ViewChild for usage
const elementRef = this.injector.get(ElementRef);
return fromEvent(elementRef.nativeElement, eventName);
},
});
};
}
11 changes: 11 additions & 0 deletions modules/component/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export { Hook$ } from './hook$/hook$.decorator';
export { selectChange } from './hook$/operators/selectChange';
export { HostListener$ } from './host-listener$/host-listener$.decorator';
export { Input$ } from './input$/input$.decorator';
export { LocalStateService } from './local-state/local-state';
export { selectSlice } from './local-state/operators/selectSlice';
export { Push$Pipe } from './push$/push$.pipe';
export { Async$Pipe } from './push$/async$.pipe';
export { detectChanges } from './push$/operators/detectChanges';
export { LetDirective } from './let/let.directive';
export { NgRxComponentModule } from './component.module';
36 changes: 36 additions & 0 deletions modules/component/src/input$/input$.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Observable } from 'rxjs';
import {
getPropertySubject,
getReplaySubjectFactory,
} from '../core/get-property-subject';

export function Input$<T>(): PropertyDecorator {
return (
// @TODO get better typing
// tslint:disable-next-line
component: Object,
propertyKey: PropertyKey
) => {
const keyUniquePerPrototype = Symbol('@ngrx-Input$');

const propertyKeyDescriptor: TypedPropertyDescriptor<Observable<T>> = {
set(newValue) {
// @TODO: Get type of property instead of any
getPropertySubject<any>(
this,
keyUniquePerPrototype,
getReplaySubjectFactory(1)
).next(newValue);
},
get() {
return getPropertySubject<any>(
this,
keyUniquePerPrototype,
getReplaySubjectFactory(1)
);
},
};

Object.defineProperty(component, propertyKey, propertyKeyDescriptor);
};
}
Loading

0 comments on commit 4126aee

Please sign in to comment.