├── .editorconfig
├── .gitignore
├── README.md
├── angular-cli.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.json
├── firebase.json
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.routing.ts
│ ├── component-inheritance
│ │ ├── index.ts
│ │ ├── my-pagination.component.ts
│ │ ├── page-component-inheritance.component.html
│ │ ├── page-component-inheritance.component.ts
│ │ ├── pagination.component.css
│ │ └── simple-pagination.component.ts
│ ├── conditional-val-model-driven-frm
│ │ ├── customer.interface.ts
│ │ ├── index.ts
│ │ ├── page-conditional-val-model-driven-frm.component.html
│ │ └── page-conditional-val-model-driven-frm.component.ts
│ ├── custom-validator
│ │ ├── equal-validator.directive.ts
│ │ ├── index.ts
│ │ ├── page-custom-validator.component.html
│ │ ├── page-custom-validator.component.ts
│ │ └── user.interface.ts
│ ├── dep-inject
│ │ ├── dep-a.service.ts
│ │ ├── dep-b.service.ts
│ │ ├── dep-ctn.component.ts
│ │ ├── dep-inject.component.ts
│ │ ├── index.ts
│ │ ├── page-dep-inject.component.html
│ │ └── page-dep-inject.component.ts
│ ├── diff-form-ctrls
│ │ ├── index.ts
│ │ ├── page-diff-form-ctrls.component.html
│ │ ├── page-diff-form-ctrls.component.ts
│ │ ├── theme.interface.ts
│ │ └── user.interface.ts
│ ├── file-upload
│ │ ├── file-upload.fake.service.ts
│ │ ├── file-upload.service.ts
│ │ ├── index.ts
│ │ ├── page-file-upload.component.css
│ │ ├── page-file-upload.component.html
│ │ └── page-file-upload.component.ts
│ ├── form-array-dyn-val
│ │ ├── customer.interface.ts
│ │ ├── index.ts
│ │ ├── page-form-array-dyn-val.component.html
│ │ └── page-form-array-dyn-val.component.ts
│ ├── global-pipe
│ │ ├── capitalize.pipe.ts
│ │ ├── index.ts
│ │ └── page-global-pipe.component.ts
│ ├── hammerjs
│ │ ├── hammer-config.ts
│ │ ├── index.ts
│ │ ├── page-hammerjs.component.css
│ │ ├── page-hammerjs.component.html
│ │ └── page-hammerjs.component.ts
│ ├── home
│ │ ├── article.interface.ts
│ │ ├── home.service.ts
│ │ ├── index.ts
│ │ ├── page-home.component.html
│ │ └── page-home.component.ts
│ ├── index.ts
│ ├── lang
│ │ ├── index.ts
│ │ ├── lang-en.ts
│ │ ├── lang-es.ts
│ │ └── lang-zh.ts
│ ├── model-driven-frm
│ │ ├── index.ts
│ │ ├── page-model-driven-frm.component.html
│ │ ├── page-model-driven-frm.component.ts
│ │ └── user.interface.ts
│ ├── nested-model-driven-frm
│ │ ├── customer.interface.ts
│ │ ├── index.ts
│ │ ├── page-nested-model-driven-frm.component.html
│ │ └── page-nested-model-driven-frm.component.ts
│ ├── same-height
│ │ ├── card-same-height.component.html
│ │ ├── card-same-height.component.ts
│ │ ├── index.ts
│ │ ├── match-height.directive.ts
│ │ ├── page-same-height.component.css
│ │ ├── page-same-height.component.html
│ │ ├── page-same-height.component.ts
│ │ └── third.component.ts
│ ├── simple-language-translation-part-1
│ │ ├── index.ts
│ │ ├── page-simple-language-translation-part-1.component.html
│ │ ├── page-simple-language-translation-part-1.component.ts
│ │ ├── translate-1.pipe.ts
│ │ ├── translate-1.service.ts
│ │ └── translations-1.ts
│ ├── simple-language-translation-part-2
│ │ ├── index.ts
│ │ ├── page-simple-language-translation-part-2.component.html
│ │ ├── page-simple-language-translation-part-2.component.ts
│ │ ├── translate-2.pipe.ts
│ │ ├── translate-2.service.ts
│ │ └── translations-2.ts
│ ├── template-driven-frm
│ │ ├── index.ts
│ │ ├── page-template-driven-frm.component.html
│ │ ├── page-template-driven-frm.component.ts
│ │ └── user.interface.ts
│ ├── three-ways
│ │ ├── blogger.component.ts
│ │ ├── index.ts
│ │ ├── page-three-ways.component.html
│ │ ├── page-three-ways.component.ts
│ │ ├── post.interface.ts
│ │ ├── posts-1.component.ts
│ │ ├── posts-2.component.ts
│ │ └── posts-3.component.ts
│ └── transclusion
│ │ ├── card.component.html
│ │ ├── card.component.ts
│ │ ├── index.ts
│ │ ├── page-transclusion.component.html
│ │ └── page-transclusion.component.ts
├── assets
│ ├── .gitkeep
│ ├── angular-2.jpg
│ ├── articles.json
│ ├── chart-a.json
│ ├── chart-x.json
│ └── mock-posts.json
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
├── tsconfig.json
└── typings.d.ts
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # IDEs and editors
12 | /.idea
13 | /.vscode
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 |
20 | # misc
21 | /.sass-cache
22 | /connect.lock
23 | /coverage/*
24 | /libpeerconnection.log
25 | npm-debug.log
26 | testem.log
27 | /typings
28 |
29 | # e2e
30 | /e2e/*.js
31 | /e2e/*.map
32 |
33 | #System Files
34 | .DS_Store
35 | Thumbs.db
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NgMusing
2 |
3 | This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.22-1.
4 |
5 | ## Development server
6 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
7 |
8 | ## Code scaffolding
9 |
10 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class`.
11 |
12 | ## Build
13 |
14 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
15 |
16 | ## Running unit tests
17 |
18 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
19 |
20 | ## Running end-to-end tests
21 |
22 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
23 | Before running the tests make sure you are serving the app via `ng serve`.
24 |
25 | ## Deploying to Github Pages
26 |
27 | Run `ng github-pages:deploy` to deploy to Github Pages.
28 |
29 | ## Further help
30 |
31 | To get more help on the `angular-cli` use `ng --help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
32 |
--------------------------------------------------------------------------------
/angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "project": {
3 | "version": "1.0.0-beta.22-1",
4 | "name": "ng-musing"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "test": "test.ts",
17 | "tsconfig": "tsconfig.json",
18 | "prefix": "app",
19 | "mobile": false,
20 | "styles": [
21 | "styles.css"
22 | ],
23 | "scripts": [],
24 | "environments": {
25 | "source": "environments/environment.ts",
26 | "dev": "environments/environment.ts",
27 | "prod": "environments/environment.prod.ts"
28 | }
29 | }
30 | ],
31 | "addons": [],
32 | "packages": [],
33 | "e2e": {
34 | "protractor": {
35 | "config": "./protractor.conf.js"
36 | }
37 | },
38 | "test": {
39 | "karma": {
40 | "config": "./karma.conf.js"
41 | }
42 | },
43 | "defaults": {
44 | "styleExt": "css",
45 | "prefixInterfaces": false,
46 | "inline": {
47 | "style": false,
48 | "template": false
49 | },
50 | "spec": {
51 | "class": false,
52 | "component": true,
53 | "directive": true,
54 | "module": false,
55 | "pipe": true,
56 | "service": true
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { NgMusingPage } from './app.po';
2 |
3 | describe('ng-musing App', function() {
4 | let page: NgMusingPage;
5 |
6 | beforeEach(() => {
7 | page = new NgMusingPage();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('app works!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class NgMusingPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "outDir": "../dist/out-tsc-e2e",
10 | "sourceMap": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "../node_modules/@types"
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "dist",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', 'angular-cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-remap-istanbul'),
12 | require('angular-cli/plugins/karma')
13 | ],
14 | files: [
15 | { pattern: './src/test.ts', watched: false }
16 | ],
17 | preprocessors: {
18 | './src/test.ts': ['angular-cli']
19 | },
20 | mime: {
21 | 'text/x-typescript': ['ts','tsx']
22 | },
23 | remapIstanbulReporter: {
24 | reports: {
25 | html: 'coverage',
26 | lcovonly: './coverage/coverage.lcov'
27 | }
28 | },
29 | angularCli: {
30 | config: './angular-cli.json',
31 | environment: 'dev'
32 | },
33 | reporters: config.angularCli && config.angularCli.codeCoverage
34 | ? ['progress', 'karma-remap-istanbul']
35 | : ['progress'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-musing",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "angular-cli": {},
6 | "scripts": {
7 | "start": "ng serve",
8 | "build": "ng build -prod",
9 | "deploy": "npm run build && firebase deploy",
10 | "lint": "tslint \"src/**/*.ts\"",
11 | "test": "ng test",
12 | "pree2e": "webdriver-manager update",
13 | "e2e": "protractor"
14 | },
15 | "private": true,
16 | "dependencies": {
17 | "@angular/common": "2.3.1",
18 | "@angular/compiler": "2.3.1",
19 | "@angular/core": "2.3.1",
20 | "@angular/forms": "2.3.1",
21 | "@angular/http": "2.3.1",
22 | "@angular/platform-browser": "2.3.1",
23 | "@angular/platform-browser-dynamic": "2.3.1",
24 | "@angular/router": "3.3.1",
25 | "core-js": "^2.4.1",
26 | "rxjs": "5.0.1",
27 | "ts-helpers": "^1.1.1",
28 | "zone.js": "^0.7.2"
29 | },
30 | "devDependencies": {
31 | "@angular/compiler-cli": "2.3.1",
32 | "@types/jasmine": "2.5.38",
33 | "@types/node": "^6.0.42",
34 | "angular-cli": "1.0.0-beta.21",
35 | "codelyzer": "~2.0.0-beta.1",
36 | "jasmine-core": "2.5.2",
37 | "jasmine-spec-reporter": "2.5.0",
38 | "karma": "1.2.0",
39 | "karma-chrome-launcher": "^2.0.0",
40 | "karma-cli": "^1.0.1",
41 | "karma-jasmine": "^1.0.2",
42 | "karma-remap-istanbul": "^0.2.1",
43 | "protractor": "4.0.9",
44 | "ts-node": "1.2.1",
45 | "tslint": "^4.0.2",
46 | "typescript": "~2.0.3",
47 | "webdriver-manager": "10.2.5"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js
3 |
4 | /*global jasmine */
5 | var SpecReporter = require('jasmine-spec-reporter');
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | './e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | useAllAngular2AppRoots: true,
24 | beforeLaunch: function() {
25 | require('ts-node').register({
26 | project: 'e2e'
27 | });
28 | },
29 | onPrepare: function() {
30 | jasmine.getEnv().addReporter(new SpecReporter());
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jecfish/ng-musing/e92d8f94b1856940ea594883e50f220b6f163003/src/app/app.component.css
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import { TestBed, async } from '@angular/core/testing';
4 | import { AppComponent } from './app.component';
5 |
6 | describe('AppComponent', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | declarations: [
10 | AppComponent
11 | ],
12 | });
13 | TestBed.compileComponents();
14 | });
15 |
16 | it('should create the app', async(() => {
17 | let fixture = TestBed.createComponent(AppComponent);
18 | let app = fixture.debugElement.componentInstance;
19 | expect(app).toBeTruthy();
20 | }));
21 |
22 | it(`should have as title 'app works!'`, async(() => {
23 | let fixture = TestBed.createComponent(AppComponent);
24 | let app = fixture.debugElement.componentInstance;
25 | expect(app.title).toEqual('app works!');
26 | }));
27 |
28 | it('should render title in a h1 tag', async(() => {
29 | let fixture = TestBed.createComponent(AppComponent);
30 | fixture.detectChanges();
31 | let compiled = fixture.debugElement.nativeElement;
32 | expect(compiled.querySelector('h1').textContent).toContain('app works!');
33 | }));
34 | });
35 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.css']
7 | })
8 | export class AppComponent {
9 | title = 'Angular Musing';
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 | import { HttpModule } from '@angular/http';
5 | import { RouterModule, Routes } from '@angular/router';
6 | import { HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
7 | import 'rxjs/Rx';
8 |
9 | // routing
10 | import { appRoutes } from './app.routing';
11 |
12 | // components
13 | import { AppComponent } from './app.component';
14 | import { PageGlobalPipeComponent, CapitalizePipe } from './global-pipe';
15 | import { PageDiffFormCtrlsComponent } from './diff-form-ctrls';
16 | import { PageTemplateDrivenFrmComponent } from './template-driven-frm';
17 | import { PageModelDrivenFrmComponent } from './model-driven-frm';
18 | import { PageNestedModelDrivenFrmComponent } from './nested-model-driven-frm';
19 | import { PageConditionalValModelDrivenFrmComponent } from './conditional-val-model-driven-frm';
20 | import { PageHammerjsComponent, MyHammerConfig } from './hammerjs';
21 | import { PageCustomValidatorComponent, EqualValidatorDirective } from './custom-validator';
22 | import { TRANSLATION1_PROVIDERS, Translate1Pipe,
23 | Translate1Service, PageSimpleLanguageTranslationPart1Component } from './simple-language-translation-part-1';
24 | import { TRANSLATION2_PROVIDERS, Translate2Pipe,
25 | Translate2Service, PageSimpleLanguageTranslationPart2Component } from './simple-language-translation-part-2';
26 | import { CardComponent, PageTransclusionComponent } from './transclusion';
27 | import { PageComponentInheritanceComponent, MyPaginationComponent,
28 | SimplePaginationComponent } from './component-inheritance';
29 | import { PageHomeComponent, HomeService } from './home';
30 | import { DepAService, DepBService,
31 | DepCtnComponent, DepInjectComponent, PageDepInjectComponent } from './dep-inject';
32 | import { PageFormArrayDynValComponent } from './form-array-dyn-val';
33 | import { PageSameHeightComponent, CardSameHeightComponent,
34 | MatchHeightDirective, ThirdComponent } from './same-height';
35 | import { PageThreeWaysComponent, BloggerComponent,
36 | Posts1Component, Posts2Component, Posts3Component } from './three-ways';
37 | import { PageFileUploadComponent, FileUploadFakeService,
38 | FileUploadService } from './file-upload';
39 |
40 | @NgModule({
41 | declarations: [
42 | AppComponent,
43 | // implement pipe
44 | PageGlobalPipeComponent, CapitalizePipe,
45 | // dealing with different form controls
46 | PageDiffFormCtrlsComponent,
47 | // template driven form
48 | PageTemplateDrivenFrmComponent,
49 | // model driven form
50 | PageModelDrivenFrmComponent,
51 | // nested model driven form
52 | PageNestedModelDrivenFrmComponent,
53 | // conditional validation model driven form
54 | PageConditionalValModelDrivenFrmComponent,
55 | // using hammerjs
56 | PageHammerjsComponent,
57 | // implement a Custom Validator Directive (Confirm Password)
58 | PageCustomValidatorComponent, EqualValidatorDirective,
59 | // Simple Language Translation (Part 1)
60 | PageSimpleLanguageTranslationPart1Component, Translate1Pipe,
61 | // Simple Language Translation (Part 2)
62 | PageSimpleLanguageTranslationPart2Component, Translate2Pipe,
63 | // Transclusion using ng-content
64 | PageTransclusionComponent, CardComponent,
65 | // Component Inheritance
66 | PageComponentInheritanceComponent,
67 | SimplePaginationComponent, MyPaginationComponent,
68 | // home page
69 | PageHomeComponent,
70 | // dependency inject
71 | PageDepInjectComponent, DepCtnComponent, DepInjectComponent,
72 | // Dynamic Form Array Validation in Model Driven Form
73 | PageFormArrayDynValComponent,
74 | // Same height component
75 | PageSameHeightComponent, CardSameHeightComponent,
76 | MatchHeightDirective, ThirdComponent,
77 | // Three ways
78 | PageThreeWaysComponent, BloggerComponent, Posts1Component,
79 | Posts2Component, Posts3Component,
80 | // Template Driven File Upload
81 | PageFileUploadComponent,
82 | ],
83 | imports: [
84 | BrowserModule,
85 | FormsModule,
86 | ReactiveFormsModule,
87 | HttpModule,
88 | RouterModule.forRoot(appRoutes),
89 | ],
90 | providers: [
91 | { provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig },
92 | // Simple Language Translation (Part 1)
93 | TRANSLATION1_PROVIDERS, Translate1Service,
94 | // Simple Language Translation (Part 2)
95 | TRANSLATION2_PROVIDERS, Translate2Service,
96 | // home page
97 | HomeService,
98 | // Template Driven File Upload
99 | // use real for your own project
100 | // use fake for demo
101 | // { provide: FileUploadService, useClass: FileUploadService },
102 | { provide: FileUploadService, useClass: FileUploadFakeService },
103 | // dependency injection
104 | DepAService,
105 | // { provide: DepAService, useValue: { getGreeting(name) { return 'yahoo ' + name; } } },
106 | // { provide: DepAService, useClass: class { getGreeting(name) { return 'class ' + name; } } },
107 | // DepBService
108 | ],
109 | schemas: [ NO_ERRORS_SCHEMA ],
110 | bootstrap: [AppComponent]
111 | })
112 | export class AppModule { }
113 |
--------------------------------------------------------------------------------
/src/app/app.routing.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { PageGlobalPipeComponent } from './global-pipe';
3 | import { PageDiffFormCtrlsComponent } from './diff-form-ctrls';
4 | import { PageTemplateDrivenFrmComponent } from './template-driven-frm';
5 | import { PageModelDrivenFrmComponent } from './model-driven-frm';
6 | import { PageNestedModelDrivenFrmComponent } from './nested-model-driven-frm';
7 | import { PageConditionalValModelDrivenFrmComponent } from './conditional-val-model-driven-frm';
8 | import { PageHammerjsComponent } from './hammerjs';
9 | import { PageCustomValidatorComponent } from './custom-validator';
10 | import { PageSimpleLanguageTranslationPart1Component } from './simple-language-translation-part-1';
11 | import { PageSimpleLanguageTranslationPart2Component } from './simple-language-translation-part-2';
12 | import { PageTransclusionComponent } from './transclusion';
13 | import { PageComponentInheritanceComponent } from './component-inheritance';
14 | import { PageHomeComponent } from './home';
15 | import { PageDepInjectComponent } from './dep-inject';
16 | import { PageFormArrayDynValComponent } from './form-array-dyn-val';
17 | import { PageSameHeightComponent } from './same-height';
18 | import { PageThreeWaysComponent } from './three-ways';
19 | import { PageFileUploadComponent } from './file-upload';
20 |
21 | export const appRoutes: Routes = [
22 | { path: 'global-pipe', component: PageGlobalPipeComponent },
23 | { path: 'diff-form-ctrls', component: PageDiffFormCtrlsComponent },
24 | { path: 'template-driven-frm', component: PageTemplateDrivenFrmComponent },
25 | { path: 'model-driven-frm', component: PageModelDrivenFrmComponent },
26 | { path: 'nested-model-driven-frm', component: PageNestedModelDrivenFrmComponent },
27 | { path: 'conditional-val-model-driven-frm', component: PageConditionalValModelDrivenFrmComponent },
28 | { path: 'hammerjs', component: PageHammerjsComponent },
29 | { path: 'custom-validator', component: PageCustomValidatorComponent },
30 | { path: 'simple-language-translation-part-1', component: PageSimpleLanguageTranslationPart1Component },
31 | { path: 'simple-language-translation-part-2', component: PageSimpleLanguageTranslationPart2Component },
32 | { path: 'transclusion', component: PageTransclusionComponent },
33 | { path: 'component-inheritance', component: PageComponentInheritanceComponent },
34 | { path: 'dependency-injection', component: PageDepInjectComponent },
35 | { path: 'dynamic-form-array-val', component: PageFormArrayDynValComponent },
36 | { path: 'same-height', component: PageSameHeightComponent },
37 | { path: 'three-ways', component: PageThreeWaysComponent },
38 | { path: 'file-upload', component: PageFileUploadComponent },
39 | { path: '', component: PageHomeComponent },
40 | ];
41 |
--------------------------------------------------------------------------------
/src/app/component-inheritance/index.ts:
--------------------------------------------------------------------------------
1 | export * from './my-pagination.component';
2 | export * from './simple-pagination.component';
3 | export * from './page-component-inheritance.component';
--------------------------------------------------------------------------------
/src/app/component-inheritance/my-pagination.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { SimplePaginationComponent } from './simple-pagination.component';
3 |
4 | @Component({
5 | selector: 'my-pagination',
6 | template: `
7 | {{ title }}
8 |
10 | {{ previousText }}
11 |
12 | {{ page }} / {{ pageCount }}
13 |
15 | {{ nextText }}
16 |
17 | `,
18 | styleUrls: ['./pagination.component.css']
19 | })
20 | export class MyPaginationComponent extends SimplePaginationComponent {
21 | @Input()
22 | title: string;
23 |
24 | @Input()
25 | previousText = '<<';
26 |
27 | @Input()
28 | nextText = '>>';
29 | }
--------------------------------------------------------------------------------
/src/app/component-inheritance/page-component-inheritance.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Parent Component:
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Child Component:
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/component-inheritance/page-component-inheritance.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'page-component-inheritance',
5 | templateUrl: './page-component-inheritance.component.html'
6 | })
7 | export class PageComponentInheritanceComponent {
8 | messages: string[] = [];
9 |
10 | gatherCount(message) {
11 | this.messages.push(message);
12 | }
13 |
14 | gatherPage(pageNo) {
15 | this.messages.push(`I am at page ${pageNo} now.`);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/app/component-inheritance/pagination.component.css:
--------------------------------------------------------------------------------
1 | button {
2 | padding: 10px;
3 | margin: 10px;
4 | }
5 |
6 | p {
7 | margin: 10px;
8 | }
9 |
10 | a {
11 | margin: 10px;
12 | }
13 |
14 | .disabled {
15 | pointer-events: none;
16 | cursor: default;
17 | opacity: 0.6;
18 | color: grey;
19 | }
--------------------------------------------------------------------------------
/src/app/component-inheritance/simple-pagination.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'simple-pagination',
5 | template: `
6 | {{ previousText }}
7 | {{ nextText }}
8 |
9 | page {{ page }} of {{ pageCount }}
10 | `,
11 | styleUrls: ['./pagination.component.css']
12 | })
13 | export class SimplePaginationComponent {
14 |
15 | title: string;
16 |
17 | @Input()
18 | previousText = 'Previous';
19 |
20 | @Input()
21 | nextText = 'Next';
22 |
23 | @Input()
24 | pageCount: number;
25 |
26 | @Input()
27 | page: number;
28 |
29 | @Output()
30 | pageChanged = new EventEmitter();
31 |
32 | nextPage() {
33 | this.page++;
34 | this.pageChanged.emit(this.page);
35 | }
36 |
37 | previousPage() {
38 | this.page--;
39 | this.pageChanged.emit(this.page);
40 | }
41 |
42 | hasPrevious(): boolean { return +this.page > 1; }
43 |
44 | hasNext(): boolean { return +this.page < +this.pageCount; }
45 |
46 | }
--------------------------------------------------------------------------------
/src/app/conditional-val-model-driven-frm/customer.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Customer {
2 | name: string;
3 | paymentMethod: {
4 | type: string; // must be either 'bank' or 'card'
5 | card: {
6 | cardNo: string; // must be visa, master, amex
7 | cardHolder: string;
8 | expiry: string; // must be format MM/YY
9 | },
10 | bank: {
11 | accountNo: string;
12 | accountHolder: string;
13 | routingNo: string;
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/app/conditional-val-model-driven-frm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-conditional-val-model-driven-frm.component';
--------------------------------------------------------------------------------
/src/app/conditional-val-model-driven-frm/page-conditional-val-model-driven-frm.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Add customer
6 |
7 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/app/conditional-val-model-driven-frm/page-conditional-val-model-driven-frm.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
3 | import { Customer } from './customer.interface';
4 |
5 | @Component({
6 | selector: 'page-conditional-val-model-driven-frm',
7 | templateUrl: './page-conditional-val-model-driven-frm.component.html'
8 | })
9 | export class PageConditionalValModelDrivenFrmComponent implements OnInit {
10 | public myForm: FormGroup; // our model driven form
11 |
12 | // standing data
13 | public PAYMENT_METHOD_TYPE = {
14 | BANK: 'bank',
15 | CARD: 'card'
16 | };
17 |
18 | constructor(private _fb: FormBuilder) { } // form builder simplify form initialization
19 |
20 | ngOnInit() {
21 | // we will initialize our form model here
22 | this.myForm = this._fb.group({
23 | name: ['Jane Doe'],
24 | paymentMethod: this.initPaymentMethodFormGroup()
25 | });
26 |
27 | this.subscribePaymentTypeChanges();
28 |
29 | this.setPaymentMethodType(this.PAYMENT_METHOD_TYPE.BANK);
30 | }
31 |
32 | initPaymentMethodFormGroup() {
33 | // initialize payment method form group
34 | const group = this._fb.group({
35 | type: [''],
36 | card: this._fb.group(this.initPaymentMethodCardModel()),
37 | bank: this._fb.group(this.initPaymentMethodBankModel()),
38 | });
39 |
40 | return group;
41 | }
42 |
43 | initPaymentMethodCardModel(): any {
44 | // you get valid testing credit card from http://www.getcreditcardnumbers.com/
45 | const cardNoRegex = `^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$`;
46 | const expiryRegex = `^(0[1-9]|1[0-2])\/?([0-9]{4}|[0-9]{2})$`;
47 |
48 | // initialize card model
49 | const model = {
50 | cardNo: ['', [Validators.required, Validators.pattern(cardNoRegex)]],
51 | cardHolder: ['', Validators.required],
52 | expiry: ['', [Validators.required, Validators.pattern(expiryRegex)]]
53 | };
54 |
55 | return model;
56 | }
57 |
58 | initPaymentMethodBankModel(): any {
59 | // initialize bank model
60 | const model = {
61 | accountNo: ['', Validators.required],
62 | accountHolder: ['', Validators.required],
63 | routingNo: ['', Validators.required]
64 | };
65 |
66 | return model;
67 | }
68 |
69 | setPaymentMethodType(type: string) {
70 | // update payment method type value
71 | const ctrl = this.myForm.get('paymentMethod.type');
72 | ctrl.setValue(type);
73 | }
74 |
75 | save(model: Customer, isValid: boolean) {
76 | // call API to save
77 | // ...
78 | console.log(model, isValid);
79 | }
80 |
81 | subscribePaymentTypeChanges() {
82 | // controls
83 | const bankCtrl = this.myForm.get('paymentMethod.bank') as FormGroup;
84 | const cardCtrl = this.myForm.get('paymentMethod.card') as FormGroup;
85 |
86 | // initialize value changes stream
87 | const changes$ = this.myForm.get('paymentMethod.type').valueChanges;
88 |
89 | // subscribe to the stream
90 | changes$.subscribe(paymentMethodType => {
91 |
92 | if (paymentMethodType === this.PAYMENT_METHOD_TYPE.BANK) {
93 | this.setPaymentMethodValidity(bankCtrl, this.initPaymentMethodBankModel());
94 | this.clearPaymentMethodValidity(cardCtrl);
95 | }
96 |
97 | if (paymentMethodType === this.PAYMENT_METHOD_TYPE.CARD) {
98 | this.setPaymentMethodValidity(cardCtrl, this.initPaymentMethodCardModel());
99 | this.clearPaymentMethodValidity(bankCtrl);
100 | }
101 |
102 | });
103 | }
104 |
105 | private clearPaymentMethodValidity(control: FormGroup) {
106 | Object.keys(control.controls).forEach(key => {
107 | const ctrl = control.get(key);
108 | ctrl.clearValidators();
109 | ctrl.updateValueAndValidity();
110 | });
111 | }
112 |
113 | private setPaymentMethodValidity(control: FormGroup, model) {
114 | Object.keys(control.controls).forEach(key => {
115 | const ctrl = control.get(key);
116 | ctrl.setValidators(model[key][1]);
117 | ctrl.updateValueAndValidity();
118 | });
119 | }
120 | }
--------------------------------------------------------------------------------
/src/app/custom-validator/equal-validator.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, forwardRef, Attribute } from '@angular/core';
2 | import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';
3 |
4 | @Directive({
5 | selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',
6 | providers: [
7 | { provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidatorDirective), multi: true }
8 | ]
9 | })
10 | export class EqualValidatorDirective implements Validator {
11 | constructor( @Attribute('validateEqual') public validateEqual: string,
12 | @Attribute('reverse') public reverse: string) {
13 |
14 | }
15 |
16 | private get isReverse() {
17 | if (!this.reverse) return false;
18 | return this.reverse === 'true' ? true : false;
19 | }
20 |
21 | validate(c: AbstractControl): { [key: string]: any } {
22 | // self value
23 | let v = c.value;
24 |
25 | // control vlaue
26 | let e = c.root.get(this.validateEqual);
27 |
28 | // value not equal
29 | if (e && v !== e.value && !this.isReverse) {
30 | return {
31 | validateEqual: false
32 | }
33 | }
34 |
35 | // value equal and reverse
36 | if (e && v === e.value && this.isReverse) {
37 | delete e.errors['validateEqual'];
38 | if (!Object.keys(e.errors).length) e.setErrors(null);
39 | }
40 |
41 | // value not equal and reverse
42 | if (e && v !== e.value && this.isReverse) {
43 | e.setErrors({
44 | validateEqual: false
45 | })
46 | }
47 |
48 | return null;
49 | }
50 | }
--------------------------------------------------------------------------------
/src/app/custom-validator/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-custom-validator.component';
2 | export * from './equal-validator.directive';
--------------------------------------------------------------------------------
/src/app/custom-validator/page-custom-validator.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/custom-validator/page-custom-validator.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { User } from './user.interface';
3 |
4 | @Component({
5 | selector: 'page-custom-validator',
6 | templateUrl: './page-custom-validator.component.html'
7 | })
8 | export class PageCustomValidatorComponent {
9 | public user: User;
10 |
11 | ngOnInit() {
12 | this.user = {
13 | username: '',
14 | email: '',
15 | password: '',
16 | confirmPassword: ''
17 | }
18 | }
19 |
20 | save(model: User, isValid: boolean) {
21 | // call API to save customer
22 | console.log(model, isValid);
23 | }
24 | }
--------------------------------------------------------------------------------
/src/app/custom-validator/user.interface.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | username: string;
3 | email: string;
4 | password: string;
5 | confirmPassword: string;
6 | }
--------------------------------------------------------------------------------
/src/app/dep-inject/dep-a.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | // import { Http } from '@angular/http';
3 |
4 | @Injectable()
5 | export class DepAService {
6 |
7 | constructor() { }
8 |
9 | getGreeting(name: string) {
10 | return 'Hello ' + name;
11 | }
12 |
13 | // getUsers() {
14 | // return this.http.get(`https://api.github.com/users`)
15 | // .map(x => x.json());
16 | // }
17 | }
--------------------------------------------------------------------------------
/src/app/dep-inject/dep-b.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class DepBService {
5 |
6 | constructor() { }
7 | }
--------------------------------------------------------------------------------
/src/app/dep-inject/dep-ctn.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'dep-ctn',
5 | template: `{{ name }}
`
6 | })
7 | export class DepCtnComponent implements OnInit {
8 | constructor() { }
9 |
10 | ngOnInit() { }
11 | }
--------------------------------------------------------------------------------
/src/app/dep-inject/dep-inject.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, SkipSelf, Optional, Inject } from '@angular/core';
2 | import { DepAService } from './dep-a.service';
3 |
4 | class Engine {
5 |
6 | }
7 |
8 | @Component({
9 | selector: 'dep-inject',
10 | template: `{{ greeting }}
`,
11 | providers: [ { provide: DepAService, useValue: { getGreeting(name) { return 'self ' + name } } } ]
12 | })
13 | export class DepInjectComponent implements OnInit {
14 | constructor(@SkipSelf() private depSvc: DepAService, @Optional() @Inject('a') private engine) { }
15 |
16 | greeting: string;
17 |
18 | ngOnInit() {
19 | this.greeting = this.depSvc.getGreeting('jecelyn');
20 | // console.log(this.engine)
21 |
22 | // this.depSvc.getUsers()
23 | // .subscribe(x => console.log(x))
24 | }
25 | }
--------------------------------------------------------------------------------
/src/app/dep-inject/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dep-a.service';
2 | export * from './dep-b.service';
3 | export * from './dep-ctn.component';
4 | export * from './dep-inject.component';
5 | export * from './page-dep-inject.component';
--------------------------------------------------------------------------------
/src/app/dep-inject/page-dep-inject.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Dependency Injection
3 |
4 |
--------------------------------------------------------------------------------
/src/app/dep-inject/page-dep-inject.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'page-dep-inject',
5 | templateUrl: './page-dep-inject.component.html'
6 | })
7 | export class PageDepInjectComponent implements OnInit {
8 | constructor() { }
9 |
10 | ngOnInit() { }
11 | }
--------------------------------------------------------------------------------
/src/app/diff-form-ctrls/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-diff-form-ctrls.component';
--------------------------------------------------------------------------------
/src/app/diff-form-ctrls/page-diff-form-ctrls.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Add user
3 |
59 |
60 |
Form details:-
61 |
Is form valid?: {{f.valid | json}}
62 |
Is form submitted?: {{f.submitted | json}}
63 |
submitted value: {{f.value | json}}
64 |
65 |
--------------------------------------------------------------------------------
/src/app/diff-form-ctrls/page-diff-form-ctrls.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { User } from './user.interface';
3 | import { Theme } from './theme.interface';
4 |
5 | @Component({
6 | selector: 'page-diff-form-ctrls',
7 | templateUrl: './page-diff-form-ctrls.component.html'
8 | })
9 | export class PageDiffFormCtrlsComponent {
10 | public user: User;
11 |
12 | public genders = [
13 | { value: 'F', display: 'Female' },
14 | { value: 'M', display: 'Male' }
15 | ];
16 | public roles = [
17 | { value: 'admin', display: 'Administrator' },
18 | { value: 'guest', display: 'Guest' },
19 | { value: 'custom', display: 'Custom' }
20 | ]
21 |
22 | public themes: Theme[] = [
23 | { backgroundColor: 'black', fontColor: 'white', display: 'Dark' },
24 | { backgroundColor: 'white', fontColor: 'black', display: 'Light' },
25 | { backgroundColor: 'grey', fontColor: 'white', display: 'Sleek' }
26 | ];
27 |
28 | public topics = [
29 | { value: 'game', display: 'Gaming' },
30 | { value: 'tech', display: 'Technology' },
31 | { value: 'life', display: 'Lifestyle' },
32 | ];
33 |
34 | public toggles = [
35 | { value: 'toggled', display: 'Toggled' },
36 | { value: 'untoggled', display: 'UnToggled' },
37 | ];
38 |
39 | public t = {
40 | true: { value: 'toggled', display: 'Toggled' },
41 | false: { value: 'untoggled', display: 'UnToggled' }
42 | }
43 |
44 | ngOnInit() {
45 | this.user = {
46 | name: '',
47 | gender: this.genders[0].value,
48 | role: null,
49 | theme: this.themes[0],
50 | isActive: false,
51 | toggle: this.toggles[1].value,
52 | topics: [this.topics[1].value]
53 | }
54 | }
55 |
56 | save(isValid: boolean, f: User) {
57 | if (!isValid) return;
58 | console.log(f);
59 | }
60 | }
--------------------------------------------------------------------------------
/src/app/diff-form-ctrls/theme.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Theme {
2 | display: string;
3 | backgroundColor: string;
4 | fontColor: string;
5 | }
--------------------------------------------------------------------------------
/src/app/diff-form-ctrls/user.interface.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from './theme.interface';
2 |
3 | export interface User {
4 | name: string;
5 | age?: number;
6 | gender?: string;
7 | role?: string;
8 | theme?: Theme;
9 | isActive?: boolean;
10 | topics?: string[];
11 | toggle?: string;
12 | }
--------------------------------------------------------------------------------
/src/app/file-upload/file-upload.fake.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs/Rx';
3 |
4 | @Injectable()
5 | export class FileUploadFakeService {
6 |
7 | upload(formData: any) {
8 | const photos: any[] = formData.getAll('photos');
9 | const promises = photos.map((x: File) => this.getImage(x)
10 | .then(img => ({
11 | id: img,
12 | originalName: x.name,
13 | fileName: x.name,
14 | url: img
15 | })));
16 | return Observable.fromPromise(Promise.all(promises));
17 | }
18 |
19 | private getImage(file: File) {
20 | return new Promise((resolve, reject) => {
21 | const fReader = new FileReader();
22 | const img = document.createElement('img');
23 |
24 | fReader.onload = () => {
25 | img.src = fReader.result;
26 | resolve(this.getBase64Image(img));
27 | }
28 |
29 | fReader.readAsDataURL(file);
30 | })
31 | }
32 |
33 | private getBase64Image(img) {
34 | const canvas = document.createElement('canvas');
35 | canvas.width = img.width;
36 | canvas.height = img.height;
37 |
38 | const ctx = canvas.getContext('2d');
39 | ctx.drawImage(img, 0, 0);
40 |
41 | const dataURL = canvas.toDataURL('image/png');
42 |
43 | return dataURL;
44 | }
45 | }
--------------------------------------------------------------------------------
/src/app/file-upload/file-upload.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http, RequestOptionsArgs, Headers } from '@angular/http';
3 |
4 | @Injectable()
5 | export class FileUploadService {
6 |
7 | baseUrl = 'http://localhost:3001';
8 |
9 | constructor(private _http: Http) { }
10 |
11 | upload(formData) {
12 | const url = `${this.baseUrl}/photos/upload`;
13 | return this._http.post(url, formData)
14 | .map(x => x.json())
15 | .map((x: any[]) => x
16 | .map(item => Object
17 | .assign({}, item, { url: `${this.baseUrl}/images/${item.id}` }))
18 | );
19 | }
20 | }
--------------------------------------------------------------------------------
/src/app/file-upload/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-file-upload.component';
2 | export * from './file-upload.fake.service';
3 | export * from './file-upload.service';
4 |
--------------------------------------------------------------------------------
/src/app/file-upload/page-file-upload.component.css:
--------------------------------------------------------------------------------
1 | /* page-file-upload.component.css */
2 |
3 | .dropbox {
4 | outline: 2px dashed grey; /* the dash box */
5 | outline-offset: -10px;
6 | background: lightcyan;
7 | color: dimgray;
8 | padding: 10px 10px;
9 | min-height: 200px; /* minimum height */
10 | position: relative;
11 | cursor: pointer;
12 | }
13 |
14 | .dropbox:hover {
15 | background: lightblue; /* when mouse over to the drop zone, change color */
16 | }
17 |
18 | input[type="file"] {
19 | opacity: 0; /* invisible but it's there! */
20 | width: 100%;
21 | height: 200px;
22 | position: absolute;
23 | cursor: pointer;
24 | }
25 |
26 | .dropbox p {
27 | font-size: 1.2em;
28 | text-align: center;
29 | padding: 50px 0;
30 | }
--------------------------------------------------------------------------------
/src/app/file-upload/page-file-upload.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
Uploaded {{ uploadedFiles.length }} file(s) successfully.
22 |
23 | Upload again
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
Uploaded failed.
35 |
36 | Try again
37 |
38 |
{{ uploadError | json }}
39 |
40 |
--------------------------------------------------------------------------------
/src/app/file-upload/page-file-upload.component.ts:
--------------------------------------------------------------------------------
1 | // page-file-upload.component.ts
2 |
3 | import { Component } from '@angular/core';
4 | import { FileUploadService } from './file-upload.service';
5 |
6 | @Component({
7 | selector: 'page-file-upload',
8 | templateUrl: './page-file-upload.component.html',
9 | styleUrls: ['./page-file-upload.component.css']
10 | })
11 | export class PageFileUploadComponent {
12 |
13 | uploadedFiles = [];
14 | uploadError;
15 | currentStatus: number;
16 | uploadFieldName = 'photos';
17 |
18 | readonly STATUS_INITIAL = 0;
19 | readonly STATUS_SAVING = 1;
20 | readonly STATUS_SUCCESS = 2;
21 | readonly STATUS_FAILED = 3;
22 |
23 | constructor(private _svc: FileUploadService) {
24 | this.reset(); // set initial state
25 | }
26 |
27 | filesChange(fieldName: string, fileList: FileList) {
28 | // handle file changes
29 | const formData = new FormData();
30 |
31 | if (!fileList.length) return;
32 |
33 | // append the files to FormData
34 | Array
35 | .from(Array(fileList.length).keys())
36 | .map(x => {
37 | formData.append(fieldName, fileList[x], fileList[x].name);
38 | });
39 |
40 | // save it
41 | this.save(formData);
42 | }
43 |
44 | reset() {
45 | this.currentStatus = this.STATUS_INITIAL;
46 | this.uploadedFiles = [];
47 | this.uploadError = null;
48 | }
49 |
50 | save(formData: FormData) {
51 | // upload data to the server
52 | this.currentStatus = this.STATUS_SAVING;
53 | this._svc.upload(formData)
54 | .take(1)
55 | .delay(1500) // DEV ONLY: delay 1.5s to see the changes
56 | .subscribe(x => {
57 | this.uploadedFiles = [].concat(x);
58 | this.currentStatus = this.STATUS_SUCCESS;
59 | }, err => {
60 | this.uploadError = err;
61 | this.currentStatus = this.STATUS_FAILED;
62 | })
63 | }
64 | }
--------------------------------------------------------------------------------
/src/app/form-array-dyn-val/customer.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Customer {
2 | name: string;
3 | addresses: Address[];
4 | }
5 |
6 | export interface Address {
7 | street: string;
8 | postcode: string;
9 | }
--------------------------------------------------------------------------------
/src/app/form-array-dyn-val/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-form-array-dyn-val.component';
--------------------------------------------------------------------------------
/src/app/form-array-dyn-val/page-form-array-dyn-val.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Add customer
6 |
7 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/app/form-array-dyn-val/page-form-array-dyn-val.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormGroup, FormArray, FormBuilder, Validators, FormControl } from '@angular/forms';
3 | import { Customer } from './customer.interface';
4 |
5 | @Component({
6 | selector: 'page-form-array-dyn-val',
7 | templateUrl: './page-form-array-dyn-val.component.html'
8 | })
9 | export class PageFormArrayDynValComponent implements OnInit {
10 | public myForm: FormGroup;
11 |
12 | formErrors = {}; // expected format = { name: 'error message' }
13 | addressesErrors = {}; // expected format = { 0: { street: 'error message' } }
14 |
15 | constructor(private _fb: FormBuilder) { }
16 |
17 | ngOnInit() {
18 | this.myForm = this._fb.group({
19 | name: ['111', [Validators.required, Validators.minLength(5)]],
20 | addresses: this._fb.array([
21 | this.initAddress(),
22 | ])
23 | });
24 |
25 | this.myForm.valueChanges.subscribe(x => this.onValChanges(x));
26 |
27 | // HACK: trigger value changes immediately
28 | this.myForm.patchValue({});
29 | }
30 |
31 | onValChanges(data) {
32 | if (!this.myForm) return;
33 |
34 | // handle main form errors
35 | this.onFormValueChanged();
36 | }
37 |
38 | /* Update Main Form Validations */
39 | onFormValueChanged() {
40 | const custF = this.myForm;
41 |
42 | // setup fields to validate and the messages
43 | const fields = {
44 | name: '',
45 | addresses: {
46 | $each: {
47 | street: ''
48 | }
49 | }
50 | };
51 | const validationMessages = {
52 | name: {
53 | required: 'it is required',
54 | minlength: 'must be more than 5'
55 | },
56 | addresses: {
57 | $each: {
58 | street: {
59 | required: 'my god',
60 | minlength: 'must be more than 5'
61 | }
62 | }
63 | }
64 | };
65 |
66 | const re = this.handleValidations(fields, validationMessages, custF);
67 | this.formErrors = Object.assign(this.formErrors, re);
68 | }
69 |
70 | handleValidations(fs, validationMessages, fg: FormGroup) {
71 | // avoid mutation
72 | const fields = Object.assign({}, fs);
73 |
74 | Object.keys(fields).forEach(field => {
75 | const control = fg.get(field);
76 | if (control instanceof FormControl) {
77 | if (!control.valid) {
78 | const messages = validationMessages[field];
79 | Object.keys(control.errors).forEach(key => {
80 | fields[field] += messages[key];
81 | })
82 | }
83 | }
84 |
85 | if (control instanceof FormArray && fields[field].$each) {
86 | (control as FormArray).controls.forEach((val, idx) => {
87 | const innerFg = control.get(idx.toString()) as FormGroup;
88 | const err = this.handleValidations(fields[field].$each, validationMessages[field].$each,
89 | innerFg)
90 |
91 | fields[field][idx] = err;
92 | })
93 | }
94 | })
95 | console.log('final', fields)
96 | return fields;
97 | }
98 |
99 | initAddress() {
100 | return this._fb.group({
101 | street: ['', [Validators.required, Validators.minLength(5)]],
102 | postcode: ['']
103 | });
104 | }
105 |
106 | addAddress() {
107 | const control = this.myForm.get('addresses') as FormArray;
108 | control.push(this.initAddress());
109 | }
110 |
111 | removeAddress(i: number) {
112 | const control = this.myForm.get('addresses') as FormArray;
113 | control.removeAt(i);
114 | }
115 |
116 | save(model: Customer) {
117 | // call API to save
118 | // ...
119 | console.log(model);
120 | }
121 | }
--------------------------------------------------------------------------------
/src/app/global-pipe/capitalize.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'capitalize'
5 | })
6 |
7 | export class CapitalizePipe implements PipeTransform {
8 | transform(value: string, args: any[]): any {
9 | if (!value) return value;
10 |
11 | return value.replace(/\w\S*/g, function(txt) {
12 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
13 | });
14 | }
15 | }
--------------------------------------------------------------------------------
/src/app/global-pipe/index.ts:
--------------------------------------------------------------------------------
1 | export * from './capitalize.pipe';
2 | export * from './page-global-pipe.component';
--------------------------------------------------------------------------------
/src/app/global-pipe/page-global-pipe.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'page-global-pipe',
5 | template: `
6 |
7 |
My name is {{ name | capitalize }} .
8 |
`
9 | })
10 | export class PageGlobalPipeComponent {
11 | name = 'john doe';
12 | }
--------------------------------------------------------------------------------
/src/app/hammerjs/hammer-config.ts:
--------------------------------------------------------------------------------
1 | import { HammerGestureConfig } from '@angular/platform-browser';
2 |
3 | export class MyHammerConfig extends HammerGestureConfig {
4 | overrides = {
5 | 'swipe': {velocity: 0.4, threshold: 20} // override default settings
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/hammerjs/index.ts:
--------------------------------------------------------------------------------
1 | export * from './hammer-config';
2 | export * from './page-hammerjs.component';
--------------------------------------------------------------------------------
/src/app/hammerjs/page-hammerjs.component.css:
--------------------------------------------------------------------------------
1 | .swipe-box {
2 | display: block;
3 | width: 100%;
4 | float: left;
5 | margin: 0;
6 | transform: .5s ease .1s;
7 | }
8 |
9 | .ctn {
10 | max-width: 400px;
11 | margin: 0 auto;
12 | }
13 |
14 | .inner-ctn {
15 | margin: 20px;
16 | }
17 |
18 | .visible {
19 | display: block;
20 | }
21 |
22 | .hidden {
23 | display: none;
24 | }
--------------------------------------------------------------------------------
/src/app/hammerjs/page-hammerjs.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/hammerjs/page-hammerjs.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'page-hammerjs',
5 | templateUrl: './page-hammerjs.component.html',
6 | styleUrls: ['./page-hammerjs.component.css']
7 | })
8 | export class PageHammerjsComponent {
9 | SWIPE_ACTION = { LEFT: 'swipeleft', RIGHT: 'swiperight' };
10 |
11 | avatars = [
12 | {
13 | name: 'kristy',
14 | image: 'http://semantic-ui.com/images/avatar2/large/kristy.png',
15 | visible: true
16 | },
17 | {
18 | name: 'matthew',
19 | image: 'http://semantic-ui.com/images/avatar2/large/matthew.png',
20 | visible: false
21 | },
22 | {
23 | name: 'chris',
24 | image: 'http://semantic-ui.com/images/avatar/large/chris.jpg',
25 | visible: false
26 | },
27 | {
28 | name: 'jenny',
29 | image: 'http://semantic-ui.com/images/avatar/large/jenny.jpg',
30 | visible: false
31 | }
32 | ];
33 |
34 | swipe(currentIndex: number, action = this.SWIPE_ACTION.RIGHT) {
35 | console.log(currentIndex);
36 | if (currentIndex > this.avatars.length || currentIndex < 0) return;
37 |
38 | let nextIndex = 0;
39 |
40 | // next
41 | if (action === this.SWIPE_ACTION.RIGHT) {
42 | const isLast = currentIndex === this.avatars.length - 1;
43 | nextIndex = isLast ? 0 : currentIndex + 1;
44 | }
45 |
46 | // previous
47 | if (action === this.SWIPE_ACTION.LEFT) {
48 | const isFirst = currentIndex === 0;
49 | nextIndex = isFirst ? this.avatars.length - 1 : currentIndex - 1;
50 | }
51 |
52 | // toggle avatar visibility
53 | this.avatars.forEach((x, i) => x.visible = (i === nextIndex));
54 | }
55 | }
--------------------------------------------------------------------------------
/src/app/home/article.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Article {
2 | name: string;
3 | url: string;
4 | desc: string;
5 | }
--------------------------------------------------------------------------------
/src/app/home/home.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http } from '@angular/http';
3 |
4 | @Injectable()
5 | export class HomeService {
6 |
7 | constructor(private _http: Http) { }
8 |
9 | getArticles() {
10 | return this._http.get('/assets/articles.json')
11 | .map(x => x.json());
12 | }
13 | }
--------------------------------------------------------------------------------
/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-home.component';
2 | export * from './home.service';
--------------------------------------------------------------------------------
/src/app/home/page-home.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/home/page-home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { HomeService } from './home.service';
3 | import { Article } from './article.interface';
4 |
5 | @Component({
6 | selector: 'page-home',
7 | templateUrl: './page-home.component.html'
8 | })
9 | export class PageHomeComponent implements OnInit {
10 |
11 | articles: Article[];
12 |
13 | constructor(private _homeSvc: HomeService) {
14 | }
15 |
16 | ngOnInit() {
17 | this._homeSvc.getArticles().subscribe(x => {
18 | this.articles = x;
19 | })
20 | }
21 | }
--------------------------------------------------------------------------------
/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export * from './app.component';
2 | export * from './app.module';
3 |
--------------------------------------------------------------------------------
/src/app/lang/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lang-en';
2 | export * from './lang-es';
3 | export * from './lang-zh';
--------------------------------------------------------------------------------
/src/app/lang/lang-en.ts:
--------------------------------------------------------------------------------
1 | // lang-en.ts
2 |
3 | export const LANG_EN_NAME = 'en';
4 |
5 | export const LANG_EN_TRANS = {
6 | 'hello world': 'hello world',
7 | 'hello greet': 'Hello, %0 %1!',
8 | 'well done': 'Well done %0',
9 | 'good bye': 'bye bye',
10 | };
--------------------------------------------------------------------------------
/src/app/lang/lang-es.ts:
--------------------------------------------------------------------------------
1 | // lang-es.ts
2 |
3 | export const LANG_ES_NAME = 'es';
4 |
5 | export const LANG_ES_TRANS = {
6 | 'hello world': 'hola mundo',
7 | 'hello greet': 'Hola, %0 %1!',
8 | 'well done': '%0 bien hecho',
9 | };
--------------------------------------------------------------------------------
/src/app/lang/lang-zh.ts:
--------------------------------------------------------------------------------
1 | // lang-zh.ts
2 |
3 | export const LANG_ZH_NAME = 'zh';
4 |
5 | export const LANG_ZH_TRANS = {
6 | 'hello world': '你好,世界',
7 | 'hello greet': '你好, %0 %1!',
8 | 'well done': '干得好, %0',
9 | };
--------------------------------------------------------------------------------
/src/app/model-driven-frm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-model-driven-frm.component';
--------------------------------------------------------------------------------
/src/app/model-driven-frm/page-model-driven-frm.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/model-driven-frm/page-model-driven-frm.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
3 | import { User } from './user.interface';
4 |
5 | @Component({
6 | selector: 'page-model-driven-frm',
7 | templateUrl: './page-model-driven-frm.component.html'
8 | })
9 | export class PageModelDrivenFrmComponent implements OnInit {
10 | public myForm: FormGroup;
11 | public submitted: boolean;
12 | public events: any[] = [];
13 |
14 | constructor(private _fb: FormBuilder) { }
15 |
16 | ngOnInit() {
17 | // the long way
18 | // this.myForm = new FormGroup({
19 | // name: new FormControl('', [Validators.required, Validators.minLength(5)]),
20 | // address: new FormGroup({
21 | // address1: new FormControl('', Validators.required),
22 | // postcode: new FormControl('8000')
23 | // })
24 | // });
25 |
26 | // the short way
27 | this.myForm = this._fb.group({
28 | name: ['', [Validators.required, Validators.minLength(5)]],
29 | address: this._fb.group({
30 | street: ['', Validators.required],
31 | postcode: ['8000']
32 | })
33 | });
34 |
35 | // subscribe to form changes
36 | this.subcribeToFormChanges();
37 |
38 | // Update single value
39 | this.myForm.controls['name']
40 | .setValue('John', { onlySelf: true });
41 |
42 | // Update form model
43 | // const people = {
44 | // name: 'Jane',
45 | // address: {
46 | // street: 'High street',
47 | // postcode: '94043'
48 | // }
49 | // };
50 |
51 | // this.myForm
52 | // .setValue(people, { onlySelf: true });
53 |
54 | }
55 |
56 | subcribeToFormChanges() {
57 | const myFormStatusChanges$ = this.myForm.statusChanges;
58 | const myFormValueChanges$ = this.myForm.valueChanges;
59 |
60 | myFormStatusChanges$.subscribe(x => this.events.push({ event: 'STATUS_CHANGED', object: x }));
61 | myFormValueChanges$.subscribe(x => this.events.push({ event: 'VALUE_CHANGED', object: x }));
62 | }
63 |
64 | save(model: User, isValid: boolean) {
65 | this.submitted = true;
66 | console.log(model, isValid);
67 | }
68 | }
--------------------------------------------------------------------------------
/src/app/model-driven-frm/user.interface.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | name: string;
3 | address: {
4 | street: string;
5 | postcode: string;
6 | }
7 | }
--------------------------------------------------------------------------------
/src/app/nested-model-driven-frm/customer.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Customer {
2 | name: string;
3 | addresses: Address[];
4 | }
5 |
6 | export interface Address {
7 | street: string;
8 | postcode: string;
9 | }
--------------------------------------------------------------------------------
/src/app/nested-model-driven-frm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-nested-model-driven-frm.component';
--------------------------------------------------------------------------------
/src/app/nested-model-driven-frm/page-nested-model-driven-frm.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Add customer
6 |
7 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/app/nested-model-driven-frm/page-nested-model-driven-frm.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormGroup, FormArray, FormBuilder, Validators } from '@angular/forms';
3 | import { Customer } from './customer.interface';
4 |
5 | @Component({
6 | selector: 'page-nested-model-driven-frm',
7 | templateUrl: './page-nested-model-driven-frm.component.html'
8 | })
9 | export class PageNestedModelDrivenFrmComponent implements OnInit {
10 | public myForm: FormGroup;
11 |
12 | constructor(private _fb: FormBuilder) { }
13 |
14 | ngOnInit() {
15 | this.myForm = this._fb.group({
16 | name: ['', [Validators.required, Validators.minLength(5)]],
17 | addresses: this._fb.array([
18 | this.initAddress(),
19 | ])
20 | });
21 | }
22 |
23 | initAddress() {
24 | return this._fb.group({
25 | street: ['', Validators.required],
26 | postcode: ['']
27 | });
28 | }
29 |
30 | addAddress() {
31 | const control = this.myForm.controls['addresses'];
32 | control.push(this.initAddress());
33 | }
34 |
35 | removeAddress(i: number) {
36 | const control = this.myForm.controls['addresses'];
37 | control.removeAt(i);
38 | }
39 |
40 | save(model: Customer) {
41 | // call API to save
42 | // ...
43 | console.log(model);
44 | }
45 | }
--------------------------------------------------------------------------------
/src/app/same-height/card-same-height.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ title }}
3 |
4 | {{ content }}
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/app/same-height/card-same-height.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'card-same-height',
5 | templateUrl: './card-same-height.component.html'
6 | })
7 | export class CardSameHeightComponent implements OnInit {
8 |
9 | @Input()
10 | title: string;
11 |
12 | @Input()
13 | content: string;
14 |
15 | constructor() { }
16 |
17 | ngOnInit() {
18 | }
19 |
20 |
21 | }
--------------------------------------------------------------------------------
/src/app/same-height/index.ts:
--------------------------------------------------------------------------------
1 | export * from './card-same-height.component';
2 | export * from './page-same-height.component';
3 | export * from './match-height.directive';
4 | export * from './third.component';
--------------------------------------------------------------------------------
/src/app/same-height/match-height.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive, ElementRef, AfterViewChecked,
3 | Input, HostListener
4 | } from '@angular/core';
5 |
6 | @Directive({
7 | selector: '[myMatchHeight]'
8 | })
9 | export class MatchHeightDirective implements AfterViewChecked {
10 |
11 | // class name to match height
12 | @Input()
13 | myMatchHeight: any;
14 |
15 | constructor(private el: ElementRef) {
16 | }
17 |
18 | ngAfterViewChecked() {
19 | // call our matchHeight function here later
20 | this.matchHeight(this.el.nativeElement, this.myMatchHeight);
21 | }
22 |
23 | @HostListener('window:resize')
24 | onResize() {
25 | // call our matchHeight function here later
26 | this.matchHeight(this.el.nativeElement, this.myMatchHeight);
27 | }
28 |
29 | matchHeight(parent: HTMLElement, className: string) {
30 | // match height logic here
31 |
32 | if (!parent) return;
33 | const children = parent.getElementsByClassName(className);
34 |
35 | if (!children) return;
36 |
37 | // reset all children height
38 | Array.from(children).forEach((x: HTMLElement) => {
39 | x.style.height = 'initial';
40 | })
41 |
42 | // gather all height
43 | const itemHeights = Array.from(children)
44 | .map(x => x.getBoundingClientRect().height);
45 |
46 | // find max height
47 | const maxHeight = itemHeights.reduce((prev, curr) => {
48 | return curr > prev ? curr : prev;
49 | }, 0);
50 |
51 | // apply max height
52 | Array.from(children)
53 | .forEach((x: HTMLElement) => x.style.height = `${maxHeight}px`);
54 | }
55 | }
--------------------------------------------------------------------------------
/src/app/same-height/page-same-height.component.css:
--------------------------------------------------------------------------------
1 | .half {
2 | width: 50%;
3 | border: 1px solid blue;
4 | margin: 10px;
5 | padding: 10px;
6 | }
--------------------------------------------------------------------------------
/src/app/same-height/page-same-height.component.html:
--------------------------------------------------------------------------------
1 |
2 | Malaysia States
3 |
4 |
5 |
6 |
7 |
8 |
{{ state.title }}
9 |
10 | {{ state.content }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
43 |
--------------------------------------------------------------------------------
/src/app/same-height/page-same-height.component.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Component } from '@angular/core';
3 |
4 | @Component({
5 | selector: 'page-same-height',
6 | templateUrl: './page-same-height.component.html',
7 | styleUrls: ['./page-same-height.component.css']
8 | })
9 | export class PageSameHeightComponent {
10 | list = [
11 | {
12 | title: 'Selangor',
13 | content: 'Selangor is a state on the west coast of Peninsular Malaysia, encircling the capital Kuala Lumpur. In the state capital, Shah Alam, the Sultan Salahuddin Abdul Aziz Mosque has 4 soaring minarets and a huge blue dome.'
14 | },
15 | {
16 | title: 'Kuala Lumpur',
17 | content: 'Kuala Lumpur is the capital of Malaysia. Its modern skyline is dominated by the 451m-tall Petronas Twin Towers, a pair of glass-and-steel-clad skyscrapers with Islamic motifs. The towers also offer a public skybridge and observation deck. The city is also home to British colonial-era landmarks such as the Kuala Lumpur Railway Station and the Sultan Abdul Samad Building.'
18 | },
19 | {
20 | title: 'Perak',
21 | content: 'Perak is a state in the northwest of Peninsular Malaysia. The capital city Ipoh is known for its British colonial landmarks, including a baroque railway station. Sam Poh Tong is a huge Buddhist cave temple filled with wall paintings. The town of Gopeng is a base for rafting on the Kampar river.'
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/app/same-height/third.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 | import { Observable } from 'rxjs/Observable';
3 | @Component({
4 | selector: 'third',
5 | template: '{{ paragraph }}
'
6 | })
7 | export class ThirdComponent implements OnInit {
8 |
9 | @Input()
10 | content: string;
11 |
12 | paragraph: string;
13 |
14 | constructor() { }
15 |
16 | ngOnInit() {
17 | Observable.of('sssss ssss ss s s s s s s ss')
18 | .delay(2000)
19 | .subscribe(x => this.paragraph = this.content.concat(x));
20 | // console.log(this.content)
21 | }
22 |
23 |
24 | }
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-1/index.ts:
--------------------------------------------------------------------------------
1 | export * from './translate-1.pipe';
2 | export * from './translate-1.service';
3 | export * from './translations-1';
4 | export * from './page-simple-language-translation-part-1.component';
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-1/page-simple-language-translation-part-1.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Translate: Hello World
3 |
4 |
7 | {{ lang.display }}
8 |
9 |
10 |
11 |
12 | Translate With Pipe: {{ 'hello world' | translate1 }}
13 |
14 |
15 | Translate with Service: {{ translatedText }}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-1/page-simple-language-translation-part-1.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Translate1Service } from './translate-1.service';
3 |
4 | @Component({
5 | selector: 'page-simple-language-translation-part-1',
6 | templateUrl: './page-simple-language-translation-part-1.component.html'
7 | })
8 | export class PageSimpleLanguageTranslationPart1Component {
9 | translatedText: string;
10 | supportedLangs: any[];
11 |
12 | constructor(private _translate: Translate1Service) { }
13 |
14 | ngOnInit() {
15 | // standing data
16 | this.supportedLangs = [
17 | { display: 'English', value: 'en' },
18 | { display: 'Español', value: 'es' },
19 | { display: '华语', value: 'zh' },
20 | ];
21 |
22 | this.selectLang('es');
23 |
24 | }
25 |
26 | isCurrentLang(lang: string) {
27 | return lang === this._translate.currentLang;
28 | }
29 |
30 | selectLang(lang: string) {
31 | // set default;
32 | this._translate.use(lang);
33 | this.refreshText();
34 | }
35 |
36 | refreshText() {
37 | this.translatedText = this._translate.instant('hello world');
38 | }
39 | }
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-1/translate-1.pipe.ts:
--------------------------------------------------------------------------------
1 | // app/translate/translate.pipe.ts
2 |
3 | import { Pipe, PipeTransform } from '@angular/core';
4 | import { Translate1Service } from './translate-1.service'; // our translate service
5 |
6 | @Pipe({
7 | name: 'translate1',
8 | pure: false // impure pipe, update value when we change language
9 | })
10 |
11 | export class Translate1Pipe implements PipeTransform {
12 |
13 | constructor(private _translate: Translate1Service) { }
14 |
15 | transform(value: string, args: any[]): any {
16 | if (!value) return;
17 |
18 | return this._translate.instant(value);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-1/translate-1.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable, Inject} from '@angular/core';
2 | import { TRANSLATIONS1 } from './translations-1'; // import our opaque token
3 |
4 | @Injectable()
5 | export class Translate1Service {
6 | private _currentLang: string;
7 |
8 | public get currentLang() {
9 | return this._currentLang;
10 | }
11 |
12 | // inject our translations
13 | constructor(@Inject(TRANSLATIONS1) private _translations: any) {
14 | }
15 |
16 | public use(lang: string): void {
17 | // set current language
18 | this._currentLang = lang;
19 | }
20 |
21 | private translate(key: string): string {
22 | // private perform translation
23 | let translation = key;
24 |
25 | if (this._translations[this.currentLang] && this._translations[this.currentLang][key]) {
26 | return this._translations[this.currentLang][key];
27 | }
28 |
29 | return translation;
30 | }
31 |
32 | public instant(key: string) {
33 | // public perform translation
34 | return this.translate(key);
35 | }
36 | }
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-1/translations-1.ts:
--------------------------------------------------------------------------------
1 | // app/translate/translation.ts
2 |
3 | import { OpaqueToken } from '@angular/core';
4 |
5 | // import translations
6 | import {
7 | LANG_EN_NAME, LANG_EN_TRANS,
8 | LANG_ES_NAME, LANG_ES_TRANS,
9 | LANG_ZH_NAME, LANG_ZH_TRANS,
10 | } from '../lang';
11 |
12 | // translation token
13 | export const TRANSLATIONS1 = new OpaqueToken('translations1');
14 |
15 | // all traslations
16 | const dictionary = {
17 | [LANG_EN_NAME]: LANG_EN_TRANS,
18 | [LANG_ES_NAME]: LANG_ES_TRANS,
19 | [LANG_ZH_NAME]: LANG_ZH_TRANS,
20 | };
21 |
22 | // providers
23 | export const TRANSLATION1_PROVIDERS = [
24 | { provide: TRANSLATIONS1, useValue: dictionary },
25 | ];
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-2/index.ts:
--------------------------------------------------------------------------------
1 | export * from './translate-2.pipe';
2 | export * from './translate-2.service';
3 | export * from './translations-2';
4 | export * from './page-simple-language-translation-part-2.component';
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-2/page-simple-language-translation-part-2.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Translate: Hello World
3 |
4 |
7 | {{ lang.display }}
8 |
9 |
10 |
11 |
12 | Translate With Pipe: {{ 'hello world' | translate2 }}
13 |
14 |
15 | Translate with Service: {{ translatedText }}
16 |
17 |
18 | Translate Hello, %0 %1! :
19 |
20 | {{ 'hello greet' | translate2:['Jane', 'Doe'] }}
21 |
22 |
23 | Translate Well done %0 :
24 |
25 | {{ 'well done' | translate2:'John' }}
26 |
27 |
28 | Translate Good bye (fallback) :
29 |
30 | {{ 'good bye' | translate2 }}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-2/page-simple-language-translation-part-2.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Translate2Service } from './translate-2.service';
3 |
4 | @Component({
5 | selector: 'page-simple-language-translation-part-2',
6 | templateUrl: './page-simple-language-translation-part-2.component.html'
7 | })
8 | export class PageSimpleLanguageTranslationPart2Component {
9 | translatedText: string;
10 | supportedLangs: any[];
11 |
12 | constructor(private _translate: Translate2Service) { }
13 |
14 | ngOnInit() {
15 | // standing data
16 | this.supportedLangs = [
17 | { display: 'English', value: 'en' },
18 | { display: 'Español', value: 'es' },
19 | { display: '华语', value: 'zh' },
20 | ];
21 |
22 | this.selectLang('es');
23 |
24 | }
25 |
26 | isCurrentLang(lang: string) {
27 | return lang === this._translate.currentLang;
28 | }
29 |
30 | selectLang(lang: string) {
31 | // set default;
32 | this._translate.use(lang);
33 | this.refreshText();
34 | }
35 |
36 | refreshText() {
37 | this.translatedText = this._translate.instant('hello world');
38 | }
39 | }
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-2/translate-2.pipe.ts:
--------------------------------------------------------------------------------
1 | // app/translate/translate.pipe.ts
2 |
3 | import { Pipe, PipeTransform } from '@angular/core';
4 | import { Translate2Service } from './translate-2.service'; // our translate service
5 |
6 | @Pipe({
7 | name: 'translate2',
8 | pure: false // impure pipe, update value when we change language
9 | })
10 |
11 | export class Translate2Pipe implements PipeTransform {
12 |
13 | constructor(private _translate: Translate2Service) { }
14 |
15 | transform(value: string, args: string | string[]): any {
16 | if (!value) return;
17 |
18 | return this._translate.instant(value, args);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-2/translate-2.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable, Inject, EventEmitter } from '@angular/core';
2 | import { TRANSLATIONS2 } from './translations-2'; // import our opaque token
3 |
4 | @Injectable()
5 | export class Translate2Service {
6 | private _currentLang: string;
7 | private PLACEHOLDER = '%';
8 | private _defaultLang: string;
9 | private _fallback: boolean;
10 |
11 | public onLangChanged: EventEmitter = new EventEmitter();
12 |
13 | public get currentLang() {
14 | return this._currentLang || this._defaultLang;
15 | }
16 |
17 | public setDefaultLang(lang: string) {
18 | this._defaultLang = lang;
19 | }
20 |
21 | public enableFallback(enable: boolean) {
22 | this._fallback = enable;
23 | }
24 |
25 | // inject our translations
26 | constructor(@Inject(TRANSLATIONS2) private _translations: any) {
27 | }
28 |
29 | public use(lang: string): void {
30 | // set current language
31 | this._currentLang = lang;
32 | this.onLangChanged.emit(lang);
33 | }
34 |
35 | private translate(key: string): string {
36 | let translation = key;
37 |
38 | // found in current language
39 | if (this._translations[this.currentLang] && this._translations[this.currentLang][key]) {
40 | return this._translations[this.currentLang][key];
41 | }
42 |
43 | // fallback disabled
44 | if (!this._fallback) {
45 | return translation;
46 | }
47 |
48 | // found in default language
49 | if (this._translations[this._defaultLang] && this._translations[this._defaultLang][key]) {
50 | return this._translations[this._defaultLang][key];
51 | }
52 |
53 | // not found
54 | return translation;
55 | }
56 |
57 | public replace(word: string = '', words: string | string[] = '') {
58 | let translation: string = word;
59 |
60 | const values: string[] = [].concat(words);
61 | values.forEach((e, i) => {
62 | translation = translation.replace(this.PLACEHOLDER.concat(i), e);
63 | });
64 |
65 | return translation;
66 | }
67 |
68 | public instant(key: string, words?: string | string[]) {
69 | // public perform translation
70 | const translation: string = this.translate(key);
71 |
72 | if (!words) return translation;
73 | return this.replace(translation, words);
74 | }
75 | }
--------------------------------------------------------------------------------
/src/app/simple-language-translation-part-2/translations-2.ts:
--------------------------------------------------------------------------------
1 | // app/translate/translation.ts
2 |
3 | import { OpaqueToken } from '@angular/core';
4 |
5 | // import translations
6 | import {
7 | LANG_EN_NAME, LANG_EN_TRANS,
8 | LANG_ES_NAME, LANG_ES_TRANS,
9 | LANG_ZH_NAME, LANG_ZH_TRANS,
10 | } from '../lang';
11 |
12 | // translation token
13 | export const TRANSLATIONS2 = new OpaqueToken('translations1');
14 |
15 | // all traslations
16 | const dictionary = {
17 | [LANG_EN_NAME]: LANG_EN_TRANS,
18 | [LANG_ES_NAME]: LANG_ES_TRANS,
19 | [LANG_ZH_NAME]: LANG_ZH_TRANS,
20 | };
21 |
22 | // providers
23 | export const TRANSLATION2_PROVIDERS = [
24 | { provide: TRANSLATIONS2, useValue: dictionary },
25 | ];
--------------------------------------------------------------------------------
/src/app/template-driven-frm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-template-driven-frm.component';
--------------------------------------------------------------------------------
/src/app/template-driven-frm/page-template-driven-frm.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Add user
3 |
33 |
34 |
Form details:-
35 |
Is form valid?: {{f.valid | json}}
36 |
Is form submitted?: {{f.submitted | json}}
37 |
form value: {{f.value | json}}
38 |
39 |
--------------------------------------------------------------------------------
/src/app/template-driven-frm/page-template-driven-frm.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { User } from './user.interface';
3 |
4 | @Component({
5 | selector: 'page-template-driven-frm',
6 | templateUrl: './page-template-driven-frm.component.html'
7 | })
8 | export class PageTemplateDrivenFrmComponent {
9 | public user: User;
10 |
11 | ngOnInit() {
12 | this.user = {
13 | name: '',
14 | address: {
15 | street: '',
16 | postcode: '8000'
17 | }
18 | };
19 | }
20 |
21 | save(model: User, isValid: boolean) {
22 | console.log(model, isValid);
23 | }
24 | }
--------------------------------------------------------------------------------
/src/app/template-driven-frm/user.interface.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | name: string;
3 | address: {
4 | street: string;
5 | postcode: string;
6 | }
7 | }
--------------------------------------------------------------------------------
/src/app/three-ways/blogger.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 | import { Http } from '@angular/http';
3 | import { Post } from './post.interface';
4 |
5 | @Component({
6 | selector: 'bloggers',
7 | template: `
8 | Posts by: {{ blogger }}
9 | Solution 1: ngIf
10 |
13 | Solution 2: ngOnChanges
14 |
17 | Solution 3: RxJs BehavourSubject
18 |
21 | `
22 | })
23 | export class BloggerComponent implements OnInit {
24 |
25 | blogger = 'Jecelyn';
26 | posts: Post[];
27 |
28 | constructor(private _http: Http) { }
29 |
30 | ngOnInit() {
31 | this.getPostsByBlogger()
32 | .subscribe(x => this.posts = x);
33 | }
34 |
35 | getPostsByBlogger() {
36 | const url = 'assets/mock-posts.json';
37 | return this._http.get(url)
38 | .map(x => x.json());
39 | }
40 | }
--------------------------------------------------------------------------------
/src/app/three-ways/index.ts:
--------------------------------------------------------------------------------
1 | export * from './page-three-ways.component';
2 | export * from './blogger.component';
3 | export * from './posts-1.component';
4 | export * from './posts-2.component';
5 | export * from './posts-3.component';
--------------------------------------------------------------------------------
/src/app/three-ways/page-three-ways.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/app/three-ways/page-three-ways.component.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Component } from '@angular/core';
3 |
4 | @Component({
5 | selector: 'page-three-ways',
6 | templateUrl: './page-three-ways.component.html'
7 | })
8 | export class PageThreeWaysComponent {
9 |
10 | }
--------------------------------------------------------------------------------
/src/app/three-ways/post.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Post {
2 | title: string;
3 | category: string;
4 | }
5 |
6 | export interface GroupPosts {
7 | category: string;
8 | posts: Post[];
9 | }
--------------------------------------------------------------------------------
/src/app/three-ways/posts-1.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2 | import { BehaviorSubject } from 'rxjs/BehaviorSubject';
3 | import { Post, GroupPosts } from './post.interface';
4 |
5 | @Component({
6 | selector: 'posts-1',
7 | template: `
8 |
9 |
10 |
{{ group.category }}
11 |
12 |
13 | {{ post.title }}
14 |
15 |
16 |
17 |
18 | `
19 | })
20 | export class Posts1Component implements OnInit {
21 |
22 | @Input()
23 | data: Post[];
24 |
25 | groupPosts: GroupPosts[];
26 |
27 | constructor() { }
28 |
29 | ngOnInit() {
30 | this.groupPosts = this.groupByCategory(this.data);
31 | }
32 |
33 | groupByCategory(data: Post[]): GroupPosts[] {
34 | if (!data) return;
35 | const categories = new Set(data.map(x => x.category));
36 | const result = Array.from(categories).map(x => ({
37 | category: x,
38 | posts: data.filter(post => post.category === x)
39 | }));
40 |
41 | return result;
42 | }
43 | }
--------------------------------------------------------------------------------
/src/app/three-ways/posts-2.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2 | import { BehaviorSubject } from 'rxjs/BehaviorSubject';
3 | import { Post, GroupPosts } from './post.interface';
4 |
5 | @Component({
6 | selector: 'posts-2',
7 | template: `
8 |
9 |
10 |
{{ group.category }}
11 |
12 |
13 | {{ post.title }}
14 |
15 |
16 |
17 |
18 | `
19 | })
20 | export class Posts2Component implements OnChanges {
21 |
22 | @Input()
23 | data: Post[];
24 |
25 | groupPosts: GroupPosts[];
26 |
27 | constructor() { }
28 |
29 | ngOnChanges(changes: SimpleChanges) {
30 | // only run when property "data" changed
31 | if (changes['data']) {
32 | this.groupPosts = this.groupByCategory(this.data);
33 | }
34 | }
35 |
36 | groupByCategory(data: Post[]): GroupPosts[] {
37 | if (!data) return;
38 | const categories = new Set(data.map(x => x.category));
39 | const result = Array.from(categories).map(x => ({
40 | category: x,
41 | posts: data.filter(post => post.category === x)
42 | }));
43 |
44 | return result;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/app/three-ways/posts-3.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2 | import { BehaviorSubject } from 'rxjs/BehaviorSubject';
3 | import { Post, GroupPosts } from './post.interface';
4 |
5 | @Component({
6 | selector: 'posts-3',
7 | template: `
8 |
9 |
10 |
{{ group.category }}
11 |
12 |
13 | {{ post.title }}
14 |
15 |
16 |
17 |
18 | `
19 | })
20 | export class Posts3Component implements OnInit {
21 |
22 | private _data = new BehaviorSubject
([]);
23 |
24 | @Input()
25 | set data(value) {
26 | this._data.next(value);
27 | };
28 |
29 | get data() {
30 | return this._data.getValue();
31 | }
32 |
33 | groupPosts: GroupPosts[];
34 |
35 | constructor() { }
36 |
37 | ngOnInit() {
38 | this._data
39 | .takeWhile(() => !this.groupPosts) // unsubscribe once groupPosts has value
40 | .subscribe(x => {
41 | this.groupPosts = this.groupByCategory(this.data);
42 | });
43 | }
44 |
45 | groupByCategory(data: Post[]): GroupPosts[] {
46 | if (!data) return;
47 | const categories = new Set(data.map(x => x.category));
48 | const result = Array.from(categories).map(x => ({
49 | category: x,
50 | posts: data.filter(post => post.category === x)
51 | }));
52 |
53 | return result;
54 | }
55 | }
--------------------------------------------------------------------------------
/src/app/transclusion/card.component.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
--------------------------------------------------------------------------------
/src/app/transclusion/card.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'card',
5 | templateUrl: './card.component.html',
6 | })
7 | export class CardComponent {
8 | @Input()
9 | public header: string = 'this is header';
10 |
11 | @Input()
12 | public footer: string = 'this is footer';
13 | }
--------------------------------------------------------------------------------
/src/app/transclusion/index.ts:
--------------------------------------------------------------------------------
1 | export * from './card.component';
2 | export * from './page-transclusion.component';
--------------------------------------------------------------------------------
/src/app/transclusion/page-transclusion.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Transclusion
3 |
4 |
5 |
6 |
7 |
You can put any content here
8 |
For example this line of text and
9 |
This button
10 |
11 |
12 |
13 |
16 |
17 |
18 |
21 |
22 |
23 |
26 |
27 |
28 |
31 |
32 |
33 |
36 |
37 |
38 |
41 |
42 |
43 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/app/transclusion/page-transclusion.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'page-transclusion',
5 | templateUrl: './page-transclusion.component.html'
6 | })
7 | export class PageTransclusionComponent {
8 | }
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jecfish/ng-musing/e92d8f94b1856940ea594883e50f220b6f163003/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/angular-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jecfish/ng-musing/e92d8f94b1856940ea594883e50f220b6f163003/src/assets/angular-2.jpg
--------------------------------------------------------------------------------
/src/assets/articles.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Create a Globally Available Custom Pipe in Angular",
4 | "url": "https://scotch.io/?p=13431",
5 | "demo": "global-pipe",
6 | "desc": "Understanding, creating and using pipe."
7 | },
8 | {
9 | "name": "How to Deal with Different Form Controls in Angular",
10 | "url": "https://scotch.io/?p=13444",
11 | "demo": "diff-form-ctrls",
12 | "desc": "Dealing with different form controls in template-driven form."
13 | },
14 | {
15 | "name": "Using Angular's Template-Driven Forms",
16 | "url": "https://scotch.io/?p=13446",
17 | "demo": "template-driven-frm",
18 | "desc": "What is template-driven form and how to use it."
19 | },
20 | {
21 | "name": "Using Angular's Model-Driven Forms",
22 | "url": "https://scotch.io/?p=13438",
23 | "demo": "model-driven-frm",
24 | "desc": "What is model-driven form and how to use it."
25 | },
26 | {
27 | "name": "How to Build Nested Model-driven Forms in Angular",
28 | "url": "https://scotch.io/?p=13483",
29 | "demo": "nested-model-driven-frm",
30 | "desc": "Handle validatiion and binding in nested model driven form."
31 | },
32 | {
33 | "name": "How to Implement Conditional Validation in Angular Model-driven Forms",
34 | "url": "https://scotch.io/?p=13497",
35 | "demo": "conditional-val-model-driven-frm",
36 | "desc": "Handling conditional validation in Angular Model-driven form."
37 | },
38 | {
39 | "name": "Simple Language Translation in Angular (Part 1)",
40 | "url": "https://scotch.io/?p=13499",
41 | "demo": "simple-language-translation-part-1",
42 | "desc": "A two parts tutorial. Part 1. Implementing translation."
43 | },
44 | {
45 | "name": "Simple Language Translation in Angular (Part 2)",
46 | "url": "https://scotch.io/?p=13501",
47 | "demo": "simple-language-translation-part-2",
48 | "desc": "A two parts tutorial. Part 2. Implementing translation."
49 | },
50 | {
51 | "name": "Using HammerJS (Touch gesture) in Angular",
52 | "url": "https://scotch.io/?p=13550",
53 | "demo": "hammerjs",
54 | "desc": "Swipe left, swipe right in Angular."
55 | },
56 | {
57 | "name": "How to Implement a Custom Validator Directive (Confirm Password) in Angular",
58 | "url": "https://scotch.io/?p=13552",
59 | "demo": "custom-validator",
60 | "desc": "Create a validator to handle confirm password."
61 | },
62 | {
63 | "name": "Getting to Know Angular's Module: @NgModule",
64 | "url": "https://scotch.io/bar-talk/getting-to-know-angular-2s-module-ngmodule",
65 | "demo": "",
66 | "desc": "Understanding Angular Module"
67 | },
68 | {
69 | "name": "Angular Transclusion using ng-content",
70 | "url": "https://scotch.io/tutorials/angular-2-transclusion-using",
71 | "demo": "transclusion",
72 | "desc": "What is transclusion. Handling content projection in Angular."
73 | },
74 | {
75 | "name": "Component Inheritance in Angular",
76 | "url": "https://scotch.io/tutorials/component-inheritance-in-angular-2",
77 | "demo": "component-inheritance",
78 | "desc": "Component is inheritable. Explaining component inheritance with a simple pagination example."
79 | },
80 | {
81 | "name": "Dynamic Form Array Validation in Model Driven Form",
82 | "url": "",
83 | "demo": "dynamic-form-array-val",
84 | "desc": "Handle form array validation dynamically and reactively."
85 | },
86 | {
87 | "name": "Responsive Equal Height with Angular Directive",
88 | "url": "https://scotch.io/tutorials/responsive-equal-height-with-angular-directive",
89 | "demo": "same-height",
90 | "desc": "Making element same height."
91 | },
92 | {
93 | "name": "3 ways to Pass Async Data to Angular Child Component ",
94 | "url": "https://scotch.io/tutorials/3-ways-to-pass-async-data-to-angular-child-component",
95 | "demo": "three-ways",
96 | "desc": "Three ways"
97 | },
98 | {
99 | "name": "How to Handle File Upload in Angular",
100 | "url": "https://scotch.io/tutorials/how-to-handle-file-upload-in-angular",
101 | "demo": "file-upload",
102 | "desc": "Handle file upload in Angular"
103 | }
104 | ]
--------------------------------------------------------------------------------
/src/assets/chart-a.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Jenna",
4 | "mode": "happy"
5 | },
6 | {
7 | "name": "Jenny",
8 | "mode": "angry"
9 | },
10 | {
11 | "name": "Larica",
12 | "mode": "happy"
13 | },
14 | {
15 | "name": "Lorence",
16 | "mode": "happy"
17 | },
18 | {
19 | "name": "Alaric",
20 | "mode": "happy"
21 | },
22 | {
23 | "name": "Maria",
24 | "mode": "happy"
25 | },
26 | {
27 | "name": "Gunner",
28 | "mode": "angry"
29 | },
30 | {
31 | "name": "Henry",
32 | "mode": "happy"
33 | },
34 | {
35 | "name": "Chris",
36 | "mode": "happy"
37 | },
38 | {
39 | "name": "Jean",
40 | "mode": "happy"
41 | },
42 | {
43 | "name": "Christine",
44 | "mode": "happy"
45 | },
46 | {
47 | "name": "Chritina",
48 | "mode": "happy"
49 | },
50 | {
51 | "name": "Fenny",
52 | "mode": "happy"
53 | },
54 | {
55 | "name": "Rainie",
56 | "mode": "happy"
57 | },
58 | {
59 | "name": "Rene",
60 | "mode": "happy"
61 | },
62 | {
63 | "name": "Wendy",
64 | "mode": "happy"
65 | },
66 | {
67 | "name": "Lydia",
68 | "mode": "happy"
69 | },
70 | {
71 | "name": "Dean",
72 | "mode": "happy"
73 | },
74 | {
75 | "name": "Darren",
76 | "mode": "happy"
77 | },
78 | {
79 | "name": "Adrian",
80 | "mode": "happy"
81 | },
82 | {
83 | "name": "Nick",
84 | "mode": "happy"
85 | },
86 | {
87 | "name": "Nicholas",
88 | "mode": "happy"
89 | },
90 | {
91 | "name": "Shawn",
92 | "mode": "happy"
93 | },
94 | {
95 | "name": "Mark",
96 | "mode": "sad"
97 | },
98 | {
99 | "name": "Russell",
100 | "mode": "happy"
101 | },
102 | {
103 | "name": "Jennifer",
104 | "mode": "sad"
105 | },
106 | {
107 | "name": "Sarah",
108 | "mode": "sad"
109 | },
110 | {
111 | "name": "Vanessa",
112 | "mode": "sad"
113 | },
114 | {
115 | "name": "Rihanna",
116 | "mode": "sad"
117 | },
118 | {
119 | "name": "Taylor",
120 | "mode": "wow"
121 | },
122 | {
123 | "name": "Yasmine",
124 | "mode": "wow"
125 | },
126 | {
127 | "name": "Jasmine",
128 | "mode": "wow"
129 | },
130 | {
131 | "name": "Hannah",
132 | "mode": "happy"
133 | },
134 | {
135 | "name": "Jimmy",
136 | "mode": "happy"
137 | },
138 | {
139 | "name": "Una",
140 | "mode": "happy"
141 | },
142 | {
143 | "name": "Yuna",
144 | "mode": "happy"
145 | },
146 | {
147 | "name": "Cathy",
148 | "mode": "wow"
149 | },
150 | {
151 | "name": "Cat",
152 | "mode": "wow"
153 | },
154 | {
155 | "name": "Zara",
156 | "mode": "happy"
157 | },
158 | {
159 | "name": "Dylan",
160 | "mode": "angry"
161 | },
162 | {
163 | "name": "Bonnie",
164 | "mode": "angry"
165 | },
166 | {
167 | "name": "Bunny",
168 | "mode": "angry"
169 | },
170 | {
171 | "name": "Nancy",
172 | "mode": "angry"
173 | },
174 | {
175 | "name": "Mango",
176 | "mode": "angry"
177 | },
178 | {
179 | "name": "Mandy",
180 | "mode": "angry"
181 | },
182 | {
183 | "name": "Amanda",
184 | "mode": "angry"
185 | },
186 | {
187 | "name": "Vannie",
188 | "mode": "wow"
189 | },
190 | {
191 | "name": "Claire",
192 | "mode": "angry"
193 | },
194 | {
195 | "name": "Dexter",
196 | "mode": "angry"
197 | },
198 | {
199 | "name": "Rainbow",
200 | "mode": "angry"
201 | },
202 | {
203 | "name": "Penny",
204 | "mode": "angry"
205 | },
206 | {
207 | "name": "Peery",
208 | "mode": "angry"
209 | }
210 | ]
211 |
--------------------------------------------------------------------------------
/src/assets/chart-x.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Jenna",
4 | "group": "gold",
5 | "balance": 42.3,
6 | "owe": 33.2
7 | },
8 | {
9 | "name": "Jenny",
10 | "group": "gold",
11 | "balance": 48.6,
12 | "owe": 39.2
13 | },
14 | {
15 | "name": "Larica",
16 | "group": "gold",
17 | "balance": 100,
18 | "owe": 23.1
19 | },
20 | {
21 | "name": "Lorence",
22 | "group": "gold",
23 | "balance": 1222.3,
24 | "owe": 303.2
25 | },
26 | {
27 | "name": "Alaric",
28 | "group": "gold",
29 | "balance": 424.3,
30 | "owe": 133.2
31 | },
32 | {
33 | "name": "Maria",
34 | "group": "gold",
35 | "balance": 462.45,
36 | "owe": 733.2
37 | },
38 | {
39 | "name": "Gunner",
40 | "group": "gold",
41 | "balance": 425.3,
42 | "owe": 334.2
43 | },
44 | {
45 | "name": "Henry",
46 | "group": "gold",
47 | "balance": 842.6,
48 | "owe": 283.2
49 | },
50 | {
51 | "name": "Chris",
52 | "group": "gold",
53 | "balance": 212.3,
54 | "owe": 58.2
55 | },
56 | {
57 | "name": "Jean",
58 | "group": "gold",
59 | "balance": 432.3,
60 | "owe": 349.2
61 | },
62 | {
63 | "name": "Christine",
64 | "group": "platinum",
65 | "balance": 17.8,
66 | "owe": 90.5
67 | },
68 | {
69 | "name": "Chritina",
70 | "group": "platinum",
71 | "balance": 175.8,
72 | "owe": 940.5
73 | },
74 | {
75 | "name": "Fenny",
76 | "group": "platinum",
77 | "balance": 217.8,
78 | "owe": 690.5
79 | },
80 | {
81 | "name": "Rainie",
82 | "group": "platinum",
83 | "balance": 417.8,
84 | "owe": 92.5
85 | },
86 | {
87 | "name": "Rene",
88 | "group": "platinum",
89 | "balance": 970.8,
90 | "owe": 390.5
91 | },
92 | {
93 | "name": "Wendy",
94 | "group": "platinum",
95 | "balance": 117.8,
96 | "owe": 98.5
97 | },
98 | {
99 | "name": "Lydia",
100 | "group": "platinum",
101 | "balance": 317.8,
102 | "owe": 94.5
103 | },
104 | {
105 | "name": "Dean",
106 | "group": "platinum",
107 | "balance": 147.8,
108 | "owe": 120.5
109 | },
110 | {
111 | "name": "Darren",
112 | "group": "silver",
113 | "balance": 37.6,
114 | "owe": 25.7
115 | },
116 | {
117 | "name": "Adrian",
118 | "group": "silver",
119 | "balance": 317.6,
120 | "owe": 125.7
121 | },
122 | {
123 | "name": "Nick",
124 | "group": "silver",
125 | "balance": 3137.6,
126 | "owe": 245.7
127 | },
128 | {
129 | "name": "Nicholas",
130 | "group": "silver",
131 | "balance": 837.6,
132 | "owe": 257.7
133 | },
134 | {
135 | "name": "Shawn",
136 | "group": "silver",
137 | "balance": 437.6,
138 | "owe": 325.7
139 | },
140 | {
141 | "name": "Mark",
142 | "group": "silver",
143 | "balance": 397.6,
144 | "owe": 26.5
145 | },
146 | {
147 | "name": "Russell",
148 | "group": "silver",
149 | "balance": 537.6,
150 | "owe": 215.7
151 | },
152 | {
153 | "name": "Jennifer",
154 | "group": "silver",
155 | "balance": 37.6,
156 | "owe": 123.6
157 | },
158 | {
159 | "name": "Sarah",
160 | "group": "silver",
161 | "balance": 537.6,
162 | "owe": 115.7
163 | },
164 | {
165 | "name": "Vanessa",
166 | "group": "silver",
167 | "balance": 437.6,
168 | "owe": 15.7
169 | },
170 | {
171 | "name": "Rihanna",
172 | "group": "silver",
173 | "balance": 937.6,
174 | "owe": 425.7
175 | },
176 | {
177 | "name": "Taylor",
178 | "group": "silver",
179 | "balance": 387.6,
180 | "owe": 255.7
181 | },
182 | {
183 | "name": "Yasmine",
184 | "group": "silver",
185 | "balance": 837.6,
186 | "owe": 250.7
187 | },
188 | {
189 | "name": "Jasmine",
190 | "group": "silver",
191 | "balance": 307.6,
192 | "owe": 205.7
193 | },
194 | {
195 | "name": "Hannah",
196 | "group": "silver",
197 | "balance": 2137.6,
198 | "owe": 415.7
199 | },
200 | {
201 | "name": "Jimmy",
202 | "group": "gold",
203 | "balance": 24.1,
204 | "owe": 13.6
205 | },
206 | {
207 | "name": "Una",
208 | "group": "gold",
209 | "balance": 224.1,
210 | "owe": 18.6
211 | },
212 | {
213 | "name": "Yuna",
214 | "group": "gold",
215 | "balance": 324.1,
216 | "owe": 123.6
217 | },
218 | {
219 | "name": "Cathy",
220 | "group": "gold",
221 | "balance": 924.1,
222 | "owe": 138.6
223 | },
224 | {
225 | "name": "Cat",
226 | "group": "gold",
227 | "balance": 249.1,
228 | "owe": 137.8
229 | },
230 | {
231 | "name": "Zara",
232 | "group": "gold",
233 | "balance": 724.1,
234 | "owe": 1389.6
235 | },
236 | {
237 | "name": "Dylan",
238 | "group": "gold",
239 | "balance": 1324.1,
240 | "owe": 43.6
241 | },
242 | {
243 | "name": "Bonnie",
244 | "group": "gold",
245 | "balance": 244.1,
246 | "owe": 3.6
247 | },
248 | {
249 | "name": "Bunny",
250 | "group": "gold",
251 | "balance": 23.1,
252 | "owe": 18.6
253 | },
254 | {
255 | "name": "Nancy",
256 | "group": "gold",
257 | "balance": 264.1,
258 | "owe": 134.6
259 | },
260 | {
261 | "name": "Mango",
262 | "group": "gold",
263 | "balance": 240.1,
264 | "owe": 103.8
265 | },
266 | {
267 | "name": "Mandy",
268 | "group": "platinum",
269 | "balance": 80.1,
270 | "owe": 17.3
271 | },
272 | {
273 | "name": "Amanda",
274 | "group": "platinum",
275 | "balance": 180.1,
276 | "owe": 174.3
277 | },
278 | {
279 | "name": "Vannie",
280 | "group": "platinum",
281 | "balance": 830.1,
282 | "owe": 17.4
283 | },
284 | {
285 | "name": "Claire",
286 | "group": "platinum",
287 | "balance": 809.1,
288 | "owe": 517.3
289 | },
290 | {
291 | "name": "Dexter",
292 | "group": "platinum",
293 | "balance": 83.1,
294 | "owe": 97.3
295 | },
296 | {
297 | "name": "Rainbow",
298 | "group": "platinum",
299 | "balance": 180.1,
300 | "owe": 175.3
301 | },
302 | {
303 | "name": "Penny",
304 | "group": "platinum",
305 | "balance": 182.1,
306 | "owe": 16.3
307 | },
308 | {
309 | "name": "Perry",
310 | "group": "platinum",
311 | "balance": 3480.1,
312 | "owe": 1117.3
313 | }
314 | ]
315 |
--------------------------------------------------------------------------------
/src/assets/mock-posts.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "Learn Angular",
4 | "category": "tech"
5 | },
6 | {
7 | "title": "Forrest Gump Reviews",
8 | "category": "movie"
9 | },
10 | {
11 | "title": "Yoga Meditation",
12 | "category": "lifestyle"
13 | },
14 | {
15 | "title": "What is Promises?",
16 | "category": "tech"
17 | },
18 | {
19 | "title": "Star Wars Reviews",
20 | "category": "movie"
21 | },
22 | {
23 | "title": "Diving in Komodo",
24 | "category": "lifestyle"
25 | }
26 | ]
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jecfish/ng-musing/e92d8f94b1856940ea594883e50f220b6f163003/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ng musing
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Loading...
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.ts';
2 |
3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4 | import { enableProdMode } from '@angular/core';
5 | import { environment } from './environments/environment';
6 | import { AppModule } from './app/';
7 |
8 | if (environment.production) {
9 | enableProdMode();
10 | }
11 |
12 | platformBrowserDynamic().bootstrapModule(AppModule);
13 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | // This file includes polyfills needed by Angular 2 and is loaded before
2 | // the app. You can add your own extra polyfills to this file.
3 | import 'core-js/es6/symbol';
4 | import 'core-js/es6/object';
5 | import 'core-js/es6/function';
6 | import 'core-js/es6/parse-int';
7 | import 'core-js/es6/parse-float';
8 | import 'core-js/es6/number';
9 | import 'core-js/es6/math';
10 | import 'core-js/es6/string';
11 | import 'core-js/es6/date';
12 | import 'core-js/es6/array';
13 | import 'core-js/es6/regexp';
14 | import 'core-js/es6/map';
15 | import 'core-js/es6/set';
16 | import 'core-js/es6/reflect';
17 |
18 | import 'core-js/es7/reflect';
19 | import 'zone.js/dist/zone';
20 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | .margin-20 {
3 | margin-top: 20px;
4 | }
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.ts';
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare var __karma__: any;
17 | declare var require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | let context = require.context('./', true, /\.spec\.ts/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "",
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "lib": ["es6", "dom"],
8 | "mapRoot": "./",
9 | "module": "es6",
10 | "moduleResolution": "node",
11 | "outDir": "../dist/out-tsc",
12 | "sourceMap": true,
13 | "target": "es5",
14 | "typeRoots": [
15 | "../node_modules/@types"
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | // Typings reference file, you can add your own global typings here
2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
3 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "class-name": true,
7 | "comment-format": [
8 | true,
9 | "check-space"
10 | ],
11 | "curly": true,
12 | "eofline": true,
13 | "forin": true,
14 | "indent": [
15 | true,
16 | "spaces"
17 | ],
18 | "label-position": true,
19 | "max-line-length": [
20 | true,
21 | 140
22 | ],
23 | "member-access": false,
24 | "member-ordering": [
25 | true,
26 | "static-before-instance",
27 | "variables-before-functions"
28 | ],
29 | "no-arg": true,
30 | "no-bitwise": true,
31 | "no-console": [
32 | true,
33 | "debug",
34 | "info",
35 | "time",
36 | "timeEnd",
37 | "trace"
38 | ],
39 | "no-construct": true,
40 | "no-debugger": true,
41 | "no-duplicate-variable": true,
42 | "no-empty": false,
43 | "no-eval": true,
44 | "no-inferrable-types": true,
45 | "no-shadowed-variable": true,
46 | "no-string-literal": false,
47 | "no-switch-case-fall-through": true,
48 | "no-trailing-whitespace": true,
49 | "no-unused-expression": true,
50 | "no-use-before-declare": true,
51 | "no-var-keyword": true,
52 | "object-literal-sort-keys": false,
53 | "one-line": [
54 | true,
55 | "check-open-brace",
56 | "check-catch",
57 | "check-else",
58 | "check-whitespace"
59 | ],
60 | "quotemark": [
61 | true,
62 | "single"
63 | ],
64 | "radix": true,
65 | "semicolon": [
66 | "always"
67 | ],
68 | "triple-equals": [
69 | true,
70 | "allow-null-check"
71 | ],
72 | "typedef-whitespace": [
73 | true,
74 | {
75 | "call-signature": "nospace",
76 | "index-signature": "nospace",
77 | "parameter": "nospace",
78 | "property-declaration": "nospace",
79 | "variable-declaration": "nospace"
80 | }
81 | ],
82 | "variable-name": false,
83 | "whitespace": [
84 | true,
85 | "check-branch",
86 | "check-decl",
87 | "check-operator",
88 | "check-separator",
89 | "check-type"
90 | ],
91 |
92 | "directive-selector": [true, "attribute", "app", "camelCase"],
93 | "component-selector": [true, "element", "app", "kebab-case"],
94 | "use-input-property-decorator": true,
95 | "use-output-property-decorator": true,
96 | "use-host-property-decorator": true,
97 | "no-input-rename": true,
98 | "no-output-rename": true,
99 | "use-life-cycle-interface": true,
100 | "use-pipe-transform-interface": true,
101 | "component-class-suffix": true,
102 | "directive-class-suffix": true,
103 | "no-access-missing-member": true,
104 | "templates-use-public": true,
105 | "invoke-injectable": true
106 | }
107 | }
108 |
--------------------------------------------------------------------------------