Skip to content

Instantly share code, notes, and snippets.

@HipsterZipster
Forked from JamesHenry/app\actions\foo.ts
Created July 29, 2016 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HipsterZipster/5043ab6543f13aa783f2d5ffac608a30 to your computer and use it in GitHub Desktop.
Save HipsterZipster/5043ab6543f13aa783f2d5ffac608a30 to your computer and use it in GitHub Desktop.
ngMetadata 2.x - with @ngrx/store on Angular 1
import { Injectable } from 'ng-metadata/core'
import { Action } from '@ngrx/store'
interface SomePayload {
baz: string
}
/**
* Instead of passing around action string constants and manually recreating
* action objects at the point of dispatch, we create services encapsulating
* each appropriate action group. Action types are included as static
* members and kept next to their action creator. This promotes a
* uniform interface and single import for appropriate actions
* within your application components.
*/
@Injectable()
export class FooActions {
static BAR = 'Bar'
bar(somePayload: SomePayload): Action {
return {
type: FooActions.BAR,
payload: somePayload,
}
}
}
import { FooActions } from './foo'
export {
FooActions,
}
export default [
FooActions,
]
import { Component } from 'ng-metadata/core';
import { FooComponent } from './foo.component'
@Component({
selector: 'my-app',
template: `
<h1>My First Angular 1 App <small>with ng-metadata!</small></h1>
<br><br>
<foo></foo>
`,
directives: [FooComponent],
})
export class AppComponent { }
import { Component } from 'ng-metadata/core'
import { Store } from './ngrx-store-one'
import { FooState } from './reducers/foo'
import { FooActions } from './actions/index'
@Component({
selector: 'foo',
template: `
<p>Foo.qux state:</p>
<h3 style="color: blue;">{{ ($ctrl.foo | async:this).qux }}</h3>
<button ng-click="$ctrl.onClick()">Click me!</button>
`,
})
export class FooComponent {
foo: Observable<number>
constructor(
private store: Store<AppState>,
private fooActions: FooActions
) {
this.foo = this.store.select<FooState>('foo')
}
onClick() {
this.store.dispatch(this.fooActions.bar({
baz: Math.random().toString(16).slice(2),
}))
}
}
export { AppComponent } from './app.component';
import { bootstrap } from 'ng-metadata/platform-browser-dynamic';
import { AsyncPipe } from 'ng-metadata/common'
import { provideStore } from './ngrx-store-one'
import reducers from './reducers/index'
import actions from './actions/index'
import { AppComponent } from './index';
const providers = [
AsyncPipe,
provideStore(reducers),
actions,
]
/**
* Bootstrap the Angular application using ng-metadata,
* which uses strict dependency injection by default
*/
bootstrap(AppComponent, providers)
/**
* Import @ngrx/store providers
*/
import { Reducer } from '@ngrx/store'
import { Dispatcher } from '@ngrx/store'
import { Store } from '@ngrx/store'
import { State } from '@ngrx/store'
import { combineReducers } from '@ngrx/store'
/**
* Create annotated versions of the providers so that ng-metadata
* can register them against our ng1 app
*/
import { Injectable, OpaqueToken } from 'ng-metadata/core'
@Injectable()
class Ng1Dispatcher extends Dispatcher {}
@Injectable()
class Ng1Store<T> extends Store<T> {}
@Injectable()
class Ng1State<T> extends State<T> {}
@Injectable()
class Ng1Reducer extends Reducer {}
/**
* ng-metadata doesn't currently support objects created via `new String()`,
* so create an alternative INITIAL_REDUCER and INITIAL_STATE using OpaqueToken
*/
export const INITIAL_REDUCER = new OpaqueToken('Token ngrx/store/reducer')
export const INITIAL_STATE = new OpaqueToken('Token ngrx/store/initial-state')
/**
* Replicate the structure of the contents of ng2.ts from @ngrx/store,
* but using the annotated dependencies
*/
const dispatcherProvider = {
provide: Ng1Dispatcher,
useFactory() {
return new Dispatcher()
}
}
const storeProvider = {
provide: Ng1Store,
deps: [Ng1Dispatcher, Ng1Reducer, Ng1State, INITIAL_STATE],
useFactory(dispatcher: Dispatcher, reducer: Reducer, state$: State<any>, initialState: any) {
return new Store<any>(dispatcher, reducer, state$, initialState)
}
}
const stateProvider = {
provide: Ng1State,
deps: [INITIAL_STATE, Ng1Dispatcher, Ng1Reducer],
useFactory(initialState: any, dispatcher: Dispatcher, reducer: Reducer) {
return new State(initialState, dispatcher, reducer)
}
}
const reducerProvider = {
provide: Ng1Reducer,
deps: [Ng1Dispatcher, INITIAL_REDUCER],
useFactory(dispatcher: Dispatcher, reducer: any) {
return new Reducer(dispatcher, reducer)
}
}
export function provideStore(reducer: any, initialState?: any): any[] {
return [
{
provide: INITIAL_REDUCER,
useFactory() {
if (typeof reducer === 'function') {
return reducer
}
return combineReducers(reducer)
}
},
{
provide: INITIAL_STATE,
deps: [INITIAL_REDUCER],
useFactory(reducer: Function) {
if (initialState === undefined) {
return reducer(undefined, { type: Dispatcher.INIT })
}
return initialState
}
},
dispatcherProvider,
storeProvider,
stateProvider,
reducerProvider,
]
}
/**
* Export the annotated store as `Store` so that we match
* the public interface of @ngrx/store
*/
export { Ng1Store as Store }
import { Action, ActionReducer } from '@ngrx/store'
import { FooActions } from '../actions/index'
export interface FooState {
qux: string
}
const initialState: FooState = {
qux: 'No one has clicked me :(',
}
const fooReducer: ActionReducer<FooState> = (state = initialState, action: Action) => {
switch (action.type) {
case FooActions.BAR:
return Object.assign({}, state, {
qux: action.payload.baz,
})
default:
return state
}
}
export default fooReducer
/**
* The compose function is one of our most handy tools. In basic terms, you give
* it any number of functions and it returns a function. This new function
* takes a value and chains it through every composed function, returning
* the output.
*
* More: https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch5.html
*/
import { compose } from '@ngrx/core/compose'
/**
* storeLogger is a powerful metareducer that logs out each time we dispatch
* an action.
*
* A metareducer wraps a reducer function and returns a new reducer function
* with superpowers. They are handy for all sorts of tasks, including
* logging, undo/redo, and more.
*/
import { storeLogger } from 'ngrx-store-logger'
/**
* combineReducers is another useful metareducer that takes a map of reducer
* functions and creates a new reducer that stores the gathers the values
* of each reducer and stores them using the reducer's key. Think of it
* almost like a database, where every reducer is a table in the db.
*
* More: https://egghead.io/lessons/javascript-redux-implementing-combinereducers-from-scratch
*/
import { combineReducers } from '@ngrx/store'
/**
* Import reducers and state interfaces
*/
import fooReducer, { FooState } from './foo'
/**
* We treat each reducer like a table in a database. This means
* our top level state interface is just a map of keys to inner state types.
*/
export interface AppState {
foo: FooState
}
/**
* Because metareducers take a reducer function and return a new reducer,
* we can use our compose helper to chain them together. Here we are
* using combineReducers to make our top level reducer, and then
* wrapping that in storeLogger. Remember that compose applies
* the result from right to left.
*/
export default compose(storeLogger(), combineReducers)({
foo: fooReducer,
})
<!DOCTYPE html>
<html>
<head>
<!-- 1. Load libraries -->
<!-- Polyfill(s) for older browsers -->
<script src="https://npmcdn.com/core-js/client/shim.min.js"></script>
<script src="https://npmcdn.com/reflect-metadata@0.1.3"></script>
<script src="https://npmcdn.com/systemjs@0.19.27/dist/system.src.js"></script>
<script src="https://code.angularjs.org/1.5.7/angular.js"></script>
<!-- 2. Configure SystemJS -->
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<link rel="stylesheet" href="style.css">
</head>
<!-- 3. Display the application -->
<body>
<my-app>Loading...</my-app>
</body>
</html>
console.log('Hello World!');
/* todo: add styles */
System.config({
//use typescript for compilation
transpiler: 'ts',
typescriptOptions: {
tsconfig: true
},
meta: {
'typescript': {
"exports": "ts"
}
},
//map tells the System loader where to look for things
map: {
'app': './app',
'ng-metadata': 'https://npmcdn.com/ng-metadata',
'ngrx-store-logger': 'https://npmcdn.com/ngrx-store-logger',
'rxjs': 'https://npmcdn.com/rxjs@5.0.0-beta.6',
'@ngrx/store': 'https://npmcdn.com/@ngrx/store',
'@ngrx/core': 'https://npmcdn.com/@ngrx/core',
'ts': 'https://npmcdn.com/plugin-typescript@4.0.10/lib/plugin.js',
'typescript': 'https://npmcdn.com/typescript@1.9.0-dev.20160409/lib/typescript.js',
},
//packages defines our app package
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
'ng-metadata': {
defaultExtension: 'js'
},
'ngrx-store-logger': {
defaultExtension: 'js'
},
'rxjs': {
main: 'index.js',
defaultExtension: 'js'
},
'@ngrx/core': {
main: 'index.js',
format: 'cjs'
},
'@ngrx/store': {
main: 'index.js',
format: 'cjs'
}
}
});
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment