├── public
└── .npmignore
├── src
├── app
│ ├── add-todo
│ │ ├── index.ts
│ │ └── add-todo.component.ts
│ ├── todo-list
│ │ ├── index.ts
│ │ ├── todo
│ │ │ ├── index.ts
│ │ │ └── todo.component.ts
│ │ └── todo-list.component.ts
│ ├── filter-selector
│ │ ├── index.ts
│ │ ├── filter-link
│ │ │ ├── index.ts
│ │ │ └── filter-link.component.ts
│ │ └── filter-selector.component.ts
│ ├── index.ts
│ ├── shared
│ │ ├── index.ts
│ │ ├── actions.ts
│ │ └── stateAndDispatcher.ts
│ ├── environment.ts
│ └── app.component.ts
├── favicon.ico
├── main.ts
├── typings.d.ts
├── tsconfig.json
├── index.html
└── system-config.ts
├── e2e
├── typings.d.ts
├── app.po.ts
├── app.e2e-spec.ts
└── tsconfig.json
├── config
├── environment.dev.ts
├── environment.prod.ts
├── environment.js
├── protractor.conf.js
├── karma-test-shim.js
└── karma.conf.js
├── .editorconfig
├── typings.json
├── .gitignore
├── README.md
├── angular-cli.json
├── angular-cli-build.js
├── LICENSE
└── package.json
/public/.npmignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/add-todo/index.ts:
--------------------------------------------------------------------------------
1 | export * from './add-todo.component';
--------------------------------------------------------------------------------
/e2e/typings.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/app/todo-list/index.ts:
--------------------------------------------------------------------------------
1 | export * from './todo-list.component';
2 |
--------------------------------------------------------------------------------
/src/app/todo-list/todo/index.ts:
--------------------------------------------------------------------------------
1 | export * from './todo.component';
2 |
--------------------------------------------------------------------------------
/src/app/filter-selector/index.ts:
--------------------------------------------------------------------------------
1 | export * from './filter-selector.component';
--------------------------------------------------------------------------------
/src/app/filter-selector/filter-link/index.ts:
--------------------------------------------------------------------------------
1 | export * from './filter-link.component';
--------------------------------------------------------------------------------
/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export * from './environment';
2 | export * from './app.component';
3 |
--------------------------------------------------------------------------------
/config/environment.dev.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false
3 | };
4 |
--------------------------------------------------------------------------------
/config/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/app/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './actions';
2 | export * from './stateAndDispatcher';
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corasla/angular2-tackling-state-rxjs/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | export class Angular2RxJSPage {
2 | navigateTo() {
3 | return browser.get('/');
4 | }
5 |
6 | getParagraphText() {
7 | return element(by.css('app-root h1')).getText();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrap } from '@angular/platform-browser-dynamic';
2 | import { enableProdMode } from '@angular/core';
3 | import { AppComponent, environment } from './app/';
4 |
5 | if (environment.production) {
6 | enableProdMode();
7 | }
8 |
9 | bootstrap(AppComponent);
10 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | // Typings reference file, see links for more information
2 | // https://github.com/typings/typings
3 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
4 |
5 | ///
6 | declare var module: { id: string };
7 |
--------------------------------------------------------------------------------
/src/app/environment.ts:
--------------------------------------------------------------------------------
1 | // The file for the current environment will overwrite this one during build
2 | // Different environments can be found in config/environment.{dev|prod}.ts
3 | // The build system defaults to the dev environment
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | // Angular-CLI server configuration
2 | // Unrelated to environment.dev|prod.ts
3 |
4 | /* jshint node: true */
5 |
6 | module.exports = function(environment) {
7 | return {
8 | environment: environment,
9 | baseURL: '/',
10 | locationType: 'auto'
11 | };
12 | };
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/src/app/shared/actions.ts:
--------------------------------------------------------------------------------
1 | export class AddTodoAction {
2 | constructor(public todoId: number, public text: string){}
3 | }
4 |
5 | export class ToggleTodoAction {
6 | constructor(public id: number){}
7 | }
8 |
9 | export class SetVisibilityFilter {
10 | constructor(public filter: string){}
11 | }
12 |
13 | export type Action = AddTodoAction | ToggleTodoAction | SetVisibilityFilter;
--------------------------------------------------------------------------------
/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "globalDevDependencies": {
3 | "angular-protractor": "registry:dt/angular-protractor#1.5.0+20160425143459",
4 | "jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
5 | "selenium-webdriver": "registry:dt/selenium-webdriver#2.44.0+20160317120654"
6 | },
7 | "globalDependencies": {
8 | "es6-shim": "registry:dt/es6-shim#0.31.2+20160602141504"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Angular2RxJSPage } from './app.po';
2 |
3 | describe('angular2-rx-js App', function() {
4 | let page: Angular2RxJSPage;
5 |
6 | beforeEach(() => {
7 | page = new Angular2RxJSPage();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('app works!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "mapRoot": "",
8 | "module": "commonjs",
9 | "moduleResolution": "node",
10 | "noEmitOnError": true,
11 | "noImplicitAny": false,
12 | "rootDir": ".",
13 | "sourceMap": true,
14 | "sourceRoot": "/",
15 | "target": "es5"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | *.launch
16 | .settings/
17 |
18 | # misc
19 | /.sass-cache
20 | /connect.lock
21 | /coverage/*
22 | /libpeerconnection.log
23 | npm-debug.log
24 | testem.log
25 | /typings
26 |
27 | # e2e
28 | /e2e/*.js
29 | /e2e/*.map
30 |
31 | #System Files
32 | .DS_Store
33 | Thumbs.db
34 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "mapRoot": "/",
8 | "module": "commonjs",
9 | "moduleResolution": "node",
10 | "noEmitOnError": true,
11 | "noImplicitAny": false,
12 | "outDir": "../dist/",
13 | "rootDir": ".",
14 | "sourceMap": true,
15 | "target": "es5",
16 | "inlineSources": true
17 | },
18 |
19 | "files": [
20 | "main.ts",
21 | "typings.d.ts"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular2RxJS
2 |
3 | This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.10.
4 |
5 | ## Development server
6 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
7 |
8 | ### Why I made This
9 |
10 | After reading the article 'Tackling State' by Victor Savkin, I really wanted to implement the solution he described myself and see how it actually looks and feels.
11 |
12 | The article can be found here Tackling State in Angular2
13 |
--------------------------------------------------------------------------------
/src/app/filter-selector/filter-selector.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { FilterLink } from './filter-link/';
4 |
5 | @Component({
6 | moduleId: module.id,
7 | selector: 'filter-selector',
8 | template: `
9 | All
10 | Active
11 | Completed
12 | `,
13 | directives: [FilterLink],
14 | })
15 | export class FilterSelectorComponent {
16 | constructor() {
17 | // nothing to do here
18 | }
19 | }
--------------------------------------------------------------------------------
/angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "project": {
3 | "version": "1.0.0-beta.10",
4 | "name": "angular2-rx-js"
5 | },
6 | "apps": [
7 | {
8 | "main": "src/main.ts",
9 | "tsconfig": "src/tsconfig.json",
10 | "mobile": false
11 | }
12 | ],
13 | "addons": [],
14 | "packages": [],
15 | "e2e": {
16 | "protractor": {
17 | "config": "config/protractor.conf.js"
18 | }
19 | },
20 | "test": {
21 | "karma": {
22 | "config": "config/karma.conf.js"
23 | }
24 | },
25 | "defaults": {
26 | "prefix": "app",
27 | "sourceDir": "src",
28 | "styleExt": "css",
29 | "prefixInterfaces": false,
30 | "lazyRoutePrefix": "+"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/angular-cli-build.js:
--------------------------------------------------------------------------------
1 | // Angular-CLI build configuration
2 | // This file lists all the node_modules files that will be used in a build
3 | // Also see https://github.com/angular/angular-cli/wiki/3rd-party-libs
4 |
5 | /* global require, module */
6 |
7 | var Angular2App = require('angular-cli/lib/broccoli/angular2-app');
8 |
9 | module.exports = function(defaults) {
10 | return new Angular2App(defaults, {
11 | vendorNpmFiles: [
12 | 'systemjs/dist/system-polyfills.js',
13 | 'systemjs/dist/system.src.js',
14 | 'zone.js/dist/**/*.+(js|js.map)',
15 | 'es6-shim/es6-shim.js',
16 | 'reflect-metadata/**/*.+(ts|js|js.map)',
17 | 'rxjs/**/*.+(js|js.map)',
18 | '@angular/**/*.+(js|js.map)'
19 | ]
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular2RxJS
6 |
7 |
8 | {{#unless environment.production}}
9 |
10 | {{/unless}}
11 |
12 |
13 |
14 |
15 | Loading...
16 |
17 | {{#each scripts.polyfills}}
18 |
19 | {{/each}}
20 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/config/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js
3 |
4 | /*global jasmine */
5 | var SpecReporter = require('jasmine-spec-reporter');
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | '../e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | useAllAngular2AppRoots: true,
24 | beforeLaunch: function() {
25 | require('ts-node').register({
26 | project: 'e2e'
27 | });
28 | },
29 | onPrepare: function() {
30 | jasmine.getEnv().addReporter(new SpecReporter());
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs/Observable';
4 |
5 | import { AppState, stateAndDispatcher, state } from './shared/';
6 | import { TodoList} from './todo-list';
7 | import { FilterSelectorComponent } from './filter-selector/';
8 | import { AddTodoComponent } from './add-todo/';
9 |
10 | @Component({
11 | moduleId: module.id,
12 | selector: 'app-root',
13 | template: `
14 |
15 |
16 |
17 | `,
18 | directives: [
19 | TodoList,
20 | AddTodoComponent,
21 | FilterSelectorComponent
22 | ],
23 | providers: stateAndDispatcher,
24 | })
25 | export class AppComponent {
26 | constructor(@Inject(state) private state: Observable) {
27 | // nothing to do here
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/todo-list/todo/todo.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, Inject, EventEmitter } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs/Observable';
4 |
5 | import { AppState, stateAndDispatcher, state } from '../../shared/';
6 |
7 | @Component({
8 | selector: 'todo',
9 | styles: [
10 | `
11 | div {
12 | cursor: pointer;
13 | }
14 |
15 | div:hover {
16 | color: #6699ff;
17 | }
18 |
19 | .is-completed {
20 | text-decoration: line-through;
21 | color: #ff9966;
22 | }
23 |
24 | .is-completed:hover {
25 | font-weight: bold;
26 | color: #ff9966;
27 | }
28 | `
29 | ],
30 | template: `
31 | {{id}}. {{ text }}
32 | `,
33 | })
34 | export class TodoComponent {
35 | @Input() text: string;
36 | @Input() completed: boolean;
37 | @Input() id: number;
38 | @Output() toggle = new EventEmitter();
39 |
40 | constructor(@Inject(state) private state: Observable) {}
41 |
42 | public clicked(): void {
43 | this.toggle.emit({id: this.id});
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Razvan Milea
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.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-rx-js",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "angular-cli": {},
6 | "scripts": {
7 | "start": "ng serve",
8 | "postinstall": "typings install",
9 | "lint": "tslint \"src/**/*.ts\"",
10 | "test": "ng test",
11 | "pree2e": "webdriver-manager update",
12 | "e2e": "protractor"
13 | },
14 | "private": true,
15 | "dependencies": {
16 | "@angular/common": "2.0.0-rc.4",
17 | "@angular/compiler": "2.0.0-rc.4",
18 | "@angular/core": "2.0.0-rc.4",
19 | "@angular/forms": "0.2.0",
20 | "@angular/http": "2.0.0-rc.4",
21 | "@angular/platform-browser": "2.0.0-rc.4",
22 | "@angular/platform-browser-dynamic": "2.0.0-rc.4",
23 | "@angular/router": "3.0.0-beta.2",
24 | "es6-shim": "0.35.1",
25 | "reflect-metadata": "0.1.3",
26 | "rxjs": "5.0.0-beta.6",
27 | "systemjs": "0.19.26",
28 | "zone.js": "0.6.12"
29 | },
30 | "devDependencies": {
31 | "angular-cli": "1.0.0-beta.10",
32 | "codelyzer": "0.0.20",
33 | "ember-cli-inject-live-reload": "1.4.0",
34 | "jasmine-core": "2.4.1",
35 | "jasmine-spec-reporter": "2.5.0",
36 | "karma": "0.13.22",
37 | "karma-chrome-launcher": "0.2.3",
38 | "karma-jasmine": "0.3.8",
39 | "protractor": "3.3.0",
40 | "ts-node": "0.5.5",
41 | "tslint": "3.11.0",
42 | "typescript": "1.8.10",
43 | "typings": "1.3.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/app/filter-selector/filter-link/filter-link.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, Input } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs/Observable';
4 | import { Observer } from 'rxjs/Observer';
5 | import 'rxjs/add/operator/map';
6 |
7 | import { AppState, stateAndDispatcher, state, dispatcher } from '../../shared/';
8 | import { Action, SetVisibilityFilter } from '../../shared/actions/';
9 |
10 | @Component({
11 | moduleId: module.id,
12 | selector: 'filter-link',
13 | styles: [
14 | `
15 | div {
16 | cursor: pointer;
17 | }
18 |
19 | div:hover {
20 | font-weight: bold;
21 | }
22 | `
23 | ],
24 | template: `
25 | {{ filter }}
26 | `,
27 | })
28 | export class FilterLink {
29 | @Input() public filter: string;
30 |
31 | constructor(@Inject(dispatcher) private dispatcher: Observer,
32 | @Inject(state) private state: Observable) {
33 | // nothing to do here
34 | }
35 |
36 | public get textEffect() {
37 | return this.state.map(s =>
38 | s.visibilityFilter === this.filter ? 'underline' : 'none');
39 | }
40 |
41 | public setVisibilityFilter() {
42 | const action: SetVisibilityFilter = new SetVisibilityFilter(this.filter);
43 |
44 | this.dispatcher.next(action);
45 | }
46 | }
--------------------------------------------------------------------------------
/src/app/add-todo/add-todo.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, OnInit } from '@angular/core';
2 |
3 | import { Observer } from 'rxjs/Observer';
4 | import { Observable } from 'rxjs/Observable';
5 |
6 | import { stateAndDispatcher, dispatcher, state, AppState, Action, AddTodoAction } from '../shared/';
7 |
8 | @Component({
9 | selector: 'add-todo',
10 | template: `
11 |
12 |
13 | `,
14 | providers: stateAndDispatcher,
15 | })
16 | export class AddTodoComponent implements OnInit {
17 | private nextId: number = 0;
18 | private text: string;
19 |
20 | constructor(
21 | @Inject(state) private state: Observable,
22 | @Inject(dispatcher) private dispatcher: Observer) {
23 | // nothing to do here
24 | }
25 |
26 | public ngOnInit(): void {
27 | this.resetText();
28 | this.state.forEach(s => {
29 | this.nextId = s.todos.length;
30 | });
31 | }
32 |
33 | public get isTextEmpty(): boolean {
34 | return this.text === '';
35 | }
36 |
37 | public addTodo(): void {
38 | if (this.isTextEmpty === false) {
39 | const action: AddTodoAction = new AddTodoAction(this.nextId++, this.text);
40 |
41 | this.dispatcher.next(action);
42 |
43 | this.resetText();
44 | }
45 | }
46 |
47 | private resetText(): void {
48 | this.text = '';
49 | }
50 | }
--------------------------------------------------------------------------------
/config/karma-test-shim.js:
--------------------------------------------------------------------------------
1 | // Test shim for Karma, needed to load files via SystemJS
2 |
3 | /*global jasmine, __karma__, window*/
4 | Error.stackTraceLimit = Infinity;
5 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
6 |
7 | __karma__.loaded = function () {
8 | };
9 |
10 | var distPath = '/base/dist/';
11 | var appPaths = ['app']; //Add all valid source code folders here
12 |
13 | function isJsFile(path) {
14 | return path.slice(-3) == '.js';
15 | }
16 |
17 | function isSpecFile(path) {
18 | return path.slice(-8) == '.spec.js';
19 | }
20 |
21 | function isAppFile(path) {
22 | return isJsFile(path) && appPaths.some(function(appPath) {
23 | var fullAppPath = distPath + appPath + '/';
24 | return path.substr(0, fullAppPath.length) == fullAppPath;
25 | });
26 | }
27 |
28 | var allSpecFiles = Object.keys(window.__karma__.files)
29 | .filter(isSpecFile)
30 | .filter(isAppFile);
31 |
32 | // Load our SystemJS configuration.
33 | System.config({
34 | baseURL: distPath
35 | });
36 |
37 | System.import('system-config.js').then(function() {
38 | // Load and configure the TestComponentBuilder.
39 | return Promise.all([
40 | System.import('@angular/core/testing'),
41 | System.import('@angular/platform-browser-dynamic/testing')
42 | ]).then(function (providers) {
43 | var testing = providers[0];
44 | var testingBrowser = providers[1];
45 |
46 | testing.setBaseTestProviders(testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
47 | testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
48 | });
49 | }).then(function() {
50 | // Finally, load all spec files.
51 | // This will run the tests directly.
52 | return Promise.all(
53 | allSpecFiles.map(function (moduleName) {
54 | return System.import(moduleName);
55 | }));
56 | }).then(__karma__.start, __karma__.error);
57 |
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '..',
7 | frameworks: ['jasmine'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher')
11 | ],
12 | customLaunchers: {
13 | // chrome setup for travis CI using chromium
14 | Chrome_travis_ci: {
15 | base: 'Chrome',
16 | flags: ['--no-sandbox']
17 | }
18 | },
19 | files: [
20 | { pattern: 'dist/vendor/es6-shim/es6-shim.js', included: true, watched: false },
21 | { pattern: 'dist/vendor/zone.js/dist/zone.js', included: true, watched: false },
22 | { pattern: 'dist/vendor/reflect-metadata/Reflect.js', included: true, watched: false },
23 | { pattern: 'dist/vendor/systemjs/dist/system-polyfills.js', included: true, watched: false },
24 | { pattern: 'dist/vendor/systemjs/dist/system.src.js', included: true, watched: false },
25 | { pattern: 'dist/vendor/zone.js/dist/async-test.js', included: true, watched: false },
26 | { pattern: 'dist/vendor/zone.js/dist/fake-async-test.js', included: true, watched: false },
27 |
28 | { pattern: 'config/karma-test-shim.js', included: true, watched: true },
29 |
30 | // Distribution folder.
31 | { pattern: 'dist/**/*', included: false, watched: true }
32 | ],
33 | exclude: [
34 | // Vendor packages might include spec files. We don't want to use those.
35 | 'dist/vendor/**/*.spec.js'
36 | ],
37 | preprocessors: {},
38 | reporters: ['progress'],
39 | port: 9876,
40 | colors: true,
41 | logLevel: config.LOG_INFO,
42 | autoWatch: true,
43 | browsers: ['Chrome'],
44 | singleRun: false
45 | });
46 | };
47 |
--------------------------------------------------------------------------------
/src/system-config.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // SystemJS configuration file, see links for more information
4 | // https://github.com/systemjs/systemjs
5 | // https://github.com/systemjs/systemjs/blob/master/docs/config-api.md
6 |
7 | /***********************************************************************************************
8 | * User Configuration.
9 | **********************************************************************************************/
10 | /** Map relative paths to URLs. */
11 | const map: any = {
12 | };
13 |
14 | /** User packages configuration. */
15 | const packages: any = {
16 | };
17 |
18 | ////////////////////////////////////////////////////////////////////////////////////////////////
19 | /***********************************************************************************************
20 | * Everything underneath this line is managed by the CLI.
21 | **********************************************************************************************/
22 | const barrels: string[] = [
23 | // Angular specific barrels.
24 | '@angular/core',
25 | '@angular/common',
26 | '@angular/compiler',
27 | '@angular/forms',
28 | '@angular/http',
29 | '@angular/router',
30 | '@angular/platform-browser',
31 | '@angular/platform-browser-dynamic',
32 |
33 | // Thirdparty barrels.
34 | 'rxjs',
35 |
36 | // App specific barrels.
37 | 'app',
38 | 'app/shared',
39 | 'app/todo-list/todo',
40 | 'app/todo-list',
41 | 'app/add-todo',
42 | 'app/filter-selector',
43 | 'app/filter-selector/filter-link',
44 | /** @cli-barrel */
45 | ];
46 |
47 | const cliSystemConfigPackages: any = {};
48 | barrels.forEach((barrelName: string) => {
49 | cliSystemConfigPackages[barrelName] = { main: 'index' };
50 | });
51 |
52 | /** Type declaration for ambient System. */
53 | declare var System: any;
54 |
55 | // Apply the CLI SystemJS configuration.
56 | System.config({
57 | map: {
58 | '@angular': 'vendor/@angular',
59 | 'rxjs': 'vendor/rxjs',
60 | 'main': 'main.js'
61 | },
62 | packages: cliSystemConfigPackages
63 | });
64 |
65 | // Apply the user's configuration.
66 | System.config({ map, packages });
67 |
--------------------------------------------------------------------------------
/src/app/todo-list/todo-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs/Observable';
4 | import { Observer } from 'rxjs/Observer';
5 | import 'rxjs/add/operator/map';
6 |
7 | import { AppState, stateAndDispatcher, state, dispatcher, ToDoItem } from '../shared/stateAndDispatcher';
8 | import { Action, ToggleTodoAction } from '../shared/actions/';
9 | import { TodoComponent } from './todo/';
10 |
11 | @Component({
12 | selector: 'todo-list',
13 | template: `
14 |
19 |
20 | `,
21 | directives: [TodoComponent],
22 | })
23 | export class TodoList {
24 | constructor(@Inject(state) private state: Observable,
25 | @Inject(dispatcher) private dispatcher: Observer) {
26 | }
27 |
28 | public get filtered() {
29 | return this.state.map(s => this.getVisibleTodos(s.todos, s.visibilityFilter));
30 | }
31 |
32 | public emitToggle(data) {
33 | const action: ToggleTodoAction = new ToggleTodoAction(data.id);
34 | this.dispatcher.next(action);
35 | }
36 |
37 | private getVisibleTodos(todoList: Array, visibilityFilter: string): Array {
38 | let items = [];
39 | todoList.forEach(item => {
40 | let ok: boolean = false;
41 | switch (visibilityFilter) {
42 | case 'SHOW_ALL':
43 | ok = true;
44 | break;
45 | case 'SHOW_ACTIVE':
46 | ok = item.completed === false;
47 | break;
48 | case 'SHOW_COMPLETED':
49 | ok = item.completed === true;
50 | break;
51 | default:
52 | ok = false;
53 | }
54 |
55 | if (ok) {
56 | items.push(item);
57 | }
58 | });
59 |
60 | return items;
61 | }
62 | }
--------------------------------------------------------------------------------
/src/app/shared/stateAndDispatcher.ts:
--------------------------------------------------------------------------------
1 | import { OpaqueToken } from '@angular/core';
2 |
3 | import { Subject } from 'rxjs/Subject';
4 | import { Observable } from 'rxjs/Observable';
5 | import { BehaviorSubject } from 'rxjs/BehaviorSubject';
6 | import 'rxjs/add/operator/zip';
7 | import 'rxjs/add/operator/map';
8 | import 'rxjs/add/operator/scan';
9 |
10 | import { Action, ToggleTodoAction, SetVisibilityFilter, AddTodoAction } from './actions/';
11 |
12 | export class ToDoItem {
13 | constructor(public id: number, public text: string, public completed: boolean = false) {}
14 | }
15 |
16 | export class AppState {
17 | public todos: Array;
18 | public visibilityFilter;
19 | constructor() {}
20 | }
21 |
22 | export const initState = new OpaqueToken('initState');
23 | export const dispatcher = new OpaqueToken('dispatcher');
24 | export const state = new OpaqueToken('state');
25 |
26 | // this is what is actually injected in the app component when using 'providers: stateAndDispatcher'
27 | export const stateAndDispatcher = [
28 | {
29 | provide: initState,
30 | useValue: {
31 | todos: [ //add some default values, ey?
32 | new ToDoItem(0, 'Eeny', false),
33 | new ToDoItem(1, 'Meeny', false),
34 | new ToDoItem(2, 'Miny', true),
35 | new ToDoItem(3, 'Moe', false),
36 | ],
37 | visibilityFilter: 'SHOW_ALL'
38 | }
39 | },
40 | {
41 | provide: dispatcher,
42 | useValue: new Subject(null)
43 | },
44 | {
45 | provide: state,
46 | useFactory: stateFn,
47 | deps: [initState, dispatcher]
48 | }
49 | ];
50 |
51 | function stateFn(initState: AppState, actions: Observable): Observable {
52 | const combine = s => ({todos: s[0], visibilityFilter: s[1]});
53 | const appStateObs: Observable =
54 | todos(initState.todos, actions)
55 | .zip(filter(initState.visibilityFilter, actions))
56 | .map(combine);
57 | return wrapIntoBehavior(initState, appStateObs);
58 | }
59 |
60 | function wrapIntoBehavior(init, obs) {
61 | const res = new BehaviorSubject(init);
62 | obs.subscribe(s => res.next(s));
63 | return res;
64 | }
65 |
66 | function todos(initState: any, actions: Observable): Observable {
67 | return actions.scan((state, action) => {
68 | if (action instanceof AddTodoAction) {
69 | const newTodo = {
70 | id: action.todoId,
71 | text: action.text,
72 | completed: false
73 | };
74 | return [...state, newTodo];
75 | } else {
76 | return state.map(t => updateTodo(t, action));
77 | }
78 | }, initState);
79 | }
80 |
81 | function updateTodo(todo: ToDoItem, action: Action): ToDoItem {
82 | if (action instanceof ToggleTodoAction) {
83 | return (action.id !== todo.id) ? todo : merge(todo, {completed: !todo.completed});
84 | } else {
85 | return todo;
86 | }
87 | }
88 |
89 | function filter(initState: string, actions: Observable): Observable {
90 | return actions.scan((state, action) => {
91 | if (action instanceof SetVisibilityFilter) {
92 | return action.filter;
93 | } else {
94 | return state;
95 | }
96 | }, initState);
97 | }
98 |
99 | function merge(todo: ToDoItem, props: any): any {
100 | return Object.assign({}, todo, props);
101 | }
--------------------------------------------------------------------------------