├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── ch3 │ ├── arrow-functions │ │ ├── arrow-functions.ts │ │ ├── context-demo.ts │ │ └── simple-reduce.ts │ ├── decorators │ │ └── nonenumerable.ts │ ├── es6-classes │ │ └── sample-classes.ts │ ├── hello-world │ │ └── hello-world.ts │ ├── let │ │ ├── let.ts │ │ └── var.ts │ ├── modules │ │ ├── app.ts │ │ ├── app2.ts │ │ ├── app3.ts │ │ ├── app4.ts │ │ ├── app5.ts │ │ ├── math.ts │ │ ├── math2.ts │ │ └── math3.ts │ └── object-literals │ │ ├── enhanced-object-literal.ts │ │ └── simple-object.ts ├── ch4 │ ├── es5 │ │ ├── hello-world │ │ │ ├── app.js │ │ │ ├── index.html │ │ │ └── meta.json │ │ ├── tabs │ │ │ ├── app.html │ │ │ ├── index.html │ │ │ └── tabs.js │ │ └── tooltip │ │ │ ├── app.html │ │ │ ├── index.html │ │ │ └── tooltip.js │ └── ts │ │ ├── basic-tab-content-children │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── basic-tab │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── change_detection_strategy │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── change_detection_strategy_broken │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── change_detection_strategy_order │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── custom-element │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── hello-world │ │ ├── app.html │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── inputs-outputs │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── life-cycle │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── ng-content │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── ng-for │ │ ├── detailed-syntax │ │ │ ├── app.html │ │ │ ├── app.ts │ │ │ ├── index.html │ │ │ └── meta.json │ │ └── syntax-sugar │ │ │ ├── app.html │ │ │ ├── app.ts │ │ │ ├── index.html │ │ │ └── meta.json │ │ ├── template-ref │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── todo-app │ │ ├── app.html │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── tooltip │ │ ├── app.html │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── view-child-content-child │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ └── zippy │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json ├── ch5 │ ├── es5 │ │ └── simple-example │ │ │ ├── app.js │ │ │ ├── index.html │ │ │ └── meta.json │ └── ts │ │ ├── configuring-providers │ │ ├── dummy-http.ts │ │ ├── existing.ts │ │ ├── factory.ts │ │ └── multi-providers.ts │ │ ├── decorators │ │ ├── optional.ts │ │ ├── self.ts │ │ └── skip-self.ts │ │ ├── directives-ngmodules │ │ ├── app.ts │ │ ├── index.html │ │ ├── meta.json │ │ └── util.js │ │ ├── directives │ │ ├── app.ts │ │ ├── index.html │ │ ├── meta.json │ │ └── util.js │ │ ├── injector-basics │ │ ├── forward-ref.ts │ │ └── injector.ts │ │ └── parent-child │ │ └── simple-example.ts ├── ch6 │ └── ts │ │ ├── multi-page-template-driven │ │ ├── add_developer.html │ │ ├── add_developer.ts │ │ ├── app.ts │ │ ├── control_errors.ts │ │ ├── developer.ts │ │ ├── developer_collection.ts │ │ ├── email_validator.ts │ │ ├── home.html │ │ ├── home.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── simple-two-way-data-binding │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── step-0 │ │ ├── app.ts │ │ ├── developer.ts │ │ ├── developer_collection.ts │ │ └── index.html │ │ ├── step-1-async │ │ ├── add_developer.ts │ │ ├── app.ts │ │ ├── developer.ts │ │ ├── developer_collection.ts │ │ ├── home.ts │ │ └── index.html │ │ ├── step-1 │ │ ├── add_developer.ts │ │ ├── app.ts │ │ ├── developer.ts │ │ ├── developer_collection.ts │ │ ├── home.ts │ │ └── index.html │ │ └── step-2 │ │ ├── add_developer.html │ │ ├── add_developer.ts │ │ ├── app.ts │ │ ├── control_errors.ts │ │ ├── developer.ts │ │ ├── developer_collection.ts │ │ ├── email_validator.ts │ │ ├── home.html │ │ ├── home.ts │ │ └── index.html ├── ch7 │ └── ts │ │ ├── async_pipe │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── builtin_pipes │ │ ├── app.ts │ │ ├── index.html │ │ └── meta.json │ │ ├── multi-page-model-driven │ │ ├── add_developer.html │ │ ├── add_developer.ts │ │ ├── app.ts │ │ ├── boolean_pipe.ts │ │ ├── control_errors.ts │ │ ├── developer.ts │ │ ├── developer_advanced_info.ts │ │ ├── developer_basic_info.ts │ │ ├── developer_collection.ts │ │ ├── developer_details.ts │ │ ├── github_gateway.ts │ │ ├── home.html │ │ ├── home.ts │ │ ├── index.html │ │ └── meta.json │ │ └── statful_pipe │ │ ├── app.ts │ │ ├── fetch_json_pipe.ts │ │ ├── index.html │ │ └── meta.json ├── ch8 │ └── ts │ │ └── todo_webworkers │ │ ├── background_app.ts │ │ ├── bootstrap.ts │ │ ├── config.ts │ │ ├── index.html │ │ ├── loader.ts │ │ └── meta.json ├── index.html ├── system.config.js └── tsconfig.json ├── appveyor.yml ├── gulpfile.ts ├── img └── book-ed1.jpg ├── manual_typings ├── connect-livereload.d.ts ├── gulp-load-plugins.d.ts ├── jsonfile.d.ts ├── karma.d.ts ├── merge-stream.d.ts ├── open.d.ts ├── run-sequence.d.ts ├── slash.d.ts ├── systemjs-builder.d.ts ├── tiny-lr.d.ts └── yargs.d.ts ├── package.json ├── tools ├── config.ts ├── tasks │ ├── build.assets.dev.ts │ ├── build.bundles.ts │ ├── build.deps.ts │ ├── build.docs.ts │ ├── build.html_css.prod.ts │ ├── build.index.ts │ ├── build.js.dev.ts │ ├── build.js.prod.ts │ ├── build.test.ts │ ├── check.versions.ts │ ├── clean.ts │ ├── karma.start.ts │ ├── npm.ts │ ├── serve.docs.ts │ ├── server.start.ts │ ├── tsd.ts │ ├── tslint.ts │ ├── watch.dev.ts │ ├── watch.serve.ts │ └── watch.test.ts ├── utils.ts └── utils │ ├── server.ts │ ├── tasks_tools.ts │ ├── template_injectables.ts │ └── template_locals.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | .tsdrc 30 | 31 | #IDE configuration files 32 | .idea 33 | .vscode 34 | 35 | dist 36 | dev 37 | docs 38 | lib 39 | test 40 | tools/typings/tsd 41 | typings 42 | tmp 43 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "immed": true, 4 | "newcap": true, 5 | "noarg": true, 6 | "noempty": true, 7 | "nonew": true, 8 | "trailing": true, 9 | "maxlen": 200, 10 | "boss": true, 11 | "eqnull": true, 12 | "expr": true, 13 | "globalstrict": true, 14 | "laxbreak": true, 15 | "loopfunc": true, 16 | "sub": true, 17 | "undef": true, 18 | "indent": 2, 19 | "unused": true, 20 | 21 | "node": true, 22 | "globals": { 23 | "System": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.0' 4 | - '4.1' 5 | - '5.1' 6 | sudo: false 7 | services: 8 | before_install: 9 | - npm update -g npm 10 | - npm --version 11 | - export CHROME_BIN=chromium-browser 12 | - export DISPLAY=:99.0 13 | - sh -e /etc/init.d/xvfb start 14 | before_script: 15 | notifications: 16 | email: true 17 | after_failure: cat /home/travis/build/mgechev/angular2-seed/npm-debug.log 18 | branches: 19 | only: 20 | - master 21 | env: 22 | global: 23 | # https://github.com/DefinitelyTyped/tsd#tsdrc 24 | # Token has no scope (read-only access to public information) 25 | - TSD_GITHUB_TOKEN=9b18c72997769f3867ef2ec470e626d39661795d 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting Pull Requests 2 | 3 | **Please follow these basic steps to simplify pull request reviews - if you don't you'll probably just be asked to anyway.** 4 | 5 | * Please rebase your branch against the current master 6 | * Run ```npm install``` to make sure your development dependencies are up-to-date 7 | * Please ensure that the test suite passes **and** that code is lint free before submitting a PR by running: 8 | * ```npm test``` 9 | * If you've added new functionality, **please** include tests which validate its behaviour 10 | * Make reference to possible [issues](https://github.com/mgechev/angular2-seed/issues) on PR comment 11 | 12 | ## Submitting bug reports 13 | 14 | * Please detail the affected browser(s) and operating system(s) 15 | * Please be sure to state which version of node **and** npm you're using 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Minko Gechev 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This repository contains the code samples for my book "Getting started with Angular". 4 | 5 | # How to start 6 | 7 | **Note** that this seed project requires node v4.x.x or higher and npm 2.14.7. 8 | 9 | ```bash 10 | git clone --depth 1 https://github.com/mgechev/getting-started-with-angular.git 11 | cd getting-started-with-angular 12 | npm install 13 | npm start 14 | ``` 15 | 16 | # First edition 17 | 18 | The code for the first edition could be found [here](https://github.com/mgechev/switching-to-angular2). 19 | 20 | # License 21 | 22 | MIT 23 | 24 | -------------------------------------------------------------------------------- /app/ch3/arrow-functions/arrow-functions.ts: -------------------------------------------------------------------------------- 1 | var result = [1, 2, 3] 2 | .reduce((total, current) => total + current, 0); 3 | 4 | console.log(result); 5 | 6 | var even = [3, 1, 56, 7].filter(el => !(el % 2)); 7 | 8 | console.log(even); 9 | 10 | var data = []; 11 | var sorted = data.sort((a, b) => { 12 | var diff = a.price - b.price; 13 | if (diff !== 0) { 14 | return diff; 15 | } 16 | return a.total - b.total; 17 | }); 18 | -------------------------------------------------------------------------------- /app/ch3/arrow-functions/context-demo.ts: -------------------------------------------------------------------------------- 1 | function MyComponent() { 2 | this.age = 42; 3 | setTimeout(() => { 4 | this.age += 1; 5 | console.log(this.age); 6 | }, 100); 7 | } 8 | 9 | new MyComponent(); // 43 in 100ms. 10 | 11 | -------------------------------------------------------------------------------- /app/ch3/arrow-functions/simple-reduce.ts: -------------------------------------------------------------------------------- 1 | // ch3/arrow-functions/simple-reduce.ts 2 | var result = [1, 2, 3].reduce(function (total, current) { 3 | return total + current; 4 | }, 0); // 6 5 | 6 | -------------------------------------------------------------------------------- /app/ch3/decorators/nonenumerable.ts: -------------------------------------------------------------------------------- 1 | class Person { 2 | @nonenumerable 3 | get kidCount() { 4 | return 42; 5 | } 6 | } 7 | 8 | function nonenumerable(target, name, descriptor) { 9 | descriptor.enumerable = false; 10 | return descriptor; 11 | } 12 | 13 | var person = new Person(); 14 | 15 | for (let prop in person) { 16 | console.log(prop); 17 | } 18 | 19 | console.log(person.kidCount); 20 | -------------------------------------------------------------------------------- /app/ch3/es6-classes/sample-classes.ts: -------------------------------------------------------------------------------- 1 | class Human { 2 | static totalPeople = 0; 3 | _name; // ES2016 property declaration syntax 4 | constructor(name) { 5 | this._name = name; 6 | Human.totalPeople += 1; 7 | } 8 | get name() { 9 | return this._name; 10 | } 11 | set name(val) { 12 | this._name = val; 13 | } 14 | talk() { 15 | return `Hi, I'm ${this.name}!`; 16 | } 17 | } 18 | 19 | class Developer extends Human { 20 | _languages; // ES2016 property declaration syntax 21 | constructor(name, languages) { 22 | super(name); 23 | this._languages = languages; 24 | } 25 | get languages() { 26 | return this._languages; 27 | } 28 | talk() { 29 | return `${super.talk()} And I know ${this.languages.join(', ')}.`; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/ch3/hello-world/hello-world.ts: -------------------------------------------------------------------------------- 1 | console.log('Hello world!'); 2 | -------------------------------------------------------------------------------- /app/ch3/let/let.ts: -------------------------------------------------------------------------------- 1 | // Available in ES2015 2 | // var fns = []; 3 | // for (let i = 0; i < 5; i += 1) { 4 | // fns.push(function() { 5 | // console.log(i); 6 | // }) 7 | // } 8 | // fns.forEach(fn => fn()); 9 | 10 | -------------------------------------------------------------------------------- /app/ch3/let/var.ts: -------------------------------------------------------------------------------- 1 | var fns = []; 2 | for (var i = 0; i < 5; i += 1) { 3 | fns.push(function() { 4 | console.log(i); 5 | }) 6 | } 7 | fns.forEach(fn => fn()); 8 | 9 | -------------------------------------------------------------------------------- /app/ch3/modules/app.ts: -------------------------------------------------------------------------------- 1 | // app.ts 2 | import {square, log10} from './math'; 3 | console.log(square(2)); // 4 4 | console.log(log10(10)); // 1 5 | -------------------------------------------------------------------------------- /app/ch3/modules/app2.ts: -------------------------------------------------------------------------------- 1 | import * as math from './math'; 2 | console.log(math.square(2)); // 4 3 | console.log(math.log10(10)); // 1 4 | console.log(math.PI); // 3.141592653589793 5 | -------------------------------------------------------------------------------- /app/ch3/modules/app3.ts: -------------------------------------------------------------------------------- 1 | import cube from './math3'; 2 | console.log(cube(3)); // 27 3 | 4 | -------------------------------------------------------------------------------- /app/ch3/modules/app4.ts: -------------------------------------------------------------------------------- 1 | import cube, { square } from './math3'; 2 | console.log(square(2)); // 4 3 | console.log(cube(3)); // 27 4 | 5 | -------------------------------------------------------------------------------- /app/ch3/modules/app5.ts: -------------------------------------------------------------------------------- 1 | import { default as cube } from './math3'; 2 | console.log(cube(3)); // 27 3 | 4 | -------------------------------------------------------------------------------- /app/ch3/modules/math.ts: -------------------------------------------------------------------------------- 1 | // math.ts 2 | export function square(x) { 3 | return Math.pow(x, 2); 4 | }; 5 | export function log10(x) { 6 | return Math.log10(x); 7 | }; 8 | export const PI = Math.PI; -------------------------------------------------------------------------------- /app/ch3/modules/math2.ts: -------------------------------------------------------------------------------- 1 | // math.ts 2 | function square(x) { 3 | return Math.pow(x, 2); 4 | }; 5 | function log10(x) { 6 | return Math.log10(x); 7 | }; 8 | const PI = Math.PI; 9 | export { square, log10, PI }; -------------------------------------------------------------------------------- /app/ch3/modules/math3.ts: -------------------------------------------------------------------------------- 1 | export default function cube(x) { 2 | return Math.pow(x, 3); 3 | }; 4 | export function square(x) { 5 | return Math.pow(x, 2); 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /app/ch3/object-literals/enhanced-object-literal.ts: -------------------------------------------------------------------------------- 1 | var selector = 'hello-world'; 2 | var templateUrl = 'hello-world.html'; 3 | var Component = { 4 | selector, 5 | templateUrl 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /app/ch3/object-literals/simple-object.ts: -------------------------------------------------------------------------------- 1 | var selector = 'hello-world'; 2 | var templateUrl = 'hello-world.html'; 3 | var Component = { 4 | selector: selector, 5 | templateUrl: templateUrl 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /app/ch4/es5/hello-world/app.js: -------------------------------------------------------------------------------- 1 | var AppComponent = ng.core.Component({ 2 | selector: 'my-app', 3 | template: '

Hello {{target}}!

' 4 | }) 5 | .Class({ 6 | constructor: function () { 7 | this.target = 'world'; 8 | } 9 | }); 10 | 11 | var AppModule = ng.core.NgModule({ 12 | imports: [ng.platformBrowser.BrowserModule], 13 | declarations: [AppComponent], 14 | bootstrap: [AppComponent] 15 | }) 16 | .Class({ 17 | constructor: function () {} 18 | }); 19 | 20 | ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(AppModule); 21 | 22 | -------------------------------------------------------------------------------- /app/ch4/es5/hello-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= TITLE %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/es5/hello-world/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Hello world in ES5", 3 | "description": "Hello world in ES5", 4 | "id": 1, 5 | "presented": true 6 | } 7 | -------------------------------------------------------------------------------- /app/ch4/es5/tabs/app.html: -------------------------------------------------------------------------------- 1 | 2 | First tab content 3 | Second tab content 4 | Third tab content 5 | 6 | -------------------------------------------------------------------------------- /app/ch4/es5/tabs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/es5/tabs/tabs.js: -------------------------------------------------------------------------------- 1 | var Tabs = ng.core.Component({ 2 | selector: 'tabs', 3 | styles: [` 4 | .tab { 5 | display: inline-block; 6 | } 7 | .tab-header { 8 | list-style: none; 9 | padding: 0; 10 | margin: 0; 11 | } 12 | .tab-header .is-active { 13 | background-color: #eee; 14 | } 15 | .tab-header li { 16 | display: inline-block; 17 | cursor: pointer; 18 | padding: 5px; 19 | border: 1px solid #ccc; 20 | } 21 | .tab-content { 22 | border: 1px solid #ccc; 23 | border-top: none; 24 | padding: 5px; 25 | } 26 | ` 27 | ], 28 | template: ` 29 |
30 | 33 |
34 | 35 |
36 |
37 | ` 38 | }) 39 | .Class({ 40 | constructor: function () { 41 | this.tabs = []; 42 | }, 43 | addTab: function (tab) { 44 | var total = this.tabs.length; 45 | this.tabs.push(tab); 46 | return total; 47 | }, 48 | selectTab(idx) { 49 | this.tabs.forEach(function (t) { 50 | t.setActive(false); 51 | }); 52 | this.tabs[idx].setActive(true); 53 | } 54 | }); 55 | 56 | var Tab = ng.core.Component({ 57 | selector: 'tab', 58 | inputs: ['tabTitle'], 59 | template: `
60 | 61 |
` 62 | }) 63 | .Class({ 64 | constructor: [[ng.core.Inject(Tabs), ng.core.Host()], function (tabs) { 65 | this.active = !tabs.addTab(this); 66 | }], 67 | setActive: function (isActive) { 68 | this.active = isActive; 69 | }, 70 | isActive: function () { 71 | return this.active; 72 | } 73 | }); 74 | 75 | var App = ng.core.Component({ 76 | selector: 'app', 77 | templateUrl: './app.html' 78 | }) 79 | .Class({ 80 | constructor: function () {} 81 | }); 82 | 83 | 84 | var AppModule = ng.core.NgModule({ 85 | imports: [ng.platformBrowser.BrowserModule], 86 | declarations: [App, Tab, Tabs], 87 | bootstrap: [App] 88 | }) 89 | .Class({ 90 | constructor: function () {} 91 | }); 92 | 93 | ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(AppModule); 94 | 95 | -------------------------------------------------------------------------------- /app/ch4/es5/tooltip/app.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /app/ch4/es5/tooltip/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/ch4/es5/tooltip/tooltip.js: -------------------------------------------------------------------------------- 1 | var Overlay = ng.core.Class({ 2 | constructor: function () { 3 | var el = document.createElement('div'); 4 | el.className = 'tooltip'; 5 | this.el = el; 6 | }, 7 | close: function () { 8 | this.el.hidden = true; 9 | }, 10 | open: function (el, text) { 11 | this.el.innerHTML = text; 12 | this.el.hidden = false; 13 | var rect = el.nativeElement.getBoundingClientRect(); 14 | this.el.style.left = rect.left + 'px'; 15 | this.el.style.top = rect.top + 'px'; 16 | }, 17 | attach: function (target) { 18 | target.appendChild(this.el); 19 | }, 20 | detach: function () { 21 | this.el.parentNode.removeChild(this.el); 22 | } 23 | }); 24 | 25 | var Tooltip = ng.core.Directive({ 26 | selector: '[tooltip]', 27 | inputs: ['tooltip'], 28 | host: { 29 | '(mouseenter)': 'onMouseEnter()', 30 | '(mouseleave)': 'onMouseLeave()' 31 | } 32 | }) 33 | .Class({ 34 | constructor: [ng.core.ElementRef, Overlay, function (el, overlay) { 35 | this.el = el; 36 | this.overlay = overlay; 37 | overlay.attach(this.el.nativeElement); 38 | }], 39 | onMouseEnter() { 40 | this.overlay.open(this.el, this.tooltip); 41 | }, 42 | onMouseLeave() { 43 | this.overlay.close(); 44 | } 45 | }); 46 | 47 | var App = ng.core.Component({ 48 | selector: 'app', 49 | templateUrl: './app.html', 50 | }) 51 | .Class({ 52 | constructor: function () {} 53 | }); 54 | 55 | var AppModule = ng.core.NgModule({ 56 | imports: [ng.platformBrowser.BrowserModule], 57 | declarations: [App, Tooltip], 58 | providers: [Overlay], 59 | bootstrap: [App] 60 | }) 61 | .Class({ 62 | constructor: function () {} 63 | }); 64 | 65 | ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(AppModule); 66 | 67 | -------------------------------------------------------------------------------- /app/ch4/ts/basic-tab-content-children/app.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | Inject, 4 | EventEmitter, 5 | Output, 6 | Component, 7 | forwardRef, 8 | Host, 9 | Attribute, 10 | ContentChildren, 11 | QueryList, 12 | NgModule 13 | } from '@angular/core'; 14 | 15 | import {BrowserModule} from '@angular/platform-browser'; 16 | 17 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 18 | 19 | @Component({ 20 | selector: 'tab-title', 21 | styles: [` 22 | .tab-title { 23 | display: inline-block; 24 | cursor: pointer; 25 | padding: 5px; 26 | border: 1px solid #ccc; 27 | } 28 | `], 29 | template: ` 30 |
31 | 32 |
33 | ` 34 | }) 35 | class TabTitle { 36 | @Output('selected') 37 | tabSelected: EventEmitter = new EventEmitter(); 38 | handleClick() { 39 | this.tabSelected.emit(this); 40 | } 41 | } 42 | 43 | @Component({ 44 | selector: 'tab-content', 45 | styles: [` 46 | .tab-content { 47 | border: 1px solid #ccc; 48 | border-top: none; 49 | padding: 5px; 50 | } 51 | `], 52 | template: ` 53 |
54 | 55 |
56 | ` 57 | }) 58 | class TabContent { 59 | isActive: boolean = false; 60 | } 61 | 62 | @Component({ 63 | selector: 'tabs', 64 | styles: [ 65 | ` 66 | .tab { 67 | display: inline-block; 68 | } 69 | .tab-nav { 70 | list-style: none; 71 | padding: 0; 72 | margin: 0; 73 | } 74 | ` 75 | ], 76 | template: ` 77 |
78 |
79 | 80 |
81 | 82 |
83 | ` 84 | }) 85 | class Tabs { 86 | @Output('changed') 87 | tabChanged: EventEmitter = new EventEmitter(); 88 | 89 | @ContentChildren(TabTitle) 90 | tabTitles: QueryList; 91 | 92 | @ContentChildren(TabContent) 93 | tabContents: QueryList; 94 | 95 | active: number; 96 | 97 | select(index: number) { 98 | let contents: TabContent[] = this.tabContents.toArray(); 99 | contents[this.active].isActive = false; 100 | this.active = index; 101 | contents[this.active].isActive = true; 102 | this.tabChanged.emit(index); 103 | } 104 | ngAfterContentInit() { 105 | this.tabTitles 106 | .map(t => t.tabSelected) 107 | .forEach((t, i) => { 108 | t.subscribe(_ => { 109 | this.select(i) 110 | }); 111 | }); 112 | this.active = 0; 113 | this.select(0); 114 | } 115 | } 116 | 117 | @Component({ 118 | selector: 'app', 119 | template: ` 120 | 121 | Tab 1 122 | Content 1 123 | Tab 2 124 | Content 2 125 | 126 | ` 127 | }) 128 | class App { 129 | tabChanged(tab) { 130 | console.log(tab); 131 | } 132 | } 133 | 134 | @NgModule({ 135 | declarations: [App, Tabs, TabContent, TabTitle], 136 | imports: [BrowserModule], 137 | bootstrap: [App], 138 | }) 139 | class AppModule {} 140 | 141 | platformBrowserDynamic().bootstrapModule(AppModule); 142 | 143 | -------------------------------------------------------------------------------- /app/ch4/ts/basic-tab-content-children/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/basic-tab-content-children/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Basic tab component with ViewChildren", 3 | "description": "Basic tab component with ViewChildren", 4 | "id": 9, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/basic-tab/app.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | Inject, 4 | EventEmitter, 5 | Output, 6 | Input, 7 | Component, 8 | forwardRef, 9 | Host, 10 | NgModule 11 | } from '@angular/core'; 12 | 13 | import {BrowserModule} from '@angular/platform-browser'; 14 | 15 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 16 | 17 | @Component({ 18 | selector: `tab`, 19 | template: ` 20 |
21 | 22 |
23 | ` 24 | }) 25 | class Tab { 26 | isActive: boolean; 27 | @Input() 28 | public title: string; 29 | constructor(@Inject(forwardRef(() => Tabs)) @Host() private tabs: Tabs) { 30 | this.tabs.addTab(this); 31 | } 32 | } 33 | 34 | @Component({ 35 | selector: 'tabs', 36 | styles: [ 37 | ` 38 | .tab { 39 | display: inline-block; 40 | } 41 | .tab-header { 42 | list-style: none; 43 | padding: 0; 44 | margin: 0; 45 | } 46 | .tab-header .is-active { 47 | background-color: #eee; 48 | } 49 | .tab-header li { 50 | display: inline-block; 51 | cursor: pointer; 52 | padding: 5px; 53 | border: 1px solid #ccc; 54 | } 55 | .tab-content { 56 | border: 1px solid #ccc; 57 | border-top: none; 58 | padding: 5px; 59 | } 60 | ` 61 | ], 62 | template: ` 63 |
64 |
    65 |
  • 67 | {{tab.title}} 68 |
  • 69 |
70 |
71 | 72 |
73 |
74 | ` 75 | }) 76 | class Tabs { 77 | @Output('changed') 78 | private tabChanged: EventEmitter = new EventEmitter(); 79 | private tabs: Tab[]; 80 | private active: number; 81 | constructor() { 82 | this.tabs = []; 83 | this.active = 0; 84 | } 85 | addTab(tab: Tab) { 86 | if (this.tabs.length === this.active) { 87 | tab.isActive = true; 88 | } 89 | this.tabs.push(tab); 90 | } 91 | select(index) { 92 | this.tabs[this.active].isActive = false; 93 | this.active = index; 94 | this.tabs[index].isActive = true; 95 | this.tabChanged.emit(this.tabs[index]); 96 | } 97 | } 98 | 99 | @Component({ 100 | selector: 'app', 101 | template: ` 102 | 103 | 104 | Content 1 105 | 106 | 107 | Content 2 108 | 109 | 110 | ` 111 | }) 112 | class App { 113 | tabChanged(tab) { 114 | console.log(tab); 115 | } 116 | } 117 | 118 | 119 | @NgModule({ 120 | declarations: [App, Tabs, Tab], 121 | imports: [BrowserModule], 122 | bootstrap: [App], 123 | }) 124 | class AppModule {} 125 | 126 | platformBrowserDynamic().bootstrapModule(AppModule); 127 | 128 | -------------------------------------------------------------------------------- /app/ch4/ts/basic-tab/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/basic-tab/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Basic tab component", 3 | "description": "Basic tab component", 4 | "id": 8, 5 | "presented": false 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy/app.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import {NgModule, Component, Input, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core'; 4 | import {BrowserModule} from '@angular/platform-browser'; 5 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 6 | 7 | import * as Immutable from 'immutable'; 8 | 9 | interface Todo { 10 | completed: boolean; 11 | label: string; 12 | } 13 | 14 | @Component({ 15 | selector: 'input-box', 16 | template: ` 17 | 18 | 21 | ` 22 | }) 23 | class InputBox { 24 | @Input() inputPlaceholder: string; 25 | @Input() buttonLabel: string; 26 | @Output() inputText = new EventEmitter(); 27 | 28 | emitText(text: string) { 29 | this.inputText.emit(text); 30 | } 31 | } 32 | 33 | @Component({ 34 | selector: 'todo-list', 35 | changeDetection: ChangeDetectionStrategy.OnPush, 36 | template: ` 37 |
    38 |
  • 39 | 41 | {{todo.get('label')}} 42 |
  • 43 |
44 | `, 45 | styles: [ 46 | `ul li { 47 | list-style: none; 48 | } 49 | .completed { 50 | text-decoration: line-through; 51 | }` 52 | ] 53 | }) 54 | class TodoList { 55 | @Input() todos; 56 | @Output() toggle = new EventEmitter(); 57 | 58 | toggleCompletion(index: number) { 59 | this.toggle.emit(index); 60 | } 61 | } 62 | 63 | @Component({ 64 | selector: 'todo-app', 65 | template: ` 66 |

Hello {{name}}!

67 | 68 |

69 | Add a new todo: 70 | 73 | 74 |

75 | 76 |

Here's the list of pending todo items:

77 | 79 | 80 | ` 81 | }) 82 | class TodoApp { 83 | todos = Immutable.fromJS([{ 84 | label: 'Buy milk', 85 | completed: false 86 | }, { 87 | label: 'Save the world', 88 | completed: false 89 | }]); 90 | 91 | name: string = 'John'; 92 | 93 | addTodo(label: string) { 94 | this.todos = this.todos.push(Immutable.fromJS({ 95 | label, 96 | completed: false 97 | })); 98 | } 99 | toggleCompletion(index: number) { 100 | this.todos = this.todos.update(index, todo => { 101 | return Immutable.fromJS({ 102 | label: todo.get('label'), 103 | completed: !todo.get('completed') 104 | }); 105 | }); 106 | } 107 | } 108 | 109 | @NgModule({ 110 | declarations: [TodoList, InputBox, TodoApp], 111 | imports: [BrowserModule], 112 | bootstrap: [TodoApp], 113 | }) 114 | class TodoAppModule {} 115 | 116 | platformBrowserDynamic().bootstrapModule(TodoAppModule); 117 | 118 | -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Change Detection strategies (OnPush)", 3 | "description": "Change Detection strategy", 4 | "id": 12, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy_broken/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component, Input, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | interface Todo { 6 | completed: boolean; 7 | label: string; 8 | } 9 | 10 | @Component({ 11 | selector: 'input-box', 12 | template: ` 13 | 14 | 17 | ` 18 | }) 19 | class InputBox { 20 | @Input() inputPlaceholder: string; 21 | @Input() buttonLabel: string; 22 | @Output() inputText = new EventEmitter(); 23 | 24 | emitText(text: string) { 25 | this.inputText.emit(text); 26 | } 27 | } 28 | 29 | @Component({ 30 | selector: 'todo-list', 31 | template: ` 32 |
    33 |
  • 34 | 36 | {{todo.label}} 37 |
  • 38 |
39 | `, 40 | styles: [ 41 | `ul li { 42 | list-style: none; 43 | } 44 | .completed { 45 | text-decoration: line-through; 46 | }` 47 | ], 48 | changeDetection: ChangeDetectionStrategy.OnPush 49 | }) 50 | class TodoList { 51 | @Input() todos: Todo[]; 52 | @Output() toggle = new EventEmitter(); 53 | 54 | toggleCompletion(index: number) { 55 | let todo = this.todos[index]; 56 | this.toggle.emit(todo); 57 | } 58 | } 59 | 60 | @Component({ 61 | selector: 'todo-app', 62 | template: ` 63 |

Hello {{name}}!

64 | 65 |

66 | Add a new todo: 67 | 70 | 71 |

72 | 73 |

Here's the list of pending todo items:

74 | 76 | 77 | ` 78 | }) 79 | class TodoApp { 80 | todos: Todo[] = [{ 81 | label: 'Buy milk', 82 | completed: false 83 | }, { 84 | label: 'Save the world', 85 | completed: false 86 | }]; 87 | name: string = 'John'; 88 | 89 | addTodo(label: string) { 90 | this.todos.push({ 91 | label, 92 | completed: false 93 | }); 94 | } 95 | 96 | toggleCompletion(todo: Todo) { 97 | todo.completed = !todo.completed; 98 | } 99 | } 100 | 101 | 102 | @NgModule({ 103 | declarations: [TodoList, InputBox, TodoApp], 104 | imports: [BrowserModule], 105 | bootstrap: [TodoApp], 106 | }) 107 | class TodoAppModule {} 108 | 109 | platformBrowserDynamic().bootstrapModule(TodoAppModule); 110 | 111 | -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy_broken/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy_broken/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Change Detection - not performing", 3 | "description": "Change Detection strategy", 4 | "id": 13, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy_order/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component, Input, Output, EventEmitter, ChangeDetectionStrategy} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | interface Todo { 6 | completed: boolean; 7 | label: string; 8 | } 9 | 10 | @Component({ 11 | selector: 'input-box', 12 | template: ` 13 | 14 | 17 | ` 18 | }) 19 | class InputBox { 20 | @Input() inputPlaceholder: string; 21 | @Input() buttonLabel: string; 22 | @Output() inputText = new EventEmitter(); 23 | emitText(text: string) { 24 | this.inputText.emit(text); 25 | } 26 | ngDoCheck() { 27 | console.log('Change detection run in the InputBox component'); 28 | } 29 | } 30 | 31 | @Component({ 32 | selector: 'todo-list', 33 | template: ` 34 |
    35 |
  • 36 | 38 | {{todo.label}} 39 |
  • 40 |
41 | `, 42 | styles: [ 43 | `ul li { 44 | list-style: none; 45 | } 46 | .completed { 47 | text-decoration: line-through; 48 | }` 49 | ] 50 | }) 51 | class TodoList { 52 | @Input() todos: Todo[]; 53 | @Output() toggle = new EventEmitter(); 54 | toggleCompletion(index: number) { 55 | let todo = this.todos[index]; 56 | this.toggle.emit(todo); 57 | } 58 | ngDoCheck() { 59 | console.log('Change detection run in the TodoList component'); 60 | } 61 | } 62 | 63 | @Component({ 64 | selector: 'todo-app', 65 | template: ` 66 |

Hello {{name}}!

67 | 68 |

69 | Add a new todo: 70 | 73 | 74 |

75 | 76 |

Here's the list of pending todo items:

77 | 79 | 80 | ` 81 | }) 82 | class TodoApp { 83 | todos: Todo[] = [{ 84 | label: 'Buy milk', 85 | completed: false 86 | }, { 87 | label: 'Save the world', 88 | completed: false 89 | }]; 90 | name: string = 'John'; 91 | addTodo(label: string) { 92 | this.todos.push({ 93 | label, 94 | completed: false 95 | }); 96 | } 97 | toggleCompletion(todo: Todo) { 98 | todo.completed = !todo.completed; 99 | } 100 | ngDoCheck() { 101 | console.log('Change detection run in the TodoApp component'); 102 | } 103 | } 104 | 105 | 106 | @NgModule({ 107 | declarations: [TodoList, InputBox, TodoApp], 108 | imports: [BrowserModule], 109 | bootstrap: [TodoApp], 110 | }) 111 | class TodoAppModule {} 112 | 113 | platformBrowserDynamic().bootstrapModule(TodoAppModule); 114 | 115 | -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy_order/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/change_detection_strategy_order/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Change Detection - order of execution", 3 | "description": "Change Detection - order of execution", 4 | "id": 13, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/custom-element/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'my-app', 7 | template: ` 8 |

Hello {{name}}

9 | The current time is 10 | ` 11 | }) 12 | class App { 13 | name: string = 'John Doe'; 14 | } 15 | 16 | @NgModule({ 17 | imports: [BrowserModule], 18 | declarations: [App], 19 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 20 | bootstrap: [App] 21 | }) 22 | class AppModule {} 23 | 24 | platformBrowserDynamic().bootstrapModule(AppModule); 25 | 26 | -------------------------------------------------------------------------------- /app/ch4/ts/custom-element/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/custom-element/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Using custom elements with Angular", 3 | "description": "Using custom elements with Angular", 4 | "id": 6, 5 | "presented": true 6 | } 7 | -------------------------------------------------------------------------------- /app/ch4/ts/hello-world/app.html: -------------------------------------------------------------------------------- 1 |

Hello {{target}}!

-------------------------------------------------------------------------------- /app/ch4/ts/hello-world/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'my-app', 7 | templateUrl: './app.html' 8 | }) 9 | class App { 10 | target: string; 11 | constructor() { 12 | this.target = 'world'; 13 | } 14 | } 15 | 16 | @NgModule({ 17 | declarations: [App], 18 | imports: [BrowserModule], 19 | bootstrap: [App], 20 | }) 21 | class AppModule {} 22 | 23 | platformBrowserDynamic().bootstrapModule(AppModule); 24 | 25 | -------------------------------------------------------------------------------- /app/ch4/ts/hello-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/hello-world/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Hello world application in TypeScript", 3 | "description": "Hello world application in TypeScript", 4 | "id": 2, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/inputs-outputs/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component, Input, Output, EventEmitter} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | interface Todo { 6 | completed: boolean; 7 | label: string; 8 | } 9 | 10 | @Component({ 11 | selector: 'input-box', 12 | template: ` 13 | 14 | 17 | ` 18 | }) 19 | class InputBox { 20 | @Input() inputPlaceholder: string; 21 | @Input() buttonLabel: string; 22 | @Output() inputText = new EventEmitter(); 23 | emitText(text: string) { 24 | this.inputText.emit(text); 25 | } 26 | } 27 | 28 | @Component({ 29 | selector: 'todo-list', 30 | template: ` 31 |
    32 |
  • 33 | 35 | {{todo.label}} 36 |
  • 37 |
38 | `, 39 | styles: [ 40 | `ul li { 41 | list-style: none; 42 | } 43 | .completed { 44 | text-decoration: line-through; 45 | }` 46 | ] 47 | }) 48 | class TodoList { 49 | @Input() todos: Todo[]; 50 | @Output() toggle = new EventEmitter(); 51 | toggleCompletion(index: number) { 52 | let todo = this.todos[index]; 53 | this.toggle.emit(todo); 54 | } 55 | } 56 | 57 | @Component({ 58 | selector: 'todo-app', 59 | template: ` 60 |

Hello {{name}}!

61 | 62 |

63 | Add a new todo: 64 | 67 | 68 |

69 | 70 |

Here's the list of pending todo items:

71 | 73 | 74 | ` 75 | }) 76 | class TodoApp { 77 | todos: Todo[] = [{ 78 | label: 'Buy milk', 79 | completed: false 80 | }, { 81 | label: "Save the world", 82 | completed: false 83 | }]; 84 | name: string = 'John'; 85 | addTodo(label: string) { 86 | this.todos.push({ 87 | label, 88 | completed: false 89 | }); 90 | } 91 | toggleCompletion(todo: Todo) { 92 | todo.completed = !todo.completed; 93 | } 94 | } 95 | 96 | @NgModule({ 97 | declarations: [TodoList, InputBox, TodoApp], 98 | imports: [BrowserModule], 99 | bootstrap: [TodoApp], 100 | }) 101 | class TodoAppModule {} 102 | 103 | platformBrowserDynamic().bootstrapModule(TodoAppModule); 104 | 105 | -------------------------------------------------------------------------------- /app/ch4/ts/inputs-outputs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | 18 | 19 | <%= INIT %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/ch4/ts/inputs-outputs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Component's @Input and @Output", 3 | "description": "Todo application which demonstrates component's inputs and outputs", 4 | "id": 8, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/life-cycle/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule, Input} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'panel', 7 | template: '' 8 | }) 9 | class Panel { 10 | @Input() title: string; 11 | @Input() caption: string; 12 | 13 | ngOnChanges(changes) { 14 | console.log('On changes', changes); 15 | } 16 | 17 | ngOnInit() { 18 | console.log('Initialized'); 19 | } 20 | 21 | ngDoCheck() { 22 | console.log('Do check'); 23 | } 24 | 25 | ngOnDestroy() { 26 | console.log('Destroy'); 27 | } 28 | 29 | ngAfterContentInit() { 30 | console.log('After content init'); 31 | } 32 | 33 | ngAfterContentChecked() { 34 | console.log('After content checked'); 35 | } 36 | 37 | ngAfterViewInit() { 38 | console.log('After view init'); 39 | } 40 | 41 | ngAfterViewChecked() { 42 | console.log('After view checked'); 43 | } 44 | } 45 | 46 | @Component({ 47 | selector: 'app', 48 | template: ` 49 | 50 |
51 | Hello world! 52 |
53 | ` 54 | }) 55 | class App { 56 | counter: number = 0; 57 | 58 | toggle() { 59 | this.counter += 1; 60 | } 61 | } 62 | 63 | @NgModule({ 64 | declarations: [Panel, App], 65 | imports: [BrowserModule], 66 | bootstrap: [App], 67 | }) 68 | class AppModule {} 69 | 70 | platformBrowserDynamic().bootstrapModule(AppModule); 71 | 72 | -------------------------------------------------------------------------------- /app/ch4/ts/life-cycle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/life-cycle/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Lifecycle hooks", 3 | "description": "Demo of the lifecycle hooks of Angular 2 component", 4 | "id": 10, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/ng-content/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'fancy-button', 7 | template: '' 8 | }) 9 | class FancyButton { /* Extra behavior */ } 10 | 11 | @Component({ 12 | selector: 'panel', 13 | styles: [ 14 | `.panel { 15 | width: auto; 16 | display: inline-block; 17 | border: 1px solid black; 18 | } 19 | .panel-title { 20 | border-bottom: 1px solid black; 21 | background-color: #eee; 22 | } 23 | .panel-content, 24 | .panel-title { 25 | padding: 5px; 26 | }` 27 | ], 28 | template: ` 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
` 37 | }) 38 | class Panel { } 39 | 40 | @Component({ 41 | selector: 'app', 42 | template: ` 43 | 44 | I will be projected 45 | 46 |
47 | 48 |
Sample title
49 |
Content
50 |
51 | ` 52 | }) 53 | class App { 54 | constructor() {} 55 | } 56 | 57 | @NgModule({ 58 | declarations: [Panel, FancyButton, App], 59 | imports: [BrowserModule], 60 | bootstrap: [App], 61 | }) 62 | class AppModule {} 63 | 64 | platformBrowserDynamic().bootstrapModule(AppModule); 65 | 66 | -------------------------------------------------------------------------------- /app/ch4/ts/ng-content/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/ng-content/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Content projection with ng-content", 3 | "description": "Various demos of ng-content", 4 | "id": 7, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/ng-for/detailed-syntax/app.html: -------------------------------------------------------------------------------- 1 |

Hello {{name}}!

2 |

3 | Here's list of the things you need to do: 4 |

5 |
    6 | 9 |
-------------------------------------------------------------------------------- /app/ch4/ts/ng-for/detailed-syntax/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'app', 7 | templateUrl: './app.html', 8 | }) 9 | class App { 10 | todos: string[]; 11 | name: string; 12 | constructor() { 13 | this.name = 'John'; 14 | this.todos = ['Buy milk', 'Save the world']; 15 | } 16 | } 17 | 18 | @NgModule({ 19 | declarations: [App], 20 | imports: [BrowserModule], 21 | bootstrap: [App], 22 | }) 23 | class AppModule {} 24 | 25 | platformBrowserDynamic().bootstrapModule(AppModule); 26 | 27 | -------------------------------------------------------------------------------- /app/ch4/ts/ng-for/detailed-syntax/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= APP_TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/ng-for/detailed-syntax/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "List of items (detailed syntax)", 3 | "description": "List of items using explicit template for ng-for", 4 | "id": 3, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/ng-for/syntax-sugar/app.html: -------------------------------------------------------------------------------- 1 |

Hello {{name}}!

2 |

3 | Here's list of the things you need to do: 4 |

5 |
    6 |
  • 7 | {{todo}} 8 |
  • 9 |
-------------------------------------------------------------------------------- /app/ch4/ts/ng-for/syntax-sugar/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'app', 7 | templateUrl: './app.html' 8 | }) 9 | class App { 10 | todos: string[]; 11 | name: string; 12 | handle() { 13 | alert(42); 14 | } 15 | constructor() { 16 | this.name = "John"; 17 | this.todos = ['Buy milk', "Save the world"]; 18 | } 19 | } 20 | 21 | @NgModule({ 22 | declarations: [App], 23 | imports: [BrowserModule], 24 | bootstrap: [App], 25 | }) 26 | class AppModule {} 27 | 28 | platformBrowserDynamic().bootstrapModule(AppModule); 29 | 30 | -------------------------------------------------------------------------------- /app/ch4/ts/ng-for/syntax-sugar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= APP_TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/ng-for/syntax-sugar/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "List of items (syntax sugar)", 3 | "description": "List of items using syntax sugar *ng-for", 4 | "id": 4, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/template-ref/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component, ContentChild, TemplateRef, Input, Output, EventEmitter} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | interface Todo { 6 | completed: boolean; 7 | label: string; 8 | } 9 | 10 | @Component({ 11 | selector: 'input-box', 12 | template: ` 13 | 14 | 17 | ` 18 | }) 19 | class InputBox { 20 | @Input() inputPlaceholder: string; 21 | @Input() buttonLabel: string; 22 | @Output() inputText = new EventEmitter(); 23 | emitText(text: string) { 24 | this.inputText.emit(text); 25 | } 26 | } 27 | 28 | @Component({ 29 | selector: 'todo-list', 30 | template: ` 31 |
    32 | 34 |
35 | ` 36 | }) 37 | class TodoList { 38 | @Input() todos: Todo[]; 39 | @Input() itemsTemplate: TemplateRef; 40 | @Output() toggle = new EventEmitter(); 41 | } 42 | 43 | @Component({ 44 | selector: 'todo-app', 45 | template: ` 46 |

Hello {{name}}!

47 | 48 |

49 | Add a new todo: 50 | 53 | 54 |

55 | 56 |

Here's the list of pending todo items:

57 | 60 | 61 | ` 62 | }) 63 | class TodoApp { 64 | todos: Todo[] = [{ 65 | label: 'Buy milk', 66 | completed: false 67 | }, { 68 | label: 'Save the world', 69 | completed: false 70 | }]; 71 | name: string = 'John'; 72 | @ContentChild(TemplateRef) itemsTemplate: TemplateRef; 73 | 74 | addTodo(label: string) { 75 | this.todos.push({ 76 | label, 77 | completed: false 78 | }); 79 | } 80 | } 81 | 82 | @Component({ 83 | selector: 'app', 84 | styles: [` 85 | .completed { 86 | text-decoration: line-through; 87 | }` 88 | ], 89 | template: ` 90 | 91 | 98 | 99 | ` 100 | }) 101 | class App {} 102 | 103 | 104 | @NgModule({ 105 | declarations: [TodoList, InputBox, TodoApp, App], 106 | imports: [BrowserModule], 107 | bootstrap: [App], 108 | }) 109 | class AppModule {} 110 | 111 | platformBrowserDynamic().bootstrapModule(AppModule); 112 | 113 | -------------------------------------------------------------------------------- /app/ch4/ts/template-ref/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= APP_TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/template-ref/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Todo application using templateRef", 3 | "description": "Simple todo application using templateRef", 4 | "id": 11, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/todo-app/app.html: -------------------------------------------------------------------------------- 1 |

Hello {{name}}!

2 | 3 |

4 | Add a new todo: 5 | 6 | 7 |

8 | 9 |

Here's the list of pending todo items:

10 | 11 |
    12 |
  • 13 | 15 | {{todo.label}} 16 |
  • 17 |
18 | -------------------------------------------------------------------------------- /app/ch4/ts/todo-app/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | interface Todo { 6 | completed: boolean; 7 | label: string; 8 | } 9 | 10 | @Component({ 11 | selector: 'app', 12 | templateUrl: './app.html', 13 | styles: [ 14 | `ul li { 15 | list-style: none; 16 | } 17 | .completed { 18 | text-decoration: line-through; 19 | }` 20 | ] 21 | }) 22 | class TodoCtrl { 23 | todos: Todo[] = [{ 24 | label: 'Buy milk', 25 | completed: false 26 | }, { 27 | label: "Save the world", 28 | completed: false 29 | }]; 30 | 31 | name: string = 'John'; 32 | 33 | addTodo(label) { 34 | this.todos.push({ 35 | label, 36 | completed: false 37 | }) 38 | } 39 | 40 | removeTodo(idx) { 41 | this.todos.splice(idx, 1); 42 | } 43 | 44 | toggleCompletion(idx) { 45 | let todo = this.todos[idx]; 46 | todo.completed = !todo.completed; 47 | } 48 | } 49 | 50 | 51 | @NgModule({ 52 | declarations: [TodoCtrl], 53 | imports: [BrowserModule], 54 | bootstrap: [TodoCtrl], 55 | }) 56 | class AppModule {} 57 | 58 | platformBrowserDynamic().bootstrapModule(AppModule); 59 | 60 | -------------------------------------------------------------------------------- /app/ch4/ts/todo-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= APP_TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | 18 | 19 | <%= INIT %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/ch4/ts/todo-app/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Basic todo application", 3 | "description": "Todo application which demonstrates basic user input", 4 | "id": 6, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/tooltip/app.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /app/ch4/ts/tooltip/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, HostListener, Input, Injectable, ElementRef, Inject, Directive, Component} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | class Overlay { 6 | private el: HTMLElement; 7 | constructor() { 8 | var el = document.createElement('div'); 9 | el.className = 'tooltip'; 10 | this.el = el; 11 | } 12 | close() { 13 | this.el.hidden = true; 14 | } 15 | open(el, text) { 16 | this.el.innerHTML = text; 17 | this.el.hidden = false; 18 | var rect = el.nativeElement.getBoundingClientRect(); 19 | this.el.style.left = rect.left + 'px'; 20 | this.el.style.top = rect.top + 'px'; 21 | } 22 | attach(target) { 23 | target.appendChild(this.el); 24 | } 25 | detach() { 26 | this.el.parentNode.removeChild(this.el); 27 | } 28 | } 29 | 30 | class OverlayMock { 31 | constructor() {} 32 | close() {} 33 | open(el, text) {} 34 | attach(target) {} 35 | detach() {} 36 | } 37 | 38 | @Directive({ 39 | selector: '[saTooltip]' 40 | }) 41 | export class Tooltip { 42 | @Input() 43 | saTooltip:string; 44 | 45 | constructor(private el: ElementRef, private overlay: Overlay) { 46 | this.overlay.attach(el.nativeElement); 47 | } 48 | @HostListener('mouseenter') 49 | onMouseEnter() { 50 | this.overlay.open(this.el, this.saTooltip); 51 | } 52 | @HostListener('mouseleave') 53 | onMouseLeave() { 54 | this.overlay.close(); 55 | } 56 | } 57 | 58 | @Component({ 59 | selector: 'app', 60 | templateUrl: './app.html', 61 | }) 62 | class App {} 63 | 64 | 65 | @NgModule({ 66 | declarations: [Tooltip, App], 67 | providers: [Overlay], 68 | imports: [BrowserModule], 69 | bootstrap: [App], 70 | }) 71 | class AppModule {} 72 | 73 | platformBrowserDynamic().bootstrapModule(AppModule); 74 | 75 | -------------------------------------------------------------------------------- /app/ch4/ts/tooltip/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <%= INIT %> 26 | 27 | -------------------------------------------------------------------------------- /app/ch4/ts/tooltip/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Tooltip directive", 3 | "description": "Tooltip directive", 4 | "id": 5, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/view-child-content-child/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component, Directive, ViewChildren, ContentChildren, QueryList} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'user-badge', 7 | template: '

View child

' 8 | }) 9 | class UserBadge {} 10 | 11 | @Component({ 12 | selector: 'user-rating', 13 | template: '

Content child

' 14 | }) 15 | class UserRating {} 16 | 17 | @Component({ 18 | selector: 'user-panel', 19 | template: '' 20 | }) 21 | class UserPanel { 22 | @ViewChildren(UserBadge) 23 | viewChildren: QueryList; 24 | 25 | @ContentChildren(UserRating) 26 | contentChildren: QueryList; 27 | 28 | ngAfterViewInit() { 29 | // view children are initialized 30 | } 31 | 32 | ngAfterContentInit() { 33 | // content children are initialized 34 | } 35 | } 36 | 37 | 38 | @Component({ 39 | selector: 'app', 40 | template: '', 41 | }) 42 | class App { 43 | constructor() {} 44 | } 45 | 46 | @NgModule({ 47 | declarations: [App, UserBadge, UserPanel, UserRating], 48 | imports: [BrowserModule], 49 | bootstrap: [App], 50 | }) 51 | class AppModule {} 52 | 53 | platformBrowserDynamic().bootstrapModule(AppModule); 54 | 55 | -------------------------------------------------------------------------------- /app/ch4/ts/view-child-content-child/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/view-child-content-child/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "ContentChildren vs ViewChildren simple example", 3 | "description": "ContentChildren vs ViewChildren simple example", 4 | "id": 11, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch4/ts/zippy/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {CommonModule} from '@angular/common'; 4 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 5 | 6 | @Component({ 7 | selector: 'zippy-header', 8 | template: '
{{ header }}
', 9 | styles: [ 10 | ` 11 | header { 12 | cursor: pointer; 13 | border-bottom: 1px solid #ccc; 14 | font-size: 1.2em; 15 | background-color: #eee; 16 | } 17 | ` 18 | ] 19 | }) 20 | class ZippyHeader { 21 | @Input() header: string; 22 | } 23 | 24 | @Component({ 25 | selector: 'zippy', 26 | template: ` 27 |
28 | 29 |
30 | 31 |
32 |
33 | `, 34 | styles: [ 35 | ` 36 | section { 37 | width: 300px; 38 | border: 1px solid #ccc; 39 | } 40 | ` 41 | ] 42 | }) 43 | class Zippy { 44 | @Input() header: string; 45 | visible = true; 46 | } 47 | 48 | @NgModule({ 49 | declarations: [Zippy, ZippyHeader], 50 | imports: [CommonModule], 51 | exports: [Zippy] 52 | }) 53 | class ZippyModule {} 54 | 55 | @Component({ 56 | selector: 'my-app', 57 | template: ` 58 | 59 | 60 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. 61 | 62 | 63 | ` 64 | }) 65 | class App { 66 | } 67 | 68 | @NgModule({ 69 | imports: [BrowserModule, ZippyModule], 70 | declarations: [App], 71 | bootstrap: [App] 72 | }) 73 | class AppModule {} 74 | 75 | platformBrowserDynamic().bootstrapModule(AppModule); 76 | 77 | -------------------------------------------------------------------------------- /app/ch4/ts/zippy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | <%= INIT %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch4/ts/zippy/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Reusable components", 3 | "description": "Reusable components", 4 | "id": 6, 5 | "presented": true 6 | } 7 | -------------------------------------------------------------------------------- /app/ch5/es5/simple-example/app.js: -------------------------------------------------------------------------------- 1 | var Markdown = ng.core.Class({ 2 | constructor: function () {}, 3 | toHTML: function (md) { 4 | return markdown.toHTML(md); 5 | } 6 | }); 7 | 8 | var MarkdownPanel = ng.core.Component({ 9 | selector: 'markdown-panel', 10 | viewProviders: [Markdown], 11 | styles: [ 12 | '.panel {' + 13 | 'width: auto;' + 14 | 'display: inline-block;' + 15 | 'border: 1px solid black;' + 16 | '}' + 17 | '.panel-title-wrapper {' + 18 | 'border-bottom: 1px solid black;' + 19 | 'background-color: #eee;' + 20 | '}' + 21 | '.panel-content-wrapper,' + 22 | '.panel-title-wrapper {' + 23 | ' padding: 5px;' + 24 | '}' 25 | ], 26 | template: '
' + 27 | '
' + 28 | '' + 29 | '
' + 30 | '
' + 31 | '' + 32 | '
' + 33 | '
' 34 | }) 35 | .Class({ 36 | constructor: [[ng.core.Optional(), ng.core.Self(), Markdown], 37 | ng.core.ElementRef, function (md, el) { 38 | this.md = md; 39 | this.el = el; 40 | }], 41 | ngAfterContentInit: function () { 42 | var el = this.el.nativeElement; 43 | var title = el.querySelector('.panel-title'); 44 | var content = el.querySelector('.panel-content'); 45 | title.innerHTML = this.md.toHTML(title.innerHTML); 46 | content.innerHTML = this.md.toHTML(content.innerHTML); 47 | } 48 | }); 49 | 50 | var App = ng.core.Component({ 51 | selector: 'app', 52 | template: ` 53 | 54 |
### Small title
55 |
56 | ## Sample title 57 | * First point 58 | * Second point 59 |
60 |
61 | ` 62 | }) 63 | .Class({ 64 | constructor: function () {} 65 | }); 66 | 67 | var AppModule = ng.core.NgModule({ 68 | imports: [ng.platformBrowser.BrowserModule], 69 | declarations: [MarkdownPanel, App], 70 | bootstrap: [App] 71 | }) 72 | .Class({ 73 | constructor: function () {} 74 | }); 75 | 76 | ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(AppModule); 77 | 78 | -------------------------------------------------------------------------------- /app/ch5/es5/simple-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/ch5/es5/simple-example/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "DI ES5 example", 3 | "description": "DI ES5 example", 4 | "id": 2, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch5/ts/configuring-providers/dummy-http.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable, 4 | OpaqueToken, forwardRef 5 | } from '@angular/core'; 6 | 7 | class Http {} 8 | 9 | class DummyHttp {} 10 | 11 | @Injectable() 12 | class UserService { 13 | constructor(private http: Http) { 14 | console.log(this.http instanceof DummyHttp); 15 | } 16 | } 17 | 18 | let injector = ReflectiveInjector.resolveAndCreate([ 19 | UserService, 20 | { provide: Http, useClass: DummyHttp } 21 | ]); 22 | 23 | 24 | injector.get(UserService); 25 | -------------------------------------------------------------------------------- /app/ch5/ts/configuring-providers/existing.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable 4 | } from '@angular/core'; 5 | 6 | class Http {} 7 | 8 | class DummyService {} 9 | 10 | @Injectable() 11 | class UserService { 12 | constructor(public http: Http) {} 13 | } 14 | 15 | // let injector = ReflectiveInjector.resolveAndCreate([ 16 | // DummyService, 17 | // { provide: Http, useExisting: DummyService }, 18 | // UserService 19 | // ]); 20 | 21 | // let us:UserService = injector.get(UserService); 22 | 23 | // console.log(us.http instanceof DummyService); 24 | let dummyHttp = { 25 | get() {}, 26 | post() {} 27 | }; 28 | 29 | let injector = ReflectiveInjector.resolveAndCreate([ 30 | { provide: DummyService, useValue: dummyHttp }, 31 | { provide: Http, useExisting: DummyService }, 32 | UserService 33 | ]); 34 | 35 | console.assert(injector.get(UserService).http === dummyHttp); 36 | -------------------------------------------------------------------------------- /app/ch5/ts/configuring-providers/factory.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable, OpaqueToken 4 | } from '@angular/core'; 5 | 6 | const BUFFER_SIZE = new OpaqueToken('buffer-size'); 7 | 8 | class Buffer { 9 | constructor(@Inject(BUFFER_SIZE) private size: Number) {} 10 | } 11 | 12 | class Certificate {} 13 | class Crypto {} 14 | @Injectable() 15 | class Socket { 16 | isOpen: boolean; 17 | constructor(private buffer: Buffer) {} 18 | open() { 19 | this.isOpen = true; 20 | } 21 | } 22 | 23 | class TLSConnection { 24 | public socket: Socket; 25 | public crypto: Crypto; 26 | public certificate: Certificate; 27 | } 28 | 29 | let injector = ReflectiveInjector.resolveAndCreate([ 30 | { 31 | provide: TLSConnection, 32 | useFactory: (socket: Socket, certificate: Certificate, crypto: Crypto) => { 33 | let connection = new TLSConnection(); 34 | connection.certificate = certificate; 35 | connection.socket = socket; 36 | connection.crypto = crypto; 37 | socket.open(); 38 | return connection; 39 | }, 40 | deps: [Socket, Certificate, Crypto] 41 | }, 42 | { provide: BUFFER_SIZE, useValue: 42 }, 43 | Buffer, 44 | Socket, 45 | Certificate, 46 | Crypto 47 | ]); 48 | 49 | console.log(injector.get(TLSConnection)); 50 | -------------------------------------------------------------------------------- /app/ch5/ts/configuring-providers/multi-providers.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable, OpaqueToken 4 | } from '@angular/core'; 5 | 6 | const VALIDATOR = new OpaqueToken('validator'); 7 | 8 | interface EmployeeValidator { 9 | (person: Employee): string; 10 | }; 11 | 12 | class Employee { 13 | name: string; 14 | constructor(@Inject(VALIDATOR) private validators: EmployeeValidator[]) {} 15 | validate() { 16 | return this.validators 17 | .map(v => v(this)) 18 | .filter(value => !!value); 19 | } 20 | } 21 | 22 | let injector = ReflectiveInjector.resolveAndCreate([ 23 | { 24 | provide: VALIDATOR, 25 | multi: true, 26 | useValue: (person: Employee) => { 27 | if (!person.name) { 28 | return 'The name is required'; 29 | } 30 | } 31 | }, 32 | { 33 | provide: VALIDATOR, 34 | multi: true, 35 | useValue: (person: Employee) => { 36 | if (!person.name || person.name.length < 1) { 37 | return 'The name should be more than 1 symbol long'; 38 | } 39 | } 40 | }, 41 | Employee 42 | ]); 43 | 44 | console.log(injector.get(Employee).validate()); 45 | -------------------------------------------------------------------------------- /app/ch5/ts/decorators/optional.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable, Optional 4 | } from '@angular/core'; 5 | 6 | abstract class SortingAlgorithm { 7 | abstract sort(collection: BaseCollection): Collection; 8 | } 9 | 10 | class BaseCollection { 11 | getDefaultSort(): SortingAlgorithm { 12 | // get some generic sorting algorithm... 13 | return null; 14 | } 15 | } 16 | 17 | @Injectable() 18 | class Collection extends BaseCollection { 19 | private sort: SortingAlgorithm; 20 | constructor(@Optional() sort: SortingAlgorithm) { 21 | super(); 22 | this.sort = sort || this.getDefaultSort(); 23 | } 24 | } 25 | 26 | let injector = ReflectiveInjector.resolveAndCreate([ 27 | Collection 28 | ]); 29 | 30 | console.log(injector.get(Collection).sort === null); 31 | -------------------------------------------------------------------------------- /app/ch5/ts/decorators/self.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable, Self 4 | } from '@angular/core'; 5 | 6 | abstract class Channel {} 7 | 8 | class Http extends Channel {} 9 | 10 | class WebSocket extends Channel {} 11 | 12 | @Injectable() 13 | class UserService { 14 | constructor(@Self() public channel: Channel) {} 15 | } 16 | 17 | let parentInjector = ReflectiveInjector.resolveAndCreate([ 18 | { provide: Channel, useClass: Http } 19 | ]); 20 | let childInjector = parentInjector.resolveAndCreateChild([ 21 | { provide: Channel, useClass: WebSocket }, 22 | UserService 23 | ]); 24 | 25 | console.log(childInjector.get(UserService).channel instanceof WebSocket); 26 | -------------------------------------------------------------------------------- /app/ch5/ts/decorators/skip-self.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable, SkipSelf 4 | } from '@angular/core'; 5 | 6 | class Context { 7 | constructor(@SkipSelf() public parentContext: Context) {} 8 | } 9 | 10 | let parentInjector = ReflectiveInjector.resolveAndCreate([ 11 | { provide: Context, useValue: new Context(null) } 12 | ]); 13 | let childInjector = parentInjector.resolveAndCreateChild([ 14 | Context 15 | ]); 16 | 17 | console.log(childInjector.get(Context).parentContext instanceof Context); 18 | -------------------------------------------------------------------------------- /app/ch5/ts/directives-ngmodules/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, ElementRef, NgModule, ViewChild} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import * as markdown from 'markdown'; 5 | 6 | let instance: Markdown; 7 | 8 | class Markdown { 9 | toHTML(md) { 10 | return markdown.toHTML(md); 11 | } 12 | } 13 | 14 | @Component({ 15 | selector: 'markdown-panel', 16 | styles: [ 17 | `.panel { 18 | width: auto; 19 | display: inline-block; 20 | border: 1px solid black; 21 | } 22 | .panel-title-wrapper { 23 | border-bottom: 1px solid black; 24 | background-color: #eee; 25 | } 26 | .panel-content-wrapper, 27 | .panel-title-wrapper { 28 | padding: 5px; 29 | }` 30 | ], 31 | template: ` 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
` 40 | }) 41 | class MarkdownPanel { 42 | constructor(private el: ElementRef, private md: Markdown) {} 43 | ngAfterContentInit() { 44 | instance = this.md; 45 | let el = this.el.nativeElement; 46 | let title = el.querySelector('.panel-title'); 47 | let content = el.querySelector('.panel-content'); 48 | title.innerHTML = this.md.toHTML(title.innerHTML); 49 | content.innerHTML = this.md.toHTML(content.innerHTML); 50 | } 51 | } 52 | 53 | @Component({ 54 | selector: 'app', 55 | template: ` 56 | 57 |
### Small title
58 |
59 | ## Sample title 60 | * First point 61 | * Second point 62 |
63 |
64 | **Click me** 65 | ` 66 | }) 67 | class App { 68 | constructor() {} 69 | } 70 | 71 | @Component({ 72 | selector: 'btn', 73 | template: '' 74 | }) 75 | class Button { 76 | @ViewChild('btn') button: ElementRef; 77 | 78 | constructor(private md: Markdown) {} 79 | 80 | ngAfterContentInit() { 81 | const el = this.button.nativeElement; 82 | el.innerHTML = this.md.toHTML(el.innerHTML); 83 | console.log(instance === this.md); 84 | } 85 | } 86 | 87 | @NgModule({ 88 | declarations: [Button], 89 | exports: [Button], 90 | providers: [Markdown], 91 | }) 92 | class ButtonModule {} 93 | 94 | @NgModule({ 95 | declarations: [App, MarkdownPanel], 96 | imports: [BrowserModule, ButtonModule], 97 | bootstrap: [App], 98 | }) 99 | class AppModule {} 100 | 101 | platformBrowserDynamic().bootstrapModule(AppModule); 102 | 103 | -------------------------------------------------------------------------------- /app/ch5/ts/directives-ngmodules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | 20 | <%= INIT %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/ch5/ts/directives-ngmodules/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "NgModules DI", 3 | "description": "NgModules DI", 4 | "id": 2, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch5/ts/directives-ngmodules/util.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/getting-started-with-angular/8e6c36a992b06dbd843bb0aff869ed47cd321c61/app/ch5/ts/directives-ngmodules/util.js -------------------------------------------------------------------------------- /app/ch5/ts/directives/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, ElementRef, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import * as markdown from 'markdown'; 5 | 6 | class Markdown { 7 | toHTML(md) { 8 | return markdown.toHTML(md); 9 | } 10 | } 11 | 12 | @Component({ 13 | selector: 'markdown-panel', 14 | viewProviders: [Markdown], 15 | styles: [ 16 | `.panel { 17 | width: auto; 18 | display: inline-block; 19 | border: 1px solid black; 20 | } 21 | .panel-title-wrapper { 22 | border-bottom: 1px solid black; 23 | background-color: #eee; 24 | } 25 | .panel-content-wrapper, 26 | .panel-title-wrapper { 27 | padding: 5px; 28 | }` 29 | ], 30 | template: ` 31 |
32 |
33 | 34 |
35 |
36 | 37 |
38 |
` 39 | }) 40 | class MarkdownPanel { 41 | constructor(private el: ElementRef, private md: Markdown) {} 42 | ngAfterContentInit() { 43 | let el = this.el.nativeElement; 44 | let title = el.querySelector('.panel-title'); 45 | let content = el.querySelector('.panel-content'); 46 | title.innerHTML = this.md.toHTML(title.innerHTML); 47 | content.innerHTML = this.md.toHTML(content.innerHTML); 48 | } 49 | } 50 | 51 | @Component({ 52 | selector: 'app', 53 | template: ` 54 | 55 |
### Small title
56 |
57 | ## Sample title 58 | * First point 59 | * Second point 60 |
61 |
62 | ` 63 | }) 64 | class App { 65 | constructor() {} 66 | } 67 | 68 | @NgModule({ 69 | declarations: [App, MarkdownPanel], 70 | imports: [BrowserModule], 71 | bootstrap: [App], 72 | }) 73 | class AppModule {} 74 | 75 | platformBrowserDynamic().bootstrapModule(AppModule); 76 | 77 | -------------------------------------------------------------------------------- /app/ch5/ts/directives/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | 20 | <%= INIT %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/ch5/ts/directives/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Components & Directives DI", 3 | "description": "Components & Directives DI", 4 | "id": 1, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch5/ts/directives/util.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/getting-started-with-angular/8e6c36a992b06dbd843bb0aff869ed47cd321c61/app/ch5/ts/directives/util.js -------------------------------------------------------------------------------- /app/ch5/ts/injector-basics/forward-ref.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable, 4 | OpaqueToken, forwardRef 5 | } from '@angular/core'; 6 | 7 | const BUFFER_SIZE = new OpaqueToken('buffer-size'); 8 | 9 | @Injectable() 10 | class Socket { 11 | constructor(@Inject(forwardRef(() => Buffer)) private buffer: Buffer) {} 12 | } 13 | 14 | // undefined 15 | console.log(Buffer); 16 | 17 | class Buffer { 18 | constructor(@Inject(BUFFER_SIZE) private size:Number) { 19 | console.log(this.size); 20 | } 21 | } 22 | 23 | // [Function: Buffer] 24 | console.log(Buffer); 25 | 26 | let injector = ReflectiveInjector.resolveAndCreate([ 27 | { provide: BUFFER_SIZE, useValue: 42 }, 28 | Buffer, 29 | Socket 30 | ]); 31 | 32 | console.log(injector.get(Socket)); 33 | -------------------------------------------------------------------------------- /app/ch5/ts/injector-basics/injector.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, 4 | Inject, 5 | Injectable, 6 | OpaqueToken 7 | } from '@angular/core'; 8 | 9 | const BUFFER_SIZE = new OpaqueToken('buffer-size'); 10 | 11 | class Buffer { 12 | constructor(@Inject(BUFFER_SIZE) private size: Number) { 13 | console.log(this.size); 14 | } 15 | } 16 | 17 | @Injectable() 18 | class Socket { 19 | constructor(private buffer: Buffer) {} 20 | } 21 | 22 | let injector = ReflectiveInjector.resolveAndCreate([ 23 | { provide: BUFFER_SIZE, useValue: 42 }, 24 | Buffer, 25 | Socket 26 | ]); 27 | 28 | injector.get(Socket); 29 | -------------------------------------------------------------------------------- /app/ch5/ts/parent-child/simple-example.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { 3 | ReflectiveInjector, Inject, Injectable 4 | } from '@angular/core'; 5 | 6 | class Http {} 7 | 8 | @Injectable() 9 | class UserService { 10 | constructor(public http: Http) {} 11 | } 12 | 13 | let parentInjector = ReflectiveInjector.resolveAndCreate([ 14 | Http 15 | ]); 16 | 17 | let childInjector = parentInjector.resolveAndCreateChild([ 18 | UserService 19 | ]); 20 | 21 | console.log(childInjector.get(UserService)); 22 | console.log(childInjector.get(Http) === parentInjector.get(Http)); 23 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/add_developer.html: -------------------------------------------------------------------------------- 1 | {{errorMessage}} 2 | {{successMessage}} 3 |
4 |
5 | 6 |
7 | 8 | 13 |
14 |
15 |
16 | 17 |
18 | 19 | 23 |
24 |
25 |
26 | 27 |
28 | 31 | 35 |
36 |
37 |
38 | 39 | 40 |
41 | 42 |
-------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/add_developer.ts: -------------------------------------------------------------------------------- 1 | import {Component, Directive} from '@angular/core'; 2 | import {NG_VALIDATORS} from '@angular/forms'; 3 | 4 | import {Developer} from './developer'; 5 | import {DeveloperCollection} from './developer_collection'; 6 | 7 | function validateEmail(emailControl) { 8 | if (!emailControl.value || /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(emailControl.value)) { 9 | return null; 10 | } else { 11 | return { 'invalidEmail': true }; 12 | } 13 | } 14 | 15 | @Directive({ 16 | selector: '[email-input]', 17 | providers: [{ 18 | provide: NG_VALIDATORS, 19 | multi: true, 20 | useValue: validateEmail 21 | }] 22 | }) 23 | export class EmailValidator {} 24 | 25 | @Component({ 26 | selector: 'dev-add', 27 | templateUrl: './add_developer.html', 28 | styles: [ 29 | `input.ng-touched.ng-invalid, 30 | select.ng-touched.ng-invalid { 31 | border: 1px solid red; 32 | }` 33 | ] 34 | }) 35 | export class AddDeveloper { 36 | developer = new Developer(); 37 | errorMessage: string; 38 | successMessage: string; 39 | submitted = false; 40 | technologies: string[] = [ 41 | 'JavaScript', 42 | 'C', 43 | 'C#', 44 | 'Clojure' 45 | ]; 46 | 47 | constructor(private developers: DeveloperCollection) {} 48 | 49 | addDeveloper() { 50 | this.developer.id = this.developers.getAll().length + 1; 51 | this.developers.addDeveloper(this.developer); 52 | this.successMessage = `Developer ${this.developer.realName} was successfully added`; 53 | this.submitted = true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import {LocationStrategy, HashLocationStrategy} from '@angular/common'; 5 | import {RouterModule} from '@angular/router'; 6 | import {FormsModule} from '@angular/forms'; 7 | import {Home} from './home'; 8 | import {DeveloperCollection} from './developer_collection'; 9 | import {AddDeveloper} from './add_developer'; 10 | import {ControlErrors} from './control_errors'; 11 | import {EmailValidator} from './email_validator'; 12 | 13 | @Component({ 14 | selector: 'app', 15 | template: ` 16 | 22 | 23 | `, 24 | providers: [DeveloperCollection] 25 | }) 26 | class App {} 27 | 28 | const routingModule = RouterModule.forRoot([ 29 | { 30 | path: '', 31 | pathMatch: 'full', 32 | redirectTo: 'home' 33 | }, 34 | { 35 | component: Home, 36 | path: 'home' 37 | }, 38 | { 39 | component: AddDeveloper, 40 | path: 'dev-add' 41 | }, 42 | { 43 | path: 'add-dev', 44 | redirectTo: 'dev-add', 45 | } 46 | ]); 47 | 48 | @NgModule({ 49 | imports: [BrowserModule, FormsModule, routingModule], 50 | declarations: [App, Home, AddDeveloper, ControlErrors, EmailValidator], 51 | providers: [{ provide: LocationStrategy, useClass: HashLocationStrategy }], 52 | bootstrap: [App] 53 | }) 54 | class AppModule {} 55 | 56 | platformBrowserDynamic().bootstrapModule(AppModule); 57 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/control_errors.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Host} from '@angular/core'; 2 | import {NgForm} from '@angular/forms'; 3 | 4 | @Component({ 5 | template: `
{{currentError}}
`, 6 | selector: 'control-errors' 7 | }) 8 | export class ControlErrors { 9 | @Input() errors: Object; 10 | @Input() control: string; 11 | 12 | constructor(@Host() private formDir: NgForm) {} 13 | 14 | get currentError() { 15 | let control = this.formDir.controls[this.control]; 16 | let errorMessages = []; 17 | if (control && control.touched) { 18 | errorMessages = Object.keys(this.errors) 19 | .map(k => control.hasError(k) ? this.errors[k] : null) 20 | .filter(error => !!error); 21 | } 22 | return errorMessages.pop(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/developer.ts: -------------------------------------------------------------------------------- 1 | export class Developer { 2 | public id: number; 3 | public githubHandle: string; 4 | public avatarUrl: string; 5 | public realName: string; 6 | public email: string; 7 | public technology: string; 8 | public popular: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/developer_collection.ts: -------------------------------------------------------------------------------- 1 | import {Developer} from './developer'; 2 | 3 | export class DeveloperCollection { 4 | private developers: Developer[] = []; 5 | 6 | getUserByGitHubHandle(username: string) { 7 | return this.developers.filter(u => u.githubHandle === username).pop(); 8 | } 9 | 10 | getUserById(id: number) { 11 | return this.developers.filter(u => u.id === id).pop(); 12 | } 13 | 14 | addDeveloper(dev: Developer) { 15 | this.developers.push(dev); 16 | } 17 | 18 | getAll() { 19 | return this.developers; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/email_validator.ts: -------------------------------------------------------------------------------- 1 | import {Directive} from '@angular/core'; 2 | import {NG_VALIDATORS} from '@angular/forms'; 3 | 4 | function validateEmail(emailControl) { 5 | if (!emailControl.value || /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(emailControl.value)) { 6 | return null; 7 | } else { 8 | return { 'invalidEmail': true }; 9 | } 10 | } 11 | 12 | @Directive({ 13 | selector: '[email-input]', 14 | providers: [{ provide: NG_VALIDATORS, useValue: validateEmail, multi: true }] 15 | }) 16 | export class EmailValidator {} 17 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 |
EmailReal nameTechnologyPopular
{{dev.email}}{{dev.realName}}{{dev.technology}} 13 | Yes 14 | Not yet 15 |
18 |
There are no any developers yet
19 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/home.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {DeveloperCollection} from './developer_collection'; 3 | 4 | @Component({ 5 | selector: 'home', 6 | templateUrl: './home.html' 7 | }) 8 | export class Home { 9 | constructor(private developers: DeveloperCollection) {} 10 | 11 | getDevelopers() { 12 | return this.developers.getAll(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= APP_TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Loading... 17 | 18 | 19 | <%= INIT %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/ch6/ts/multi-page-template-driven/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Page with multiple views and template-driven form", 3 | "description": "Page with multiple views and template-driven form", 4 | "id": 1, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch6/ts/simple-two-way-data-binding/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {FormsModule} from '@angular/forms'; 4 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 5 | 6 | @Component({ 7 | selector: 'app', 8 | template: ` 9 | 10 |
{{name}}
11 | ` 12 | }) 13 | class App { 14 | name: string; 15 | } 16 | 17 | @NgModule({ 18 | imports: [BrowserModule, FormsModule], 19 | declarations: [App], 20 | bootstrap: [App] 21 | }) 22 | class AppModule {} 23 | 24 | platformBrowserDynamic().bootstrapModule(AppModule); 25 | -------------------------------------------------------------------------------- /app/ch6/ts/simple-two-way-data-binding/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= APP_TITLE %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <%= INIT %> 14 | 15 | -------------------------------------------------------------------------------- /app/ch6/ts/simple-two-way-data-binding/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Simple two-way data-binding", 3 | "description": "Simple two-way data-binding", 4 | "id": 2, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch6/ts/step-0/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import {LocationStrategy, HashLocationStrategy} from '@angular/common'; 5 | import {RouterModule} from '@angular/router'; 6 | // import {HomeModule} from './home'; 7 | import {DeveloperCollection} from './developer_collection'; 8 | // import {AddDeveloper} from './add_developer'; 9 | 10 | @Component({ 11 | selector: 'app', 12 | template: `...`, 13 | providers: [DeveloperCollection] 14 | }) 15 | class App {} 16 | 17 | const routeModule = RouterModule.forRoot([]); 18 | 19 | @NgModule({ 20 | declarations: [App], 21 | bootstrap: [App], 22 | imports: [BrowserModule, routeModule], 23 | providers: [{ provide: LocationStrategy, useClass: HashLocationStrategy }] 24 | }) 25 | class AppModule {} 26 | 27 | platformBrowserDynamic().bootstrapModule(AppModule); 28 | 29 | -------------------------------------------------------------------------------- /app/ch6/ts/step-0/developer.ts: -------------------------------------------------------------------------------- 1 | export class Developer { 2 | public id: number; 3 | public githubHandle: string; 4 | public avatarUrl: string; 5 | public realName: string; 6 | public email: string; 7 | public technology: string; 8 | public popular: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /app/ch6/ts/step-0/developer_collection.ts: -------------------------------------------------------------------------------- 1 | import {Developer} from './developer'; 2 | 3 | export class DeveloperCollection { 4 | private developers: Developer[]; 5 | 6 | constructor() { 7 | this.developers = []; 8 | } 9 | 10 | getUserByGitHubHandle(username: string) { 11 | return this.developers.filter(u => u.githubHandle === username).pop(); 12 | } 13 | 14 | getUserById(id: number) { 15 | return this.developers.filter(u => u.id === id).pop(); 16 | } 17 | 18 | addDeveloper(dev: Developer) { 19 | this.developers.push(dev); 20 | } 21 | 22 | getAll() { 23 | return this.developers; 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/ch6/ts/step-0/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Loading... 17 | 18 | 19 | <%= INIT %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1-async/add_developer.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {RouterModule} from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'dev-add', 6 | template: `Add Developer` 7 | }) 8 | export class AddDeveloper {} 9 | 10 | @NgModule({ 11 | declarations: [AddDeveloper], 12 | imports: [RouterModule.forChild([ 13 | { 14 | path: '', 15 | component: AddDeveloper 16 | } 17 | ])] 18 | }) 19 | export class AddDeveloperModule {} 20 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1-async/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import {LocationStrategy, HashLocationStrategy} from '@angular/common'; 5 | import {RouterModule} from '@angular/router'; 6 | import {DeveloperCollection} from './developer_collection'; 7 | 8 | @Component({ 9 | selector: 'app', 10 | template: ` 11 | 17 | 18 | ` 19 | }) 20 | class App {} 21 | 22 | const routingModule = RouterModule.forRoot([ 23 | { 24 | path: '', 25 | redirectTo: 'home', 26 | pathMatch: 'full' 27 | }, 28 | { 29 | path: 'home', 30 | loadChildren: './home#HomeModule' 31 | }, 32 | { 33 | path: 'dev-add', 34 | loadChildren: './add_developer#AddDeveloperModule' 35 | } 36 | ]); 37 | 38 | @NgModule({ 39 | imports: [BrowserModule, routingModule], 40 | providers: [{ provide: LocationStrategy, useClass: HashLocationStrategy }, DeveloperCollection], 41 | declarations: [App], 42 | bootstrap: [App] 43 | }) 44 | class AppModule {} 45 | 46 | platformBrowserDynamic().bootstrapModule(AppModule); 47 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1-async/developer.ts: -------------------------------------------------------------------------------- 1 | export class Developer { 2 | public id: number; 3 | public githubHandle: string; 4 | public avatarUrl: string; 5 | public realName: string; 6 | public email: string; 7 | public technology: string; 8 | public popular: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1-async/developer_collection.ts: -------------------------------------------------------------------------------- 1 | import {Developer} from './developer'; 2 | 3 | export class DeveloperCollection { 4 | private developers: Developer[] = []; 5 | 6 | getUserByGitHubHandle(username: string) { 7 | return this.developers.filter(u => u.githubHandle === username).pop(); 8 | } 9 | 10 | getUserById(id: number) { 11 | return this.developers.filter(u => u.id === id).pop(); 12 | } 13 | 14 | addDeveloper(dev: Developer) { 15 | this.developers.push(dev); 16 | } 17 | 18 | getAll() { 19 | return this.developers; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1-async/home.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {RouterModule} from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'home', 6 | template: `Home` 7 | }) 8 | export class Home {} 9 | 10 | @NgModule({ 11 | declarations: [Home], 12 | imports: [RouterModule.forChild([ 13 | { 14 | path: '', 15 | component: Home 16 | } 17 | ])] 18 | }) 19 | export class HomeModule {} 20 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1-async/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= TITLE %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Loading... 18 | 19 | 20 | <%= INIT %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1/add_developer.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'dev-add', 5 | template: `Add developer` 6 | }) 7 | export class AddDeveloper {} 8 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import {LocationStrategy, HashLocationStrategy} from '@angular/common'; 5 | import {RouterModule} from '@angular/router'; 6 | import {Home} from './home'; 7 | import {DeveloperCollection} from './developer_collection'; 8 | import {AddDeveloper} from './add_developer'; 9 | 10 | @Component({ 11 | selector: 'app', 12 | template: ` 13 | 19 | 20 | `, 21 | providers: [DeveloperCollection] 22 | }) 23 | class App {} 24 | 25 | const routingModule = RouterModule.forRoot([ 26 | { 27 | path: '', 28 | redirectTo: 'home', 29 | pathMatch: 'full' 30 | }, 31 | { 32 | path: 'home', 33 | component: Home 34 | }, 35 | { 36 | path: 'dev-add', 37 | component: AddDeveloper 38 | }, 39 | { 40 | path: 'add-dev', 41 | redirectTo: 'dev-add' 42 | } 43 | ]); 44 | 45 | @NgModule({ 46 | imports: [BrowserModule, routingModule], 47 | declarations: [App, Home, AddDeveloper], 48 | providers: [{ provide: LocationStrategy, useClass: HashLocationStrategy }], 49 | bootstrap: [App] 50 | }) 51 | class AppModule {} 52 | 53 | platformBrowserDynamic().bootstrapModule(AppModule); 54 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1/developer.ts: -------------------------------------------------------------------------------- 1 | export class Developer { 2 | public id: number; 3 | public githubHandle: string; 4 | public avatarUrl: string; 5 | public realName: string; 6 | public email: string; 7 | public technology: string; 8 | public popular: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1/developer_collection.ts: -------------------------------------------------------------------------------- 1 | import {Developer} from './developer'; 2 | 3 | export class DeveloperCollection { 4 | private developers: Developer[] = []; 5 | 6 | getUserByGitHubHandle(username: string) { 7 | return this.developers.filter(u => u.githubHandle === username).pop(); 8 | } 9 | 10 | getUserById(id: number) { 11 | return this.developers.filter(u => u.id === id).pop(); 12 | } 13 | 14 | addDeveloper(dev: Developer) { 15 | this.developers.push(dev); 16 | } 17 | 18 | getAll() { 19 | return this.developers; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1/home.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'home', 5 | template: `Home` 6 | }) 7 | export class Home {} 8 | -------------------------------------------------------------------------------- /app/ch6/ts/step-1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= APP_TITLE %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Loading... 18 | 19 | 20 | <%= INIT %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/add_developer.html: -------------------------------------------------------------------------------- 1 | {{errorMessage}} 2 | {{successMessage}} 3 |
4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 22 |
23 |
24 |
25 | 26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/add_developer.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Developer} from './developer'; 3 | import {DeveloperCollection} from './developer_collection'; 4 | 5 | @Component({ 6 | selector: 'dev-add', 7 | templateUrl: './add_developer.html', 8 | styles: [ 9 | `input.ng-touched.ng-invalid, 10 | select.ng-touched.ng-invalid { 11 | border: 1px solid red; 12 | }` 13 | ] 14 | }) 15 | export class AddDeveloper { 16 | developer = new Developer(); 17 | errorMessage: string; 18 | successMessage: string; 19 | submitted = false; 20 | technologies: string[] = [ 21 | 'JavaScript', 22 | 'C', 23 | 'C#', 24 | 'Clojure' 25 | ]; 26 | 27 | constructor(private developers: DeveloperCollection) {} 28 | 29 | addDeveloper() {} 30 | } 31 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import {LocationStrategy, HashLocationStrategy} from '@angular/common'; 5 | import {RouterModule} from '@angular/router'; 6 | import {FormsModule} from '@angular/forms'; 7 | // import {Home} from './home'; 8 | import {DeveloperCollection} from './developer_collection'; 9 | import {AddDeveloper} from './add_developer'; 10 | import {ControlErrors} from './control_errors'; 11 | import {EmailValidator} from './email_validator'; 12 | 13 | @Component({ 14 | selector: 'app', 15 | template: ` 16 | 22 | 23 | `, 24 | providers: [DeveloperCollection] 25 | }) 26 | class App {} 27 | 28 | const routingModule = RouterModule.forRoot([ 29 | { 30 | path: '', 31 | pathMatch: 'full', 32 | redirectTo: 'home' 33 | }, 34 | // { 35 | // component: Home, 36 | // path: 'home' 37 | // }, 38 | { 39 | component: AddDeveloper, 40 | path: 'dev-add' 41 | }, 42 | { 43 | path: 'add-dev', 44 | redirectTo: 'dev-add' 45 | } 46 | ]); 47 | 48 | @NgModule({ 49 | imports: [BrowserModule, FormsModule, routingModule], 50 | declarations: [App, /*Home*/, AddDeveloper, ControlErrors, EmailValidator], 51 | providers: [{ provide: LocationStrategy, useClass: HashLocationStrategy }], 52 | bootstrap: [App] 53 | }) 54 | class AppModule {} 55 | 56 | platformBrowserDynamic().bootstrapModule(AppModule); 57 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/control_errors.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Host} from '@angular/core'; 2 | import {NgForm} from '@angular/forms'; 3 | 4 | @Component({ 5 | template: `
{{currentError}}
`, 6 | selector: 'control-errors' 7 | }) 8 | export class ControlErrors { 9 | @Input() errors: Object; 10 | @Input() control: string; 11 | 12 | constructor(@Host() private formDir: NgForm) {} 13 | 14 | get currentError() { 15 | let control = this.formDir.controls[this.control]; 16 | let errorMessages = []; 17 | if (control && control.touched) { 18 | errorMessages = Object.keys(this.errors) 19 | .map(k => control.hasError(k) ? this.errors[k] : null) 20 | .filter(error => !!error); 21 | } 22 | return errorMessages.pop(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/developer.ts: -------------------------------------------------------------------------------- 1 | export class Developer { 2 | public id: number; 3 | public githubHandle: string; 4 | public avatarUrl: string; 5 | public realName: string; 6 | public email: string; 7 | public technology: string; 8 | public popular: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/developer_collection.ts: -------------------------------------------------------------------------------- 1 | import {Developer} from './developer'; 2 | 3 | export class DeveloperCollection { 4 | private developers: Developer[]; 5 | 6 | constructor() { 7 | this.developers = []; 8 | } 9 | 10 | getUserByGitHubHandle(username: string) { 11 | return this.developers.filter(u => u.githubHandle === username).pop(); 12 | } 13 | 14 | getUserById(id: number) { 15 | return this.developers.filter(u => u.id === id).pop(); 16 | } 17 | 18 | addDeveloper(dev: Developer) { 19 | this.developers.push(dev); 20 | } 21 | 22 | getAll() { 23 | return this.developers; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/email_validator.ts: -------------------------------------------------------------------------------- 1 | import {Directive} from '@angular/core'; 2 | import {NG_VALIDATORS} from '@angular/forms'; 3 | 4 | function validateEmail(emailControl) { 5 | if (!emailControl.value || /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(emailControl.value)) { 6 | return null; 7 | } else { 8 | return { 'invalidEmail': true }; 9 | } 10 | } 11 | 12 | @Directive({ 13 | selector: '[email-input]', 14 | providers: [{ provide: NG_VALIDATORS, useValue: validateEmail, multi: true }] 15 | }) 16 | export class EmailValidator {} 17 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 20 | 21 |
NameEmailReal nameTechnologyPopular
11 | {{dev.githubHandle}} 12 | {{dev.email}}{{dev.realName}}{{dev.technology}} 17 | Yes 18 | Not yet 19 |
22 |
There are no any developers yet
23 | -------------------------------------------------------------------------------- /app/ch6/ts/step-2/home.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/getting-started-with-angular/8e6c36a992b06dbd843bb0aff869ed47cd321c61/app/ch6/ts/step-2/home.ts -------------------------------------------------------------------------------- /app/ch6/ts/step-2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Loading... 17 | 18 | 19 | <%= INIT %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/ch7/ts/async_pipe/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component, Pipe, PipeTransform} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {Observable} from 'rxjs/Observable'; 4 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 5 | import {HttpModule, Http} from '@angular/http'; 6 | 7 | @Component({ 8 | selector: 'greeting', 9 | template: 'Hello {{ greetingPromise | async }}' 10 | }) 11 | class Greeting { 12 | username: string; 13 | greetingPromise = new Promise(resolve => this.resolve = resolve); 14 | resolve: Function; 15 | constructor() { 16 | setTimeout(_ => { 17 | this.resolve('Foobar!'); 18 | }, 3000); 19 | } 20 | } 21 | 22 | @Component({ 23 | selector: 'timer', 24 | template: '{{ timer | async | date: "medium" }}' 25 | }) 26 | class Timer { 27 | username: string; 28 | timer: Observable; 29 | constructor() { 30 | let counter = 0; 31 | this.timer = Observable.create(observer => { 32 | setInterval(() => { 33 | observer.next(new Date().getTime()); 34 | }, 1000); 35 | }); 36 | } 37 | } 38 | 39 | @Component({ 40 | selector: 'app', 41 | template: '
' 42 | }) 43 | class App {} 44 | 45 | @NgModule({ 46 | imports: [HttpModule, BrowserModule], 47 | declarations: [App, Greeting, Timer], 48 | bootstrap: [App] 49 | }) 50 | class AppModule {} 51 | 52 | platformBrowserDynamic().bootstrapModule(AppModule); 53 | -------------------------------------------------------------------------------- /app/ch7/ts/async_pipe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <%= INIT %> 14 | 15 | -------------------------------------------------------------------------------- /app/ch7/ts/async_pipe/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Async pipe", 3 | "description": "Async pipe demo", 4 | "id": 6, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch7/ts/builtin_pipes/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | 5 | @Component({ 6 | selector: 'app', 7 | template: ` 8 |
    9 |
  • CurrencyPipe - {{ currencyValue | currency: 'USD' }}
  • 10 |
  • DatePipe - {{ dateValue | date: 'shortTime' }}
  • 11 |
  • DecimalPipe - {{ decimalValue | number: '3.1-2' }}
  • 12 |
  • JsonPipe - {{ jsObject | json }}
  • 13 |
  • LowerCasePipe - {{ uppercaseValue | lowercase }}
  • 14 |
  • UpperCaseFilter - {{ lowercaseValue | uppercase }}
  • 15 |
  • PercentPipe - {{ percentValue | percent: '2.1-2' }}
  • 16 |
  • SlicePipe - {{ array | slice: 1: 2 }}
  • 17 |
18 | ` 19 | }) 20 | class App { 21 | currencyValue = 42; 22 | dateValue = new Date('02/11/2010'); 23 | decimalValue = 42.1618; 24 | jsObject = { foo: 'bar' }; 25 | uppercaseValue = 'FOOBAR'; 26 | lowercaseValue = 'foobar'; 27 | percentValue = 42; 28 | array = [1, 2, 3]; 29 | } 30 | 31 | @NgModule({ 32 | imports: [BrowserModule], 33 | declarations: [App], 34 | bootstrap: [App] 35 | }) 36 | class AppModule {} 37 | 38 | platformBrowserDynamic().bootstrapModule(AppModule); 39 | -------------------------------------------------------------------------------- /app/ch7/ts/builtin_pipes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <%= INIT %> 25 | 26 | -------------------------------------------------------------------------------- /app/ch7/ts/builtin_pipes/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Built-in pipes", 3 | "description": "Built-in pipes demo", 4 | "id": 4, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/add_developer.html: -------------------------------------------------------------------------------- 1 | {{errorMessage}} 2 | {{successMessage}} 3 | 4 |
5 |
6 | 7 |
8 | 9 | 13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 | 31 |
32 |
33 |
34 | 35 |
36 | 37 | 41 |
42 |
43 |
44 | 45 |
46 | 49 | 53 |
54 |
55 |
56 | 57 | 58 |
59 |
60 | 61 |
62 |
63 | 65 |
-------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/add_developer.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, OnDestroy} from '@angular/core'; 2 | import {FormBuilder, FormGroup, Validators} from '@angular/forms'; 3 | import {Response} from '@angular/http'; 4 | import {GitHubGateway} from './github_gateway'; 5 | import {Developer} from './developer'; 6 | import {DeveloperCollection} from './developer_collection'; 7 | 8 | import {Subscription} from 'rxjs/Subscription'; 9 | import 'rxjs/add/operator/map'; 10 | import 'rxjs/add/operator/catch'; 11 | 12 | function validateEmail(emailControl) { 13 | if (!emailControl.value || /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(emailControl.value)) { 14 | return null; 15 | } else { 16 | return { 'invalidEmail': true }; 17 | } 18 | } 19 | 20 | @Component({ 21 | selector: 'dev-add', 22 | templateUrl: './add_developer.html', 23 | styles: [ 24 | `input.ng-touched.ng-invalid { 25 | border: 1px solid red; 26 | }` 27 | ], 28 | providers: [GitHubGateway] 29 | }) 30 | export class AddDeveloper implements OnInit, OnDestroy { 31 | submitted = false; 32 | importDevForm: FormGroup; 33 | addDevForm: FormGroup; 34 | errorMessage: string; 35 | successMessage: string; 36 | technologies: string[] = [ 37 | 'JavaScript', 38 | 'C', 39 | 'C#', 40 | 'Clojure' 41 | ]; 42 | 43 | private subscription: Subscription; 44 | 45 | constructor(private githubAPI: GitHubGateway, private developers: DeveloperCollection, fb: FormBuilder) { 46 | this.importDevForm = fb.group({ 47 | githubHandle: ['', Validators.required], 48 | fetchFromGitHub: [false] 49 | }); 50 | this.addDevForm = fb.group({ 51 | realName: ['', Validators.required], 52 | email: ['', validateEmail], 53 | technology: ['', Validators.required], 54 | popular: [false] 55 | }); 56 | } 57 | 58 | ngOnDestroy() { 59 | this.subscription.unsubscribe(); 60 | } 61 | 62 | get isFormValid() { 63 | return (this.importDevForm.controls['fetchFromGitHub'].value && this.importDevForm.valid) || 64 | (!this.importDevForm.controls['fetchFromGitHub'].value && this.addDevForm.valid) 65 | } 66 | 67 | addDeveloper() { 68 | let model; 69 | if (this.importDevForm.controls['fetchFromGitHub'].value) { 70 | model = this.importDevForm.value; 71 | if (this.developers.getUserByGitHubHandle(model.githubHadle)) { 72 | this.errorMessage = `Developer with githubHandle ${model.githubHadle} already exists`; 73 | return; 74 | } 75 | this.submitted = true; 76 | this.githubAPI.getUser(model.githubHandle) 77 | // .catch((error, source) => { 78 | // console.log(error) 79 | // return error; 80 | // }) 81 | .map((r: Response) => r.json()) 82 | .subscribe((res: any) => { 83 | let dev = new Developer(); 84 | dev.githubHandle = res.login; 85 | dev.email = res.email; 86 | dev.popular = res.followers >= 1000; 87 | dev.realName = res.name; 88 | dev.id = res.id; 89 | dev.avatarUrl = res.avatar_url; 90 | this.developers.addDeveloper(dev); 91 | this.successMessage = `Developer ${dev.githubHandle} successfully imported from GitHub`; 92 | }); 93 | } else { 94 | this.submitted = true; 95 | model = this.addDevForm.value; 96 | model.id = this.developers.getAll().length + 1; 97 | let dev = new Developer(); 98 | Object.assign(dev, model); 99 | this.developers.addDeveloper(dev); 100 | this.successMessage = `Developer ${model.realName} was successfully added`; 101 | } 102 | return false; 103 | } 104 | 105 | ngOnInit() { 106 | this.toggleControls(this.importDevForm.controls['fetchFromGitHub'].value); 107 | this.subscription = this.importDevForm.controls['fetchFromGitHub'] 108 | .valueChanges.subscribe(this.toggleControls.bind(this)); 109 | } 110 | 111 | private toggleControls(importEnabled: boolean) { 112 | const addDevControls = this.addDevForm.controls; 113 | if (importEnabled) { 114 | this.importDevForm.controls['githubHandle'].enable(); 115 | Object.keys(addDevControls).forEach((c: string) => addDevControls[c].disable()); 116 | } else { 117 | this.importDevForm.controls['githubHandle'].disable(); 118 | Object.keys(addDevControls).forEach((c: string) => addDevControls[c].enable()); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import {LocationStrategy, HashLocationStrategy} from '@angular/common'; 5 | import {RouterModule} from '@angular/router'; 6 | import {ReactiveFormsModule} from '@angular/forms'; 7 | import {Home} from './home'; 8 | import {DeveloperCollection} from './developer_collection'; 9 | import {AddDeveloper} from './add_developer'; 10 | import {ControlErrors} from './control_errors'; 11 | import {BooleanPipe} from './boolean_pipe'; 12 | import {DeveloperDetails, devDetailsRoutes} from './developer_details'; 13 | import {DeveloperBasicInfo} from './developer_basic_info'; 14 | import {DeveloperAdvancedInfo} from './developer_advanced_info'; 15 | import {HttpModule} from '@angular/http'; 16 | 17 | @Component({ 18 | selector: 'app', 19 | template: ` 20 | 26 | 27 | `, 28 | providers: [DeveloperCollection] 29 | }) 30 | class App {} 31 | 32 | const routingModule = RouterModule.forRoot([ 33 | { 34 | path: '', 35 | pathMatch: 'full', 36 | redirectTo: 'home' 37 | }, 38 | { 39 | component: Home, 40 | path: 'home' 41 | }, 42 | { 43 | component: AddDeveloper, 44 | path: 'dev-add' 45 | }, 46 | { 47 | component: DeveloperDetails, 48 | path: 'dev-details/:id', 49 | children: devDetailsRoutes 50 | }, 51 | { 52 | path: 'add-dev', 53 | redirectTo: 'dev-add', 54 | } 55 | ]); 56 | 57 | @NgModule({ 58 | imports: [BrowserModule, ReactiveFormsModule, HttpModule, routingModule], 59 | declarations: [App, Home, AddDeveloper, ControlErrors, BooleanPipe, DeveloperDetails, DeveloperBasicInfo, DeveloperAdvancedInfo], 60 | providers: [{ provide: LocationStrategy, useClass: HashLocationStrategy }], 61 | bootstrap: [App] 62 | }) 63 | class AppModule {} 64 | 65 | platformBrowserDynamic().bootstrapModule(AppModule); 66 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/boolean_pipe.ts: -------------------------------------------------------------------------------- 1 | import {Pipe, PipeTransform} from '@angular/core'; 2 | 3 | @Pipe({ name: 'boolean' }) 4 | export class BooleanPipe implements PipeTransform { 5 | transform(flag: boolean, trueValue: any, falseValue: any): string { 6 | return flag ? trueValue : falseValue; 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/control_errors.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Host} from '@angular/core'; 2 | import {FormGroupDirective} from '@angular/forms'; 3 | 4 | 5 | @Component({ 6 | template: `
{{currentError}}
`, 7 | selector: 'control-errors' 8 | }) 9 | export class ControlErrors { 10 | @Input() errors: Object; 11 | @Input() control: string; 12 | 13 | constructor(@Host() private formDir: FormGroupDirective) {} 14 | 15 | get currentError() { 16 | let control = this.formDir.form.controls[this.control]; 17 | let errorMessages = []; 18 | if (control && control.touched) { 19 | errorMessages = Object.keys(this.errors) 20 | .map(k => control.hasError(k) ? this.errors[k] : null) 21 | .filter(error => !!error); 22 | } 23 | return errorMessages.pop(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/developer.ts: -------------------------------------------------------------------------------- 1 | export class Developer { 2 | public id: number; 3 | public githubHandle: string; 4 | public avatarUrl: string; 5 | public realName: string; 6 | public email: string; 7 | public technology: string; 8 | public popular: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/developer_advanced_info.ts: -------------------------------------------------------------------------------- 1 | import {Component, Inject, forwardRef, Host} from '@angular/core'; 2 | import {DeveloperDetails} from './developer_details'; 3 | import {Developer} from './developer'; 4 | 5 | @Component({ 6 | selector: 'dev-details-advanced', 7 | styles: [` 8 | .row span { 9 | display: inline-block; 10 | margin-left: 5px; 11 | } 12 | ` 13 | ], 14 | template: ` 15 |

{{dev.githubHandle}}

16 |
17 |
18 | {{dev.realName}} 19 |
20 |
21 | {{dev.technology}} 22 |
23 |
24 | {{dev.email || 'none'}} 25 |
26 |
27 | {{dev.popular ? 'Yes' : 'Not yet' }} 28 |
29 |
30 | ` 31 | }) 32 | export class DeveloperAdvancedInfo { 33 | dev: Developer; 34 | constructor(@Inject(forwardRef(() => DeveloperDetails)) @Host() parent: DeveloperDetails) { 35 | this.dev = parent.dev; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/developer_basic_info.ts: -------------------------------------------------------------------------------- 1 | import {Component, Inject, forwardRef, Host} from '@angular/core'; 2 | import {DeveloperDetails} from './developer_details'; 3 | import {Developer} from './developer'; 4 | 5 | @Component({ 6 | selector: 'dev-details-basic', 7 | styles: [ 8 | `.avatar { 9 | border-radius: 150px; 10 | }` 11 | ], 12 | template: ` 13 |

{{dev.githubHandle | uppercase}}

14 | 15 | 16 | ` 17 | }) 18 | export class DeveloperBasicInfo { 19 | dev: Developer; 20 | constructor(@Inject(forwardRef(() => DeveloperDetails)) @Host() parent: DeveloperDetails) { 21 | this.dev = parent.dev; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/developer_collection.ts: -------------------------------------------------------------------------------- 1 | import {Developer} from './developer'; 2 | 3 | export class DeveloperCollection { 4 | private developers: Developer[]; 5 | 6 | constructor() { 7 | this.developers = []; 8 | } 9 | 10 | getUserByGitHubHandle(username: string) { 11 | return this.developers.filter(u => u.githubHandle === username).pop(); 12 | } 13 | 14 | getUserById(id: number) { 15 | return this.developers.filter(u => u.id === id).pop(); 16 | } 17 | 18 | addDeveloper(dev: Developer) { 19 | this.developers.push(dev); 20 | } 21 | 22 | getAll() { 23 | return this.developers; 24 | } 25 | } -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/developer_details.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {ActivatedRoute} from '@angular/router'; 3 | import {Developer} from './developer'; 4 | import {DeveloperCollection} from './developer_collection'; 5 | import {DeveloperBasicInfo} from './developer_basic_info'; 6 | import {DeveloperAdvancedInfo} from './developer_advanced_info'; 7 | 8 | import 'rxjs/add/operator/take'; 9 | 10 | @Component({ 11 | selector: 'dev-details', 12 | template: ` 13 |
14 | 18 | 19 |
20 | `, 21 | }) 22 | export class DeveloperDetails { 23 | public dev: Developer; 24 | 25 | constructor(private route: ActivatedRoute, private developers: DeveloperCollection) {} 26 | 27 | ngOnInit() { 28 | this.route.params.take(1) 29 | .subscribe((params: any) => { 30 | this.dev = this.developers.getUserById(parseInt(params['id'])); 31 | }); 32 | } 33 | } 34 | 35 | export const devDetailsRoutes = [ 36 | { path: '', redirectTo: 'dev-basic-info', pathMatch: 'full' }, 37 | { component: DeveloperBasicInfo, path: 'dev-basic-info' }, 38 | { component: DeveloperAdvancedInfo, path: 'dev-details-advanced' } 39 | ]; 40 | 41 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/github_gateway.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Http} from '@angular/http'; 3 | 4 | @Injectable() 5 | export class GitHubGateway { 6 | constructor(private http: Http) {} 7 | getUser(username: string) { 8 | return this.http.get(`https://api.github.com/users/${username}`); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 |
GitHub handleEmailReal nameTechnologyPopular
11 | 12 | {{dev.githubHandle}} 13 | 14 | {{dev.email}}{{dev.realName}}{{dev.technology}}{{dev.popular | boolean: 'Yes': 'No'}}
21 |
There are no any developers yet
22 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/home.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {DeveloperCollection} from './developer_collection'; 3 | 4 | @Component({ 5 | selector: 'home', 6 | templateUrl: './home.html' 7 | }) 8 | export class Home { 9 | constructor(private developers: DeveloperCollection) {} 10 | getDevelopers() { 11 | return this.developers.getAll(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= APP_TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Loading... 17 | 18 | 19 | <%= INIT %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/ch7/ts/multi-page-model-driven/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Page with multiple views and model-driven form", 3 | "description": "Page with multiple views and model-driven form", 4 | "id": 3, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch7/ts/statful_pipe/app.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Component, Pipe, PipeTransform} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 4 | import {NgModel} from '@angular/forms'; 5 | import {FetchJsonPipe} from './fetch_json_pipe'; 6 | import {HttpModule} from '@angular/http'; 7 | 8 | @Pipe({ 9 | name: 'objectGet' 10 | }) 11 | class ObjectGetPipe { 12 | transform(obj: Object, args: string[]) { 13 | if (obj) { 14 | return obj[args[0]]; 15 | } 16 | } 17 | } 18 | 19 | @Component({ 20 | selector: 'app', 21 | template: ` 22 | 23 | 24 |
25 | 26 | ` 27 | }) 28 | class App { 29 | username: string; 30 | setUsername(user: string) { 31 | this.username = user; 32 | } 33 | } 34 | 35 | @NgModule({ 36 | imports: [HttpModule, BrowserModule], 37 | declarations: [App, FetchJsonPipe, ObjectGetPipe], 38 | bootstrap: [App] 39 | }) 40 | class AppModule {} 41 | 42 | platformBrowserDynamic().bootstrapModule(AppModule); 43 | -------------------------------------------------------------------------------- /app/ch7/ts/statful_pipe/fetch_json_pipe.ts: -------------------------------------------------------------------------------- 1 | import {Pipe, PipeTransform} from '@angular/core'; 2 | import {Http, Response} from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/toPromise'; 5 | 6 | @Pipe({ 7 | name: 'fetchJson', 8 | pure: false 9 | }) 10 | export class FetchJsonPipe implements PipeTransform { 11 | private data: any; 12 | private prevUrl: string; 13 | constructor(private http: Http) {} 14 | transform(url: string): any { 15 | if (this.prevUrl !== url) { 16 | this.http.get(url) 17 | .toPromise() 18 | .then((data: Response) => data.json()) 19 | .then(result => this.data = result); 20 | this.prevUrl = url; 21 | } 22 | return this.data || {}; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/ch7/ts/statful_pipe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <%= INIT %> 14 | 15 | -------------------------------------------------------------------------------- /app/ch7/ts/statful_pipe/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Developing stateful pipe", 3 | "description": "Developing stateful pipe demo", 4 | "id": 5, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/ch8/ts/todo_webworkers/background_app.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule, Input, Output, EventEmitter} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | import {WorkerAppModule} from '@angular/platform-webworker'; 4 | import {FormsModule} from '@angular/forms'; 5 | import {platformWorkerAppDynamic} from '@angular/platform-webworker-dynamic'; 6 | 7 | interface Todo { 8 | completed: boolean; 9 | label: string; 10 | } 11 | 12 | @Component({ 13 | selector: 'input-box', 14 | template: ` 15 | 16 | 19 | ` 20 | }) 21 | class InputBox { 22 | @Input() inputPlaceholder: string; 23 | @Input() buttonLabel: string; 24 | @Output() inputText = new EventEmitter(); 25 | input: string; 26 | 27 | emitText() { 28 | this.inputText.emit(this.input); 29 | this.input = ''; 30 | } 31 | } 32 | 33 | @Component({ 34 | selector: 'todo-list', 35 | template: ` 36 |
    37 |
  • 38 | 40 | {{todo.label}} 41 |
  • 42 |
43 | `, 44 | styles: [ 45 | `ul li { 46 | list-style: none; 47 | } 48 | .completed { 49 | text-decoration: line-through; 50 | }` 51 | ] 52 | }) 53 | class TodoList { 54 | @Input() todos: Todo[]; 55 | @Output() toggle = new EventEmitter(); 56 | toggleCompletion(index: number) { 57 | let todo = this.todos[index]; 58 | this.toggle.emit(todo); 59 | } 60 | } 61 | 62 | @Component({ 63 | selector: 'todo-app', 64 | template: ` 65 |

Hello {{name}}!

66 | 67 |

68 | Add a new todo: 69 | 72 | 73 |

74 | 75 |

Here's the list of pending todo items:

76 | 78 | 79 | ` 80 | }) 81 | export class TodoApp { 82 | todos: Todo[] = [{ 83 | label: 'Buy milk', 84 | completed: false 85 | }, { 86 | label: "Save the world", 87 | completed: false 88 | }]; 89 | name: string = 'John'; 90 | addTodo(label: string) { 91 | this.todos.push({ 92 | label, 93 | completed: false 94 | }); 95 | } 96 | toggleCompletion(todo: Todo) { 97 | todo.completed = !todo.completed; 98 | } 99 | } 100 | 101 | @NgModule({ 102 | imports: [WorkerAppModule, FormsModule], 103 | declarations: [TodoList, InputBox, TodoApp], 104 | bootstrap: [TodoApp] 105 | }) 106 | class AppModule {} 107 | 108 | platformWorkerAppDynamic().bootstrapModule(AppModule) 109 | .catch(err => console.error(err)); 110 | 111 | -------------------------------------------------------------------------------- /app/ch8/ts/todo_webworkers/bootstrap.ts: -------------------------------------------------------------------------------- 1 | //main entry point 2 | import {bootstrapWorkerUi} from '@angular/platform-webworker'; 3 | 4 | bootstrapWorkerUi('loader.js'); 5 | 6 | -------------------------------------------------------------------------------- /app/ch8/ts/todo_webworkers/config.ts: -------------------------------------------------------------------------------- 1 | System.config({ 2 | baseURL: '/dist/dev/ch8/ts/todo_webworkers/', 3 | map: { 4 | 'rxjs': '/node_modules/rxjs', 5 | }, 6 | paths: { 7 | bootstrap: '/dist/dev/bootstrap', 8 | '@angular/common': '/node_modules/@angular/common/bundles/common.umd.js', 9 | '@angular/compiler': '/node_modules/@angular/compiler/bundles/compiler.umd.js', 10 | '@angular/core': '/node_modules/@angular/core/bundles/core.umd.js', 11 | '@angular/forms': '/node_modules/@angular/forms/bundles/forms.umd.js', 12 | '@angular/http': '/node_modules/@angular/http/bundles/http.umd.js', 13 | '@angular/platform-browser': '/node_modules/@angular/platform-browser/bundles/platform-browser.umd.js', 14 | '@angular/platform-browser-dynamic': '/node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', 15 | '@angular/router': '/node_modules/@angular/router/bundles/router.umd.js', 16 | '@angular/platform-webworker': '/node_modules/@angular/platform-webworker/bundles/platform-webworker.umd.js', 17 | '@angular/platform-webworker-dynamic': '/node_modules/@angular/platform-webworker-dynamic/bundles/platform-webworker-dynamic.umd.js', 18 | 19 | '@angular/common/testing': '/node_modules/@angular/common/bundles/common-testing.umd.js', 20 | '@angular/compiler/testing': '/node_modules/@angular/compiler/bundles/compiler-testing.umd.js', 21 | '@angular/core/testing': '/node_modules/@angular/core/bundles/core-testing.umd.js', 22 | '@angular/http/testing': '/node_modules/@angular/http/bundles/http-testing.umd.js', 23 | '@angular/platform-browser/testing': 24 | '/node_modules/@angular/platform-browser/bundles/platform-browser-testing.umd.js', 25 | '@angular/platform-browser-dynamic/testing': 26 | '/node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', 27 | '@angular/router/testing': '/node_modules/@angular/router/bundles/router-testing.umd.js', 28 | }, 29 | packages: { 30 | // '@angular/router': { 31 | // main: 'index.js', 32 | // defaultExtension: 'js' 33 | // }, 34 | 'rxjs': { 35 | defaultExtension: 'js' 36 | } 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /app/ch8/ts/todo_webworkers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/ch8/ts/todo_webworkers/loader.ts: -------------------------------------------------------------------------------- 1 | importScripts('/node_modules/systemjs/dist/system.src.js', 2 | '/node_modules/reflect-metadata/Reflect.js', 3 | '/node_modules/zone.js/dist/zone.js', 4 | './config.js'); 5 | 6 | System.import('./background_app.js') 7 | .then(() => console.log('The application has started successfully'), 8 | error => console.error('error loading background', error)); 9 | 10 | -------------------------------------------------------------------------------- /app/ch8/ts/todo_webworkers/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Running an application in WebWorkers", 3 | "description": "Running an application in WebWorkers", 4 | "id": 1, 5 | "presented": true 6 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= APP_TITLE %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <%= EXAMPLES_LIST %> 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/system.config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | defaultJSExtensions: true 3 | }); 4 | 5 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "sourceMap": true, 12 | "lib": ["es6", "es2015", "dom"], 13 | "typeRoots": [ 14 | "../../node_modules/@types", 15 | "../../node_modules" 16 | ], 17 | "types": [ 18 | "express", 19 | "node", 20 | "systemjs" 21 | ] 22 | }, 23 | "exclude": [ 24 | "../tools" 25 | ], 26 | "compileOnSave": false 27 | } 28 | 29 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor file 2 | # http://www.appveyor.com/docs/appveyor-yml 3 | # This file: cloned from https://github.com/gruntjs/grunt/blob/master/appveyor.yml 4 | 5 | # Build version format 6 | version: "{build}" 7 | 8 | # Test against this version of Node.js 9 | environment: 10 | nodejs_version: "4.1.0" 11 | # https://github.com/DefinitelyTyped/tsd#tsdrc 12 | # Token has no scope (read-only access to public information) 13 | TSD_GITHUB_TOKEN: "9b18c72997769f3867ef2ec470e626d39661795d" 14 | 15 | build: off 16 | 17 | clone_depth: 10 18 | 19 | # Fix line endings on Windows 20 | init: 21 | - git config --global core.autocrlf true 22 | 23 | install: 24 | - ps: Install-Product node $env:nodejs_version 25 | - npm install -g npm 26 | - ps: $env:path = $env:appdata + "\npm;" + $env:path 27 | - npm install 28 | 29 | test_script: 30 | # Output useful info for debugging. 31 | - node --version && npm --version 32 | # We test multiple Windows shells because of prior stdout buffering issues 33 | # filed against Grunt. https://github.com/joyent/node/issues/3584 34 | - ps: "npm --version # PowerShell" # Pass comment to PS for easier debugging 35 | - npm test 36 | -------------------------------------------------------------------------------- /gulpfile.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import {runSequence, task} from './tools/utils'; 3 | 4 | // -------------- 5 | // Clean (override). 6 | gulp.task('clean', task('clean', 'all')); 7 | gulp.task('clean.dist', task('clean', 'dist')); 8 | gulp.task('clean.tmp', task('clean', 'tmp')); 9 | 10 | gulp.task('check.versions', task('check.versions')); 11 | 12 | // -------------- 13 | // Postinstall. 14 | gulp.task('postinstall', done => 15 | runSequence('clean', 16 | 'npm', 17 | done)); 18 | 19 | // -------------- 20 | // Build dev. 21 | gulp.task('build.dev', done => 22 | runSequence('clean.dist', 23 | 'build.assets.dev', 24 | 'build.js.dev', 25 | 'build.index', 26 | done)); 27 | 28 | // -------------- 29 | // Watch. 30 | gulp.task('build.dev.watch', done => 31 | runSequence('build.dev', 32 | 'watch.dev', 33 | done)); 34 | 35 | // Serve. 36 | gulp.task('serve', done => 37 | runSequence('build.dev', 38 | 'server.start', 39 | 'watch.serve', 40 | done)); 41 | 42 | -------------------------------------------------------------------------------- /img/book-ed1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/getting-started-with-angular/8e6c36a992b06dbd843bb0aff869ed47cd321c61/img/book-ed1.jpg -------------------------------------------------------------------------------- /manual_typings/connect-livereload.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'connect-livereload' { 2 | function connectLivereload(options?: any): any; 3 | module connectLivereload {} 4 | export = connectLivereload; 5 | } 6 | -------------------------------------------------------------------------------- /manual_typings/gulp-load-plugins.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for gulp-load-plugins 2 | // Project: https://github.com/jackfranklin/gulp-load-plugins 3 | // Definitions by: Joe Skeen 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | // Does not support ES2015 import (import * as open from 'open'). 7 | 8 | /** Loads in any gulp plugins and attaches them to an object, freeing you up from having to manually require each gulp plugin. */ 9 | declare module 'gulp-load-plugins' { 10 | 11 | interface IOptions { 12 | /** the glob(s) to search for, default ['gulp-*', 'gulp.*'] */ 13 | pattern?: string[]; 14 | /** where to find the plugins, searched up from process.cwd(), default 'package.json' */ 15 | config?: string; 16 | /** which keys in the config to look within, default ['dependencies', 'devDependencies', 'peerDependencies'] */ 17 | scope?: string[]; 18 | /** what to remove from the name of the module when adding it to the context, default /^gulp(-|\.)/ */ 19 | replaceString?: RegExp; 20 | /** if true, transforms hyphenated plugin names to camel case, default true */ 21 | camelize?: boolean; 22 | /** whether the plugins should be lazy loaded on demand, default true */ 23 | lazy?: boolean; 24 | /** a mapping of plugins to rename, the key being the NPM name of the package, and the value being an alias you define */ 25 | rename?: IPluginNameMappings; 26 | } 27 | 28 | interface IPluginNameMappings { 29 | [npmPackageName: string]: string; 30 | } 31 | 32 | /** Loads in any gulp plugins and attaches them to an object, freeing you up from having to manually require each gulp plugin. */ 33 | function gulpLoadPlugins(options?: IOptions): T; 34 | module gulpLoadPlugins {} 35 | export = gulpLoadPlugins; 36 | } 37 | 38 | /** 39 | * Extend this interface to use Gulp plugins in your gulpfile.js 40 | */ 41 | interface IGulpPlugins { 42 | } 43 | -------------------------------------------------------------------------------- /manual_typings/jsonfile.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jsonfile' { 2 | let jsonfile: IJsonfile; 3 | interface IJsonfile { 4 | readFileSync(filename: string): string; 5 | } 6 | export = jsonfile; 7 | } -------------------------------------------------------------------------------- /manual_typings/karma.d.ts: -------------------------------------------------------------------------------- 1 | // Use this minimalistic definition file as bluebird dependency 2 | // generate a lot of errors. 3 | 4 | declare module 'karma' { 5 | var karma: IKarma; 6 | export = karma; 7 | interface IKarma { 8 | server: { 9 | start(options: any, callback: Function): void 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /manual_typings/merge-stream.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'merge-stream' { 2 | function mergeStream(...streams: NodeJS.ReadWriteStream[]): MergeStream; 3 | interface MergeStream extends NodeJS.ReadWriteStream { 4 | add(stream: NodeJS.ReadWriteStream): MergeStream; 5 | } 6 | module mergeStream {} 7 | export = mergeStream; 8 | } -------------------------------------------------------------------------------- /manual_typings/open.d.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/borisyankov/DefinitelyTyped/tree/master/open 2 | // Does not support ES2015 import (import * as open from 'open'). 3 | 4 | declare module 'open' { 5 | function open(target: string, app?: string): void; 6 | module open {} 7 | export = open; 8 | } 9 | -------------------------------------------------------------------------------- /manual_typings/run-sequence.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'run-sequence' { 2 | function runSequence(...args: any[]): void; 3 | module runSequence {} 4 | export = runSequence; 5 | } 6 | -------------------------------------------------------------------------------- /manual_typings/slash.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'slash' { 2 | function slash(path: string): string; 3 | module slash {} 4 | export = slash; 5 | } 6 | -------------------------------------------------------------------------------- /manual_typings/systemjs-builder.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'systemjs-builder' { 2 | class Builder { 3 | constructor(configObject?: any, baseUrl?: string, configPath?: string); 4 | bundle(source: string, target: string, options?: any): Promise; 5 | buildStatic(source: string, target: string, options?: any): Promise; 6 | } 7 | 8 | module Builder {} 9 | export = Builder; 10 | } 11 | -------------------------------------------------------------------------------- /manual_typings/tiny-lr.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'tiny-lr' { 2 | function tinylr(): ITinylr; 3 | module tinylr {} 4 | export = tinylr; 5 | 6 | interface ITinylr { 7 | changed(options: any): void; 8 | listen(port: number): void; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /manual_typings/yargs.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'yargs' { 2 | var yargs: IYargs; 3 | export = yargs; 4 | 5 | // Minimalistic but serves the usage in the seed. 6 | interface IYargs { 7 | argv: any; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-seed", 3 | "version": "0.0.0", 4 | "description": "Seed for Angular 2 apps", 5 | "repository": { 6 | "url": "https://github.com/mgechev/angular2-seed" 7 | }, 8 | "scripts": { 9 | "build.dev": "gulp build.dev", 10 | "build.dev.watch": "gulp build.dev.watch", 11 | "build.prod": "gulp build.prod --env prod", 12 | "build.test": "gulp build.test", 13 | "build.test.watch": "gulp build.test.watch", 14 | "docs": "npm run gulp -- build.docs && npm run gulp -- serve.docs", 15 | "gulp": "gulp", 16 | "karma": "karma", 17 | "karma.start": "karma start", 18 | "lint": "gulp tslint", 19 | "postinstall": "gulp check.versions && gulp postinstall", 20 | "reinstall": "rimraf node_modules && npm cache clean && npm install", 21 | "start": "gulp serve --env dev", 22 | "serve.dev": "gulp serve --env dev", 23 | "tasks.list": "gulp --tasks-simple", 24 | "test": "gulp test" 25 | }, 26 | "author": "Minko Gechev ", 27 | "license": "MIT", 28 | "devDependencies": { 29 | "@types/async": "^2.0.34", 30 | "@types/chalk": "^0.4.31", 31 | "@types/del": "^2.2.31", 32 | "@types/express": "^4.0.34", 33 | "@types/glob": "^5.0.30", 34 | "@types/gulp": "^3.8.32", 35 | "@types/gulp-util": "^3.0.29", 36 | "@types/mime": "0.0.29", 37 | "@types/minimatch": "^2.0.29", 38 | "@types/node": "^6.0.51", 39 | "@types/orchestrator": "0.0.30", 40 | "@types/q": "0.0.32", 41 | "@types/serve-static": "^1.7.31", 42 | "@types/systemjs": "^0.19.31", 43 | "@types/through2": "^2.0.32", 44 | "@types/vinyl": "^1.2.30", 45 | "async": "^1.4.2", 46 | "chalk": "^1.1.1", 47 | "connect-livereload": "^0.5.3", 48 | "del": "^1.1.1", 49 | "express": "~4.13.1", 50 | "extend": "^3.0.0", 51 | "gulp": "^3.9.0", 52 | "gulp-concat": "^2.5.2", 53 | "gulp-data": "^1.2.1", 54 | "gulp-filter": "^2.0.2", 55 | "gulp-inject": "^1.3.1", 56 | "gulp-load-plugins": "^0.10.0", 57 | "gulp-plumber": "~1.0.1", 58 | "gulp-shell": "~0.4.3", 59 | "gulp-sourcemaps": "~1.5.2", 60 | "gulp-template": "^3.0.0", 61 | "gulp-tslint": "^3.3.0", 62 | "gulp-tslint-stylish": "^1.0.4", 63 | "gulp-typescript": "~2.8.2", 64 | "gulp-uglify": "^1.2.0", 65 | "gulp-util": "^3.0.7", 66 | "gulp-watch": "^4.2.4", 67 | "jasmine-core": "~2.3.4", 68 | "jsonfile": "^2.2.3", 69 | "merge": "^1.2.0", 70 | "merge-stream": "^1.0.0", 71 | "open": "0.0.5", 72 | "rimraf": "^2.4.3", 73 | "run-sequence": "^1.1.0", 74 | "semver": "^5.0.3", 75 | "serve-static": "^1.9.2", 76 | "slash": "~1.0.0", 77 | "stream-series": "^0.1.1", 78 | "tiny-lr": "^0.2.1", 79 | "traceur": "^0.0.91", 80 | "ts-node": "^1.4.3", 81 | "typescript": "~2.1.1", 82 | "yargs": "^3.25.0" 83 | }, 84 | "dependencies": { 85 | "@angular/common": "2.2.0", 86 | "@angular/compiler": "2.2.0", 87 | "@angular/core": "2.2.0", 88 | "@angular/forms": "2.2.0", 89 | "@angular/http": "2.2.0", 90 | "@angular/platform-browser": "2.2.0", 91 | "@angular/platform-browser-dynamic": "2.2.0", 92 | "@angular/platform-webworker": "2.2.0", 93 | "@angular/platform-webworker-dynamic": "2.2.0", 94 | "@angular/router": "3.2.0", 95 | "@angular/upgrade": "2.2.0", 96 | "bootstrap": "^3.3.5", 97 | "es-module-loader": "^1.3.5", 98 | "es6-shim": "^0.35.0", 99 | "immutable": "^3.7.6", 100 | "markdown": "^0.5.0", 101 | "reflect-metadata": "0.1.2", 102 | "rxjs": "5.0.0-beta.12", 103 | "systemjs": "0.19.41", 104 | "zone.js": "0.6.21" 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tools/config.ts: -------------------------------------------------------------------------------- 1 | import {readFileSync} from 'fs'; 2 | import {normalize, join} from 'path'; 3 | import {argv} from 'yargs'; 4 | 5 | 6 | // -------------- 7 | // Configuration. 8 | export const ENV = argv['env'] || 'dev'; 9 | export const DEBUG = argv['debug'] || false; 10 | export const PORT = argv['port'] || 5555; 11 | export const PROJECT_ROOT = normalize(join(__dirname, '..')); 12 | export const LIVE_RELOAD_PORT = argv['reload-port'] || 4002; 13 | export const DOCS_PORT = argv['docs-port'] || 4003; 14 | export const APP_BASE = argv['base'] || '/'; 15 | 16 | export const APP_TITLE = 'Getting Started with Angular'; 17 | 18 | export const APP_SRC = 'app'; 19 | export const ASSETS_SRC = `${APP_SRC}/assets`; 20 | 21 | export const TOOLS_DIR = 'tools'; 22 | export const TMP_DIR = 'tmp'; 23 | export const TEST_DEST = 'test'; 24 | export const DOCS_DEST = 'docs'; 25 | export const APP_DEST = `dist/${ENV}`; 26 | export const ASSETS_DEST = `${APP_DEST}/assets`; 27 | export const BUNDLES_DEST = `${APP_DEST}/bundles`; 28 | export const CSS_DEST = `${APP_DEST}/css`; 29 | export const FONTS_DEST = `${APP_DEST}/fonts`; 30 | export const LIB_DEST = `${APP_DEST}/lib`; 31 | export const APP_ROOT = ENV === 'dev' ? `${APP_BASE}${APP_DEST}/` : `${APP_BASE}`; 32 | export const VERSION = appVersion(); 33 | 34 | export const VERSION_NPM = '2.14.7'; 35 | export const VERSION_NODE = '4.0.0'; 36 | 37 | // Declare NPM dependencies (Note that globs should not be injected). 38 | export const NPM_DEPENDENCIES = [ 39 | { src: 'systemjs/dist/system-polyfills.js', dest: LIB_DEST }, 40 | 41 | { src: 'es6-shim/es6-shim.min.js', inject: 'shims', dest: LIB_DEST }, 42 | { src: 'reflect-metadata/Reflect.js', inject: 'shims', dest: LIB_DEST }, 43 | { src: 'systemjs/dist/system.src.js', inject: 'shims', dest: LIB_DEST }, 44 | { src: 'zone.js/dist/zone.js', inject: 'shims', dest: LIB_DEST }, 45 | 46 | // Faster dev page load 47 | { src: 'rxjs/bundles/Rx.min.js', inject: 'libs', dest: LIB_DEST }, 48 | 49 | { src: 'bootstrap/dist/css/bootstrap.min.css', inject: true, dest: CSS_DEST } 50 | ]; 51 | 52 | // Declare local files that needs to be injected 53 | export const APP_ASSETS = [ 54 | { src: `${ASSETS_SRC}/main.css`, inject: true, dest: CSS_DEST } 55 | ]; 56 | 57 | NPM_DEPENDENCIES 58 | .filter(d => !/\*/.test(d.src)) // Skip globs 59 | .forEach(d => d.src = require.resolve(d.src)); 60 | 61 | export const DEPENDENCIES = NPM_DEPENDENCIES.concat(APP_ASSETS); 62 | 63 | 64 | // ---------------- 65 | // SystemsJS Configuration. 66 | const SYSTEM_CONFIG_DEV = { 67 | defaultJSExtensions: true, 68 | paths: { 69 | 'bootstrap': `${APP_ROOT}bootstrap`, 70 | 'markdown': '/node_modules/markdown/lib/markdown', 71 | 'immutable': '/node_modules/immutable/dist/immutable.js', 72 | '@angular/common': '/node_modules/@angular/common/bundles/common.umd.js', 73 | '@angular/compiler': '/node_modules/@angular/compiler/bundles/compiler.umd.js', 74 | '@angular/core': '/node_modules/@angular/core/bundles/core.umd.js', 75 | '@angular/forms': '/node_modules/@angular/forms/bundles/forms.umd.js', 76 | '@angular/http': '/node_modules/@angular/http/bundles/http.umd.js', 77 | '@angular/platform-browser': '/node_modules/@angular/platform-browser/bundles/platform-browser.umd.js', 78 | '@angular/platform-browser-dynamic': '/node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', 79 | '@angular/router': '/node_modules/@angular/router/bundles/router.umd.js', 80 | '@angular/platform-webworker': '/node_modules/@angular/platform-webworker/bundles/platform-webworker.umd.js', 81 | '@angular/platform-webworker-dynamic': '/node_modules/@angular/platform-webworker-dynamic/bundles/platform-webworker-dynamic.umd.js', 82 | 83 | '@angular/common/testing': '/node_modules/@angular/common/bundles/common-testing.umd.js', 84 | '@angular/compiler/testing': '/node_modules/@angular/compiler/bundles/compiler-testing.umd.js', 85 | '@angular/core/testing': '/node_modules/@angular/core/bundles/core-testing.umd.js', 86 | '@angular/http/testing': '/node_modules/@angular/http/bundles/http-testing.umd.js', 87 | '@angular/platform-browser/testing': 88 | '/node_modules/@angular/platform-browser/bundles/platform-browser-testing.umd.js', 89 | '@angular/platform-browser-dynamic/testing': 90 | '/node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', 91 | '@angular/router/testing': '/node_modules/@angular/router/bundles/router-testing.umd.js', 92 | '*': '/node_modules/*' 93 | } 94 | }; 95 | 96 | const SYSTEM_CONFIG_PROD = { 97 | defaultJSExtensions: true, 98 | bundles: { 99 | 'bundles/app': ['bootstrap'] 100 | } 101 | }; 102 | 103 | export const SYSTEM_CONFIG = ENV === 'dev' ? SYSTEM_CONFIG_DEV : SYSTEM_CONFIG_PROD; 104 | 105 | // This is important to keep clean module names as 'module name == module uri'. 106 | export const SYSTEM_CONFIG_BUILDER = { 107 | defaultJSExtensions: true, 108 | paths: { 109 | '*': `${TMP_DIR}/*`, 110 | 'rxjs/*': 'node_modules/rxjs/*' 111 | }, 112 | map: { 113 | 'rxjs': '/node_modules/rxjs', 114 | '@angular': '/node_modules/@angular' 115 | }, 116 | packages: { 117 | '@angular/core': { 118 | main: 'index.js', 119 | defaultExtension: 'js' 120 | }, 121 | '@angular/compiler': { 122 | main: 'index.js', 123 | defaultExtension: 'js' 124 | }, 125 | '@angular/common': { 126 | main: 'index.js', 127 | defaultExtension: 'js' 128 | }, 129 | '@angular/platform-browser': { 130 | main: 'index.js', 131 | defaultExtension: 'js' 132 | }, 133 | '@angular/platform-browser-dynamic': { 134 | main: 'index.js', 135 | defaultExtension: 'js' 136 | }, 137 | '@angular/router-deprecated': { 138 | main: 'index.js', 139 | defaultExtension: 'js' 140 | }, 141 | // '@angular/router': { 142 | // main: 'index.js', 143 | // defaultExtension: 'js' 144 | // }, 145 | 'rxjs': { 146 | defaultExtension: 'js' 147 | } 148 | } 149 | }; 150 | 151 | 152 | // -------------- 153 | // Private. 154 | function appVersion(): number|string { 155 | var pkg = JSON.parse(readFileSync('package.json').toString()); 156 | return pkg.version; 157 | } 158 | -------------------------------------------------------------------------------- /tools/tasks/build.assets.dev.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC, APP_DEST} from '../config'; 3 | 4 | export = function buildImagesDev(gulp, plugins) { 5 | return function () { 6 | return gulp.src([ 7 | join(APP_SRC, '**/*.gif'), 8 | join(APP_SRC, '**/*.jpg'), 9 | join(APP_SRC, '**/*.png'), 10 | join(APP_SRC, '**/*.svg'), 11 | join(APP_SRC, '**/*.html'), 12 | join(APP_SRC, '**/*.js'), 13 | join(APP_SRC, '**/*.css') 14 | ]) 15 | .pipe(gulp.dest(APP_DEST)); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /tools/tasks/build.bundles.ts: -------------------------------------------------------------------------------- 1 | import {parallel} from 'async'; 2 | import {join} from 'path'; 3 | import * as Builder from 'systemjs-builder'; 4 | import {BUNDLES_DEST, SYSTEM_CONFIG_BUILDER} from '../config'; 5 | 6 | const BUNDLE_OPTS = { 7 | minify: true, 8 | sourceMaps: true, 9 | format: 'cjs' 10 | }; 11 | 12 | export = function bundles(gulp, plugins) { 13 | return function (done) { 14 | let builder = new Builder(SYSTEM_CONFIG_BUILDER); 15 | 16 | parallel([ 17 | bundleApp 18 | ], () => done()); 19 | 20 | function bundleApp(done) { 21 | builder.bundle( 22 | 'bootstrap - angular2/*', 23 | join(BUNDLES_DEST, 'app.js'), BUNDLE_OPTS).then(done); 24 | } 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /tools/tasks/build.deps.ts: -------------------------------------------------------------------------------- 1 | import * as merge from 'merge-stream'; 2 | import {DEPENDENCIES} from '../config'; 3 | 4 | export = function buildDepsProd(gulp, plugins) { 5 | return function () { 6 | let stream = merge(); 7 | 8 | DEPENDENCIES.forEach(dep => { 9 | stream.add(addStream(dep)); 10 | }); 11 | 12 | return stream; 13 | 14 | function addStream(dep) { 15 | let stream = gulp.src(dep.src); 16 | stream.pipe(gulp.dest(dep.dest)); 17 | return stream; 18 | } 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /tools/tasks/build.docs.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC, APP_TITLE, DOCS_DEST} from '../config'; 3 | 4 | export = function buildDocs(gulp, plugins, option) { 5 | return function() { 6 | 7 | let src = [ 8 | join(APP_SRC, '**/*.ts'), 9 | '!' + join(APP_SRC, '**/*_spec.ts') 10 | ]; 11 | 12 | return gulp.src(src) 13 | .pipe(plugins.typedoc({ 14 | // TypeScript options (see typescript docs) 15 | module: 'commonjs', 16 | target: 'es5', 17 | includeDeclarations: true, 18 | // Output options (see typedoc docs) 19 | out: DOCS_DEST, 20 | json: join(DOCS_DEST , 'data/docs.json' ), 21 | name: APP_TITLE, 22 | ignoreCompilerErrors: false, 23 | experimentalDecorators: true, 24 | version: true 25 | })); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /tools/tasks/build.html_css.prod.ts: -------------------------------------------------------------------------------- 1 | import * as merge from 'merge-stream'; 2 | import {join} from 'path'; 3 | import {APP_SRC, TMP_DIR} from '../config'; 4 | 5 | // const HTML_MINIFIER_OPTS = { empty: true }; 6 | 7 | export = function buildJSDev(gulp, plugins) { 8 | return function () { 9 | 10 | return merge(minifyHtml(), minifyCss()); 11 | 12 | function minifyHtml() { 13 | return gulp.src(join(APP_SRC, '**/*.html')) 14 | // .pipe(plugins.minifyHtml(HTML_MINIFIER_OPTS)) 15 | .pipe(gulp.dest(TMP_DIR)); 16 | } 17 | 18 | function minifyCss() { 19 | return gulp.src(join(APP_SRC, '**/*.css')) 20 | .pipe(plugins.minifyCss()) 21 | .pipe(gulp.dest(TMP_DIR)); 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /tools/tasks/build.index.ts: -------------------------------------------------------------------------------- 1 | import {join, sep} from 'path'; 2 | import {APP_SRC, APP_DEST, DEPENDENCIES, SYSTEM_CONFIG, ENV} from '../config'; 3 | import {transformPath, templateLocals} from '../utils'; 4 | 5 | export = function buildIndexDev(gulp, plugins) { 6 | return function () { 7 | return gulp.src(join(APP_SRC, '**', 'index.html')) 8 | // NOTE: There might be a way to pipe in loop. 9 | .pipe(inject()) 10 | .pipe(plugins.template( 11 | require('merge')(templateLocals(), { 12 | TITLE: 'Getting started with Angular', 13 | INIT: ` 14 | ` 21 | }) 22 | )) 23 | .pipe(gulp.dest(APP_DEST)); 24 | }; 25 | 26 | 27 | function inject() { 28 | return plugins.inject(gulp.src(getInjectablesDependenciesRef(), { read: false }), { 29 | transform: transformPath(plugins, 'dev') 30 | }); 31 | } 32 | 33 | function getInjectablesDependenciesRef() { 34 | let shims = DEPENDENCIES.filter(dep => dep['inject'] && dep['inject'] === 'shims'); 35 | let libs = DEPENDENCIES.filter(dep => dep['inject'] && dep['inject'] === 'libs'); 36 | let all = DEPENDENCIES.filter(dep => dep['inject'] && dep['inject'] === true); 37 | return shims.concat(libs).concat(all).map(mapPath); 38 | } 39 | 40 | function mapPath(dep) { 41 | let prodPath = join(dep.dest, dep.src.split(sep).pop()); 42 | return ('prod' === ENV ? prodPath : dep.src ); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /tools/tasks/build.js.dev.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC, APP_DEST, PROJECT_ROOT} from '../config'; 3 | import {templateLocals, tsProjectFn, relativePath} from '../utils'; 4 | 5 | export = function buildJSDev(gulp, plugins) { 6 | let tsProject = tsProjectFn(plugins); 7 | return function () { 8 | let src = [ 9 | join(APP_SRC, '**/*.ts'), 10 | '!' + join(APP_SRC, '**/*_spec.ts') 11 | ]; 12 | 13 | let result = gulp.src(src) 14 | .pipe(plugins.plumber()) 15 | // Won't be required for non-production build after the change 16 | // .pipe(plugins.inlineNg2Template({ base: APP_SRC })) 17 | .pipe(plugins.sourcemaps.init()) 18 | .pipe(plugins.typescript(tsProject)); 19 | 20 | return result.js 21 | .pipe(plugins.sourcemaps.write()) 22 | .pipe(require('gulp-data')(file => { 23 | return require('merge')({ 24 | currentPath: relativePath(file.path) 25 | }, templateLocals()); 26 | })) 27 | .pipe(plugins.template()) 28 | .pipe(gulp.dest(APP_DEST)); 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /tools/tasks/build.js.prod.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC, TMP_DIR} from '../config'; 3 | import {templateLocals, tsProjectFn} from '../utils'; 4 | 5 | export = function buildJSDev(gulp, plugins) { 6 | return function () { 7 | let tsProject = tsProjectFn(plugins); 8 | let src = [ 9 | join(APP_SRC, '**/*.ts'), 10 | '!' + join(APP_SRC, '**/*_spec.ts') 11 | ]; 12 | 13 | let result = gulp.src(src) 14 | .pipe(plugins.plumber()) 15 | .pipe(plugins.inlineNg2Template({ base: TMP_DIR })) 16 | .pipe(plugins.typescript(tsProject)); 17 | 18 | return result.js 19 | .pipe(plugins.template(templateLocals())) 20 | .pipe(gulp.dest(TMP_DIR)); 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /tools/tasks/build.test.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC, TEST_DEST} from '../config'; 3 | import {tsProjectFn} from '../utils'; 4 | 5 | export = function buildTest(gulp, plugins) { 6 | return function () { 7 | let tsProject = tsProjectFn(plugins); 8 | let src = [ 9 | join(APP_SRC, '**/*.ts'), 10 | '!' + join(APP_SRC, 'bootstrap.ts') 11 | ]; 12 | 13 | let result = gulp.src(src) 14 | .pipe(plugins.plumber()) 15 | .pipe(plugins.inlineNg2Template({ base: APP_SRC })) 16 | .pipe(plugins.typescript(tsProject)); 17 | 18 | return result.js 19 | .pipe(gulp.dest(TEST_DEST)); 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /tools/tasks/check.versions.ts: -------------------------------------------------------------------------------- 1 | import {VERSION_NPM, VERSION_NODE} from '../config'; 2 | 3 | function reportError(message: string) { 4 | console.error(require('chalk').white.bgRed.bold(message)); 5 | process.exit(1); 6 | } 7 | 8 | module.exports = function check(gulp, plugins) { 9 | return function () { 10 | let exec = require('child_process').exec; 11 | let semver = require('semver'); 12 | 13 | exec('npm --version', 14 | function (error, stdout, stderr) { 15 | if (error !== null) { 16 | reportError('npm preinstall error: ' + error + stderr); 17 | } 18 | 19 | if (!semver.gte(stdout, VERSION_NPM)) { 20 | reportError('NPM is not in required version! Required is ' + VERSION_NPM + ' and you\'re using ' + stdout); 21 | } 22 | }); 23 | 24 | exec('node --version', 25 | function (error, stdout, stderr) { 26 | if (error !== null) { 27 | reportError('npm preinstall error: ' + error + stderr); 28 | } 29 | 30 | if (!semver.gte(stdout, VERSION_NODE)) { 31 | reportError('NODE is not in required version! Required is ' + VERSION_NODE + ' and you\'re using ' + stdout); 32 | } 33 | }); 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /tools/tasks/clean.ts: -------------------------------------------------------------------------------- 1 | import * as async from 'async'; 2 | import * as del from 'del'; 3 | import {APP_DEST, TEST_DEST, TMP_DIR} from '../config'; 4 | 5 | export = function clean(gulp, plugins, option) { 6 | return function (done) { 7 | 8 | switch(option) { 9 | case 'all' : cleanAll(done); break; 10 | case 'dist' : cleanDist(done); break; 11 | case 'test' : cleanTest(done); break; 12 | case 'tmp' : cleanTmp(done); break; 13 | default: done(); 14 | } 15 | 16 | }; 17 | }; 18 | 19 | function cleanAll(done) { 20 | async.parallel([ 21 | cleanDist, 22 | cleanTest, 23 | cleanTmp 24 | ], done); 25 | } 26 | function cleanDist(done) { 27 | del(APP_DEST, done); 28 | } 29 | function cleanTest(done) { 30 | del(TEST_DEST, done); 31 | } 32 | function cleanTmp(done) { 33 | del(TMP_DIR, done); 34 | } 35 | -------------------------------------------------------------------------------- /tools/tasks/karma.start.ts: -------------------------------------------------------------------------------- 1 | import * as karma from 'karma'; 2 | import {join} from 'path'; 3 | 4 | export = function karmaStart() { 5 | return function (done) { 6 | new (karma).Server({ 7 | configFile: join(process.cwd(), 'karma.conf.js'), 8 | singleRun: true 9 | }).start(done); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /tools/tasks/npm.ts: -------------------------------------------------------------------------------- 1 | export = function npm(gulp, plugins) { 2 | return plugins.shell.task([ 3 | 'npm prune' 4 | ]); 5 | }; 6 | -------------------------------------------------------------------------------- /tools/tasks/serve.docs.ts: -------------------------------------------------------------------------------- 1 | import {serveDocs} from '../utils'; 2 | 3 | export = function serverStart(gulp, plugins) { 4 | return function () { 5 | serveDocs(); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /tools/tasks/server.start.ts: -------------------------------------------------------------------------------- 1 | import {serveSPA} from '../utils'; 2 | 3 | export = function serverStart(gulp, plugins) { 4 | return function () { 5 | serveSPA(); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /tools/tasks/tsd.ts: -------------------------------------------------------------------------------- 1 | export = function tsd(gulp, plugins) { 2 | return plugins.shell.task([ 3 | 'tsd reinstall --clean', 4 | 'tsd link', 5 | 'tsd rebundle' 6 | ]); 7 | }; 8 | -------------------------------------------------------------------------------- /tools/tasks/tslint.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC, TOOLS_DIR} from '../config'; 3 | 4 | export = function tslint(gulp, plugins) { 5 | return function () { 6 | let src = [ 7 | join(APP_SRC, '**/*.ts'), 8 | '!' + join(APP_SRC, '**/*.d.ts'), 9 | join(TOOLS_DIR, '**/*.ts'), 10 | '!' + join(TOOLS_DIR, '**/*.d.ts') 11 | ]; 12 | 13 | return gulp.src(src) 14 | .pipe(plugins.tslint()) 15 | .pipe(plugins.tslint.report(plugins.tslintStylish, { 16 | emitError: false, 17 | sort: true, 18 | bell: true 19 | })); 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /tools/tasks/watch.dev.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC} from '../config'; 3 | 4 | export = function watchDev(gulp, plugins) { 5 | return function () { 6 | plugins.watch(join(APP_SRC, '**/*'), () => gulp.start('build.dev')); 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /tools/tasks/watch.serve.ts: -------------------------------------------------------------------------------- 1 | import * as runSequence from 'run-sequence'; 2 | import {join} from 'path'; 3 | import {APP_SRC} from '../config'; 4 | import {notifyLiveReload} from '../utils'; 5 | 6 | export = function watchServe(gulp, plugins) { 7 | return function () { 8 | plugins.watch(join(APP_SRC, '**'), e => 9 | runSequence('build.dev', () => notifyLiveReload(e)) 10 | ); 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /tools/tasks/watch.test.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {APP_SRC} from '../config'; 3 | 4 | export = function watchTest(gulp, plugins) { 5 | return function () { 6 | plugins.watch(join(APP_SRC, '**/*.ts'), () => gulp.start('build.test')); 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /tools/utils.ts: -------------------------------------------------------------------------------- 1 | export * from './utils/template_injectables'; 2 | export * from './utils/template_locals'; 3 | export * from './utils/server'; 4 | export * from './utils/tasks_tools'; 5 | import * as path from 'path'; 6 | import {APP_DEST} from './config'; 7 | 8 | export function relativePath(fileLocation) { 9 | fileLocation = path.dirname(fileLocation); 10 | var parentDir = __dirname.replace(path.join('tools'), ''); 11 | var result = path.join(path.sep, fileLocation.replace(path.join(parentDir, 'app'), APP_DEST), path.sep); 12 | return result; 13 | } 14 | 15 | export function tsProjectFn(plugins) { 16 | const tsconfig = __dirname + '/../app/tsconfig.json'; 17 | return plugins.typescript.createProject(tsconfig, { 18 | typescript: require('typescript') 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /tools/utils/server.ts: -------------------------------------------------------------------------------- 1 | import * as connectLivereload from 'connect-livereload'; 2 | import * as express from 'express'; 3 | import * as tinylrFn from 'tiny-lr'; 4 | import * as openResource from 'open'; 5 | import * as serveStatic from 'serve-static'; 6 | import {resolve} from 'path'; 7 | import {APP_BASE, APP_DEST, DOCS_DEST, LIVE_RELOAD_PORT, DOCS_PORT, PORT} from '../config'; 8 | 9 | let tinylr = tinylrFn(); 10 | 11 | 12 | export function serveSPA() { 13 | let server = express(); 14 | tinylr.listen(LIVE_RELOAD_PORT); 15 | 16 | server.use( 17 | APP_BASE, 18 | connectLivereload({ port: LIVE_RELOAD_PORT }), 19 | express.static(process.cwd()) 20 | ); 21 | 22 | server.listen(PORT, () => 23 | openResource('http://localhost:' + PORT + APP_BASE + APP_DEST) 24 | ); 25 | } 26 | 27 | export function notifyLiveReload(e) { 28 | let fileName = e.path; 29 | tinylr.changed({ 30 | body: { files: [fileName] } 31 | }); 32 | } 33 | 34 | export function serveDocs() { 35 | let server = express(); 36 | 37 | server.use( 38 | APP_BASE, 39 | serveStatic(resolve(process.cwd(), DOCS_DEST)) 40 | ); 41 | 42 | server.listen(DOCS_PORT, () => 43 | openResource('http://localhost:' + DOCS_PORT + APP_BASE) 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /tools/utils/tasks_tools.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as util from 'gulp-util'; 3 | import * as chalk from 'chalk'; 4 | import * as gulpLoadPlugins from 'gulp-load-plugins'; 5 | import * as _runSequence from 'run-sequence'; 6 | import {readdirSync, existsSync, lstatSync} from 'fs'; 7 | import {join} from 'path'; 8 | import {TOOLS_DIR} from '../config'; 9 | 10 | const TASKS_PATH = join(TOOLS_DIR, 'tasks'); 11 | 12 | // NOTE: Remove if no issues with runSequence function below. 13 | // export function loadTasks(): void { 14 | // scanDir(TASKS_PATH, (taskname) => registerTask(taskname)); 15 | // } 16 | 17 | export function task(taskname: string, option?: string) { 18 | util.log('Loading task', chalk.yellow(taskname, option || '')); 19 | return require(join('..', 'tasks', taskname))(gulp, gulpLoadPlugins(), option); 20 | } 21 | 22 | export function runSequence(...sequence: any[]) { 23 | let tasks = []; 24 | let _sequence = sequence.slice(0); 25 | sequence.pop(); 26 | 27 | scanDir(TASKS_PATH, taskname => tasks.push(taskname)); 28 | 29 | sequence.forEach(task => { 30 | if (tasks.indexOf(task) > -1) { registerTask(task); } 31 | }); 32 | 33 | return _runSequence(..._sequence); 34 | } 35 | 36 | // ---------- 37 | // Private. 38 | 39 | function registerTask(taskname: string, filename?: string, option: string = ''): void { 40 | gulp.task(taskname, task(filename || taskname, option)); 41 | } 42 | 43 | // TODO: add recursive lookup ? or enforce pattern file + folder (ie ./tools/utils & ./tools/utils.ts ) 44 | function scanDir(root: string, cb: (taskname: string) => void) { 45 | if (!existsSync(root)) return; 46 | 47 | walk(root); 48 | 49 | function walk(path) { 50 | let files = readdirSync(path); 51 | for (let i = 0; i < files.length; i += 1) { 52 | let file = files[i]; 53 | let curPath = join(path, file); 54 | // if (lstatSync(curPath).isDirectory()) { // recurse 55 | // path = file; 56 | // walk(curPath); 57 | // } 58 | if (lstatSync(curPath).isFile() && /\.ts$/.test(file)) { 59 | let taskname = file.replace(/(\.ts)/, ''); 60 | cb(taskname); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tools/utils/template_injectables.ts: -------------------------------------------------------------------------------- 1 | import * as slash from 'slash'; 2 | import {join} from 'path'; 3 | import {APP_BASE, APP_DEST, ENV} from '../config'; 4 | 5 | let injectables: string[] = []; 6 | 7 | export function injectableAssetsRef() { 8 | return injectables; 9 | } 10 | 11 | export function registerInjectableAssetsRef(paths: string[], target: string = '') { 12 | injectables = injectables.concat( 13 | paths 14 | .filter(path => !/(\.map)$/.test(path)) 15 | .map(path => join(target, slash(path).split('/').pop())) 16 | ); 17 | } 18 | 19 | export function transformPath(plugins, env) { 20 | return function (filepath) { 21 | filepath = ENV === 'prod' ? filepath.replace(`/${APP_DEST}`, '') : filepath; 22 | arguments[0] = join(APP_BASE, filepath); 23 | return slash(plugins.inject.transform.apply(plugins.inject.transform, arguments)); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /tools/utils/template_locals.ts: -------------------------------------------------------------------------------- 1 | import {APP_BASE, APP_DEST, APP_ROOT, APP_SRC, APP_TITLE, SYSTEM_CONFIG, VERSION} from '../config'; 2 | import * as fs from 'fs'; 3 | import {join, dirname, sep} from 'path'; 4 | import * as jsonfile from 'jsonfile'; 5 | 6 | const CHAPTERS_MAP = { 7 | ch3: 'Chapter 3', 8 | ch4: 'Chapter 4', 9 | ch5: 'Chapter 5', 10 | ch6: 'Chapter 6', 11 | ch7: 'Chapter 7', 12 | ch8: 'Chapter 8' 13 | }; 14 | 15 | function listMetadataStrategy(data) { 16 | return `
  • 17 | ${data.chapter}, ${data.meta.title} 18 | ${!data.meta.presented ? '(Not presented in the book\'s content)' : ''} 19 |
  • `; 20 | } 21 | 22 | function readMetadata(current, appRoot) { 23 | var result = []; 24 | fs.readdirSync(current).forEach(function (file) { 25 | if (file === 'meta.json') { 26 | file = join('.', sep, current, file); 27 | let currentChapterAbr = file.match(/(ch\d+)/)[0]; 28 | let chapter = CHAPTERS_MAP[currentChapterAbr]; 29 | result.push({ 30 | chapter, 31 | file: current, 32 | meta: jsonfile.readFileSync(file) 33 | }); 34 | } else if (fs.lstatSync(join(current, file)).isDirectory()) { 35 | result = result.concat(readMetadata(join(current, file), appRoot)); 36 | } 37 | }); 38 | return result; 39 | } 40 | 41 | function getMetadata(appRoot) { 42 | let metadata = readMetadata(appRoot, appRoot); 43 | metadata = metadata.sort((a, b) => { 44 | let chapterA = parseInt(a.chapter.match(/\d+/)[0]); 45 | let chapterB = parseInt(b.chapter.match(/\d+/)[0]); 46 | if (chapterA < chapterB) { 47 | return -1; 48 | } else if (chapterA > chapterB) { 49 | return 1; 50 | } 51 | if (a.meta.id < b.meta.id) { 52 | return -1; 53 | } else { 54 | return 1; 55 | } 56 | }); 57 | let items = metadata.map(listMetadataStrategy); 58 | return '
      ' + items.join('\n') + '
    '; 59 | } 60 | 61 | function transformPath(plugins, env) { 62 | return function (filepath) { 63 | var filename = filepath.replace(sep + APP_DEST, ''); 64 | arguments[0] = join(APP_BASE, filename); 65 | return plugins.inject.transform.apply(plugins.inject.transform, arguments); 66 | }; 67 | } 68 | 69 | function relativePath(fileLocation) { 70 | fileLocation = dirname(fileLocation); 71 | var parentDir = __dirname.replace(join('tools'), ''); 72 | var result = join(sep, fileLocation.replace(join(parentDir, 'app'), ''), sep); 73 | return result; 74 | } 75 | 76 | // TODO: Add an interface to register more template locals. 77 | export function templateLocals() { 78 | return { 79 | APP_BASE, 80 | APP_DEST, 81 | APP_ROOT, 82 | APP_TITLE, 83 | SYSTEM_CONFIG, 84 | VERSION, 85 | EXAMPLES_LIST: getMetadata(join(APP_SRC)) 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "lib": ["dom", "es6", "es2015"], 12 | "sourceMap": true, 13 | "typeRoots": [ 14 | "./node_modules/@types" 15 | ], 16 | "types": [ 17 | "node" 18 | ] 19 | }, 20 | "exclude": [ 21 | "dist", 22 | "node_modules" 23 | ], 24 | "compileOnSave": false 25 | } 26 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": false, 5 | "eofline": true, 6 | "indent": "spaces", 7 | "max-line-length": [true, 140], 8 | "member-ordering": [true, 9 | "public-before-private", 10 | "static-before-instance", 11 | "variables-before-functions" 12 | ], 13 | "no-arg": true, 14 | "no-construct": true, 15 | "no-duplicate-key": true, 16 | "no-duplicate-variable": true, 17 | "no-empty": true, 18 | "no-eval": true, 19 | "no-trailing-comma": true, 20 | "no-trailing-whitespace": true, 21 | "no-unused-expression": true, 22 | "no-unused-variable": true, 23 | "no-unreachable": true, 24 | "no-use-before-declare": true, 25 | "one-line": [true, 26 | "check-open-brace", 27 | "check-catch", 28 | "check-else", 29 | "check-whitespace" 30 | ], 31 | "quotemark": [true, "single"], 32 | "semicolon": true, 33 | "triple-equals": true, 34 | "variable-name": false 35 | } 36 | } 37 | --------------------------------------------------------------------------------