├── .angular-cli.json ├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── about │ │ ├── about-routing.module.ts │ │ ├── about.component.css │ │ ├── about.component.html │ │ ├── about.component.spec.ts │ │ ├── about.component.ts │ │ └── about.module.ts │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.config.ts │ ├── app.module.ts │ ├── core │ │ ├── api.service.spec.ts │ │ ├── api.service.ts │ │ ├── auth.guard.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.service.ts │ │ ├── core.module.ts │ │ ├── index.ts │ │ ├── jwt.ts │ │ └── user.model.ts │ ├── home │ │ ├── home-routing.module.ts │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.spec.ts │ │ ├── home.component.ts │ │ └── home.module.ts │ ├── index.ts │ ├── layout │ │ ├── footer.component.css │ │ ├── footer.component.html │ │ ├── footer.component.spec.ts │ │ ├── footer.component.ts │ │ ├── layout.module.ts │ │ ├── navbar.component.css │ │ ├── navbar.component.html │ │ ├── navbar.component.spec.ts │ │ └── navbar.component.ts │ ├── posts │ │ ├── edit-post │ │ │ ├── edit-post.component.css │ │ │ ├── edit-post.component.html │ │ │ ├── edit-post.component.spec.ts │ │ │ └── edit-post.component.ts │ │ ├── home │ │ │ ├── home.component.css │ │ │ ├── home.component.html │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ │ ├── new-post │ │ │ ├── new-post.component.css │ │ │ ├── new-post.component.html │ │ │ ├── new-post.component.spec.ts │ │ │ └── new-post.component.ts │ │ ├── post-details │ │ │ ├── comment │ │ │ │ ├── comment-form.component.css │ │ │ │ ├── comment-form.component.html │ │ │ │ ├── comment-form.component.spec.ts │ │ │ │ ├── comment-form.component.ts │ │ │ │ ├── comment-list-item.component.css │ │ │ │ ├── comment-list-item.component.html │ │ │ │ ├── comment-list-item.component.spec.ts │ │ │ │ ├── comment-list-item.component.ts │ │ │ │ ├── comment-list.component.css │ │ │ │ ├── comment-list.component.html │ │ │ │ ├── comment-list.component.spec.ts │ │ │ │ ├── comment-list.component.ts │ │ │ │ ├── comment-panel.component.css │ │ │ │ ├── comment-panel.component.html │ │ │ │ ├── comment-panel.component.spec.ts │ │ │ │ └── comment-panel.component.ts │ │ │ ├── post-details-panel │ │ │ │ ├── post-details-panel.component.css │ │ │ │ ├── post-details-panel.component.html │ │ │ │ ├── post-details-panel.component.spec.ts │ │ │ │ └── post-details-panel.component.ts │ │ │ ├── post-details.component.css │ │ │ ├── post-details.component.html │ │ │ ├── post-details.component.spec.ts │ │ │ └── post-details.component.ts │ │ ├── posts-routing.module.ts │ │ ├── posts.module.ts │ │ └── shared │ │ │ ├── comment.model.ts │ │ │ ├── post-form │ │ │ ├── post-form.component.css │ │ │ ├── post-form.component.html │ │ │ ├── post-form.component.spec.ts │ │ │ └── post-form.component.ts │ │ │ ├── post.model.ts │ │ │ ├── post.service.spec.ts │ │ │ └── post.service.ts │ ├── shared │ │ ├── email-validator.directive.spec.ts │ │ ├── email-validator.directive.ts │ │ ├── index.ts │ │ ├── nl2br.pipe.spec.ts │ │ ├── nl2br.pipe.ts │ │ ├── shared.module.ts │ │ ├── show-authed.directive.spec.ts │ │ └── show-authed.directive.ts │ ├── signin │ │ ├── signin-routing.module.ts │ │ ├── signin.component.css │ │ ├── signin.component.html │ │ ├── signin.component.spec.ts │ │ ├── signin.component.ts │ │ └── signin.module.ts │ └── signup │ │ ├── signup-routing.module.ts │ │ ├── signup.component.css │ │ ├── signup.component.html │ │ ├── signup.component.spec.ts │ │ ├── signup.component.ts │ │ └── signup.module.ts ├── assets │ ├── .gitkeep │ ├── .npmignore │ └── i18n │ │ ├── en.json │ │ └── zh.json ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── forms.css ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig.json └── tslint.json /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "ng2-hello" 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 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "../node_modules/bootstrap/dist/css/bootstrap.min.css", 23 | "styles.css", 24 | "forms.css" 25 | ], 26 | "scripts": [ 27 | "../node_modules/jquery/dist/jquery.slim.js", 28 | "../node_modules/popper.js/dist/umd/popper.js", 29 | "../node_modules/bootstrap/dist/js/bootstrap.js" 30 | ], 31 | "environmentSource": "environments/environment.ts", 32 | "environments": { 33 | "dev": "environments/environment.ts", 34 | "prod": "environments/environment.prod.ts" 35 | } 36 | } 37 | ], 38 | "e2e": { 39 | "protractor": { 40 | "config": "./protractor.conf.js" 41 | } 42 | }, 43 | "lint": [ 44 | { 45 | "project": "src/tsconfig.app.json" 46 | }, 47 | { 48 | "project": "src/tsconfig.spec.json" 49 | }, 50 | { 51 | "project": "e2e/tsconfig.e2e.json" 52 | } 53 | ], 54 | "test": { 55 | "karma": { 56 | "config": "./karma.conf.js" 57 | } 58 | }, 59 | "defaults": { 60 | "styleExt": "css", 61 | "component": {} 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.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 = 0 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 | .project 14 | .classpath 15 | *.launch 16 | .settings/ 17 | 18 | # misc 19 | .sass-cache 20 | connect.lock 21 | coverage/* 22 | libpeerconnection.log 23 | npm-debug.log 24 | testem.log 25 | typings 26 | 27 | # e2e 28 | e2e/*.js 29 | e2e/*.map 30 | 31 | #System Files 32 | .DS_Store 33 | Thumbs.db 34 | 35 | coverage 36 | *-backup 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.check.workspaceVersion": true, 3 | "typescript.tsdk": "./node_modules/typescript/lib", 4 | "angulardoc.repoId": "55f96c5e-38f4-4583-91c6-45e5eba57827", 5 | "angulardoc.lastSync": 0, 6 | "vsicons.presets.angular": true 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular2 Sample 2 | 3 | The sample codes are based on a seriese of my blog entries, please see the [wiki pages](https://github.com/hantsy/angular2-sample/wiki) for more details. 4 | 5 | * [Getting started](https://github.com/hantsy/angular2-sample/wiki/2-component) 6 | * [Interact with backend APIs](https://github.com/hantsy/angular2-sample/wiki/3-http) 7 | * [Handles form submission](https://github.com/hantsy/angular2-sample/wiki/4-form) 8 | * [Jwt token based Authentication](https://github.com/hantsy/angular2-sample/wiki/5-auth) 9 | * [Testing](https://github.com/hantsy/angular2-sample/wiki/6-testing) TODO 10 | 11 | ## Backend APIs 12 | 13 | Check the [Interact with Backend Apis](https://github.com/hantsy/angular2-sample/wiki/3-http) to run the backend APIs for test purpose. 14 | 15 | 16 | ## Frontend UI 17 | 18 | Make sure you have installed the latest NodeJS and NPM. 19 | 20 | Run the following command to and run the project. 21 | 22 | ``` 23 | npm run start 24 | ``` 25 | 26 | Some variants for Material Design and AngularFire2. 27 | 28 | * [Angular Material2 sample codes](https://github.com/hantsy/angular2-material-sample) 29 | * [AngularFire2/Firebase sample codes](https://github.com/hantsy/angular2-firebase-sample) 30 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Angular2SamplePage } from './app.po'; 2 | 3 | describe('angular2-sample App', function() { 4 | let page: Angular2SamplePage; 5 | 6 | beforeEach(() => { 7 | page = new Angular2SamplePage(); 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 Angular2SamplePage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types":[ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /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-jasmine-html-reporter'), 12 | require('karma-mocha-reporter'), 13 | require('karma-coverage-istanbul-reporter'), 14 | require('@angular/cli/plugins/karma') 15 | ], 16 | client:{ 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | files: [ 20 | { pattern: './src/test.ts', watched: false } 21 | ], 22 | preprocessors: { 23 | './src/test.ts': ['@angular/cli'] 24 | }, 25 | mime: { 26 | 'text/x-typescript': ['ts','tsx'] 27 | }, 28 | coverageIstanbulReporter: { 29 | reports: [ 'html', 'lcovonly' ], 30 | fixWebpackSourcePaths: true 31 | }, 32 | angularCli: { 33 | environment: 'dev' 34 | }, 35 | reporters: config.angularCli && config.angularCli.codeCoverage 36 | ? ['mocha', 'coverage-istanbul'] 37 | : ['mocha', 'kjhtml'], 38 | port: 9876, 39 | colors: true, 40 | logLevel: config.LOG_INFO, 41 | autoWatch: true, 42 | browsers: ['Chrome'], 43 | singleRun: false 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-sample", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^4.1.0", 17 | "@angular/common": "^4.1.0", 18 | "@angular/compiler": "^4.1.0", 19 | "@angular/core": "^4.1.0", 20 | "@angular/forms": "^4.1.0", 21 | "@angular/http": "^4.1.0", 22 | "@angular/platform-browser": "^4.1.0", 23 | "@angular/platform-browser-dynamic": "^4.1.0", 24 | "@angular/router": "^4.1.0", 25 | "@ngx-translate/core": "^6.0.1", 26 | "@ngx-translate/http-loader": "0.0.3", 27 | "bootstrap": "^4.0.0-beta", 28 | "core-js": "^2.4.1", 29 | "jquery": "^3.2.1", 30 | "popper.js": "^1.12.5", 31 | "rxjs": "^5.1.0", 32 | "zone.js": "^0.8.4" 33 | }, 34 | "devDependencies": { 35 | "@angular/cli": "^1.0.1", 36 | "@angular/compiler-cli": "^4.1.0", 37 | "@types/jasmine": "2.5.38", 38 | "@types/node": "~6.0.60", 39 | "codelyzer": "~2.0.0", 40 | "jasmine-core": "~2.5.2", 41 | "jasmine-spec-reporter": "~3.2.0", 42 | "karma": "~1.4.1", 43 | "karma-chrome-launcher": "~2.0.0", 44 | "karma-cli": "~1.0.1", 45 | "karma-coverage-istanbul-reporter": "^0.2.0", 46 | "karma-jasmine": "~1.1.0", 47 | "karma-jasmine-html-reporter": "^0.2.2", 48 | "karma-mocha-reporter": "^2.2.0", 49 | "protractor": "~5.1.0", 50 | "ts-node": "~2.0.0", 51 | "tslint": "~4.5.0", 52 | "typescript": "~2.2.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | beforeLaunch: function() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | }, 27 | onPrepare() { 28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/app/about/about-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { AboutComponent } from './about.component'; 5 | 6 | const routes: Routes = [ 7 | { path: 'about', component: AboutComponent } 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forChild(routes)], 12 | exports: [RouterModule], 13 | providers: [] 14 | }) 15 | export class AboutRoutingModule { } 16 | -------------------------------------------------------------------------------- /src/app/about/about.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/about/about.component.css -------------------------------------------------------------------------------- /src/app/about/about.component.html: -------------------------------------------------------------------------------- 1 |

2 | about works! 3 |

4 | -------------------------------------------------------------------------------- /src/app/about/about.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AboutComponent } from './about.component'; 5 | 6 | describe('Component: About', () => { 7 | it('should create an instance', () => { 8 | let component = new AboutComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-about', 5 | templateUrl: './about.component.html', 6 | styleUrls: ['./about.component.css'] 7 | }) 8 | export class AboutComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/about/about.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { AboutRoutingModule } from './about-routing.module'; 4 | import { AboutComponent } from './about.component'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | CommonModule, 9 | AboutRoutingModule 10 | ], 11 | declarations: [AboutComponent] 12 | }) 13 | export class AboutModule { } 14 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { path: '', redirectTo: '/home', pathMatch: 'full' }, 6 | { path: 'signin', loadChildren: 'app/signin/signin.module#SigninModule' }, 7 | { path: 'signup', loadChildren: 'app/signup/signup.module#SignupModule' }, 8 | { path: 'posts', loadChildren: 'app/posts/posts.module#PostsModule' }, 9 | // { path: '**', component:PageNotFoundComponent} 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forRoot(routes)], 14 | exports: [RouterModule], 15 | }) 16 | export class AppRoutingModule { } 17 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer styles 2 | -------------------------------------------------- */ 3 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |
7 |
8 | 9 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | 3 | import { AppComponent } from './app.component'; 4 | 5 | xdescribe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | declarations: [ 9 | AppComponent 10 | ], 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the app', async(() => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | })); 19 | 20 | it(`should have as title 'app works!'`, async(() => { 21 | const fixture = TestBed.createComponent(AppComponent); 22 | const app = fixture.debugElement.componentInstance; 23 | expect(app.title).toEqual('app works!'); 24 | })); 25 | 26 | it('should render title in a h1 tag', async(() => { 27 | const fixture = TestBed.createComponent(AppComponent); 28 | fixture.detectChanges(); 29 | const compiled = fixture.debugElement.nativeElement; 30 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 31 | })); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { TranslateService } from '@ngx-translate/core'; 3 | import { AuthService } from './core'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent implements OnInit { 11 | title = 'app works!'; 12 | 13 | constructor(private translate: TranslateService, private authService: AuthService) { 14 | // this language will be used as a fallback when a translation isn't found in the current language 15 | this.translate.setDefaultLang('en'); 16 | 17 | // the lang to use, if the lang isn't available, it will use the current loader to get them 18 | // let browserLang = translate.getBrowserLang(); 19 | // translate.use(browserLang.match(/en|zh/) ? browserLang : 'en'); 20 | 21 | this.translate.use('en'); 22 | 23 | console.log('posts of lang:' + this.translate.instant('posts')); 24 | console.log('posts nonexist of lang:' + this.translate.instant('posts-nonexist')); 25 | } 26 | 27 | ngOnInit() { 28 | this.authService.verifyAuth(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { OpaqueToken } from '@angular/core'; 2 | 3 | export interface AppConfig { 4 | jwtKey?: string; 5 | apiEndpoint?: string; 6 | } 7 | 8 | export const DEFAULT_APP_CONFIG: AppConfig = { 9 | jwtKey: 'id_token', 10 | apiEndpoint: 'http://localhost:8080/blog-api-cdi/api' 11 | }; 12 | 13 | export let APP_CONFIG = new OpaqueToken('app.config'); 14 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { NgModule } from '@angular/core'; 4 | import { Http } from '@angular/http'; 5 | import { AppRoutingModule } from './app-routing.module'; 6 | 7 | import { APP_CONFIG, DEFAULT_APP_CONFIG } from './app.config'; 8 | import { AppComponent } from './app.component'; 9 | import { LayoutModule } from './layout/layout.module'; 10 | import { CoreModule } from './core/core.module'; 11 | import { HomeModule } from './home/home.module'; 12 | import { AboutModule } from './about/about.module'; 13 | 14 | import { Observable } from 'rxjs/Observable'; 15 | import { 16 | TranslateModule, 17 | TranslateLoader, 18 | MissingTranslationHandler, 19 | MissingTranslationHandlerParams 20 | } from '@ngx-translate/core'; 21 | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; 22 | 23 | export function createTranslateLoader(http: Http) { 24 | return new TranslateHttpLoader(http, './assets/i18n/', '.json'); 25 | } 26 | 27 | export class MyMissingTranslationHandler implements MissingTranslationHandler { 28 | handle(params: MissingTranslationHandlerParams) { 29 | return '[' + params.key + ']'; 30 | } 31 | } 32 | 33 | @NgModule({ 34 | imports: [ 35 | BrowserModule, 36 | BrowserAnimationsModule, 37 | LayoutModule, 38 | TranslateModule.forRoot({ 39 | loader: { 40 | provide: TranslateLoader, 41 | useFactory: (createTranslateLoader), 42 | deps: [Http] 43 | } 44 | }), 45 | CoreModule, 46 | AppRoutingModule, 47 | HomeModule, 48 | AboutModule 49 | ], 50 | exports: [], 51 | providers: [ 52 | { 53 | provide: APP_CONFIG, 54 | useValue: DEFAULT_APP_CONFIG 55 | }, 56 | { 57 | provide: MissingTranslationHandler, 58 | useClass: MyMissingTranslationHandler 59 | } 60 | ], 61 | declarations: [ 62 | AppComponent 63 | ], 64 | bootstrap: [AppComponent] 65 | }) 66 | export class AppModule { } 67 | -------------------------------------------------------------------------------- /src/app/core/api.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { HttpModule, BaseRequestOptions, Http, Response, ResponseOptions } from '@angular/http'; 5 | import { MockBackend } from '@angular/http/testing'; 6 | 7 | import { APP_CONFIG, DEFAULT_APP_CONFIG } from '../app.config'; 8 | import { ApiService } from './api.service'; 9 | 10 | describe('Service: Api', () => { 11 | 12 | let apiService: ApiService; 13 | let mockBackend: MockBackend; 14 | 15 | beforeEach(() => { 16 | TestBed.configureTestingModule({ 17 | imports: [HttpModule], 18 | providers: [ 19 | { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG }, 20 | BaseRequestOptions, 21 | MockBackend, 22 | { 23 | provide: Http, 24 | useFactory: (backend, options) => new Http(backend, options), 25 | deps: [MockBackend, BaseRequestOptions] 26 | }, 27 | ApiService 28 | ] 29 | }); 30 | }); 31 | 32 | beforeEach(inject([ApiService, MockBackend], (service, mock) => { 33 | apiService = service; 34 | mockBackend = mock; 35 | })); 36 | 37 | it('apiService should not be null...', () => { 38 | expect(apiService).toBeTruthy(); 39 | }); 40 | 41 | it('should make get request', async(() => { 42 | let connection; 43 | let response = [ 44 | { 45 | id: 1, 46 | title: 'Getting started with REST', 47 | content: 'Content of Getting started with REST', 48 | createdAt: '9/22/16 4:15 PM' 49 | }, 50 | { 51 | id: 2, 52 | title: 'Getting started with AngularJS 1.x', 53 | content: 'Content of Getting started with AngularJS 1.x', 54 | createdAt: '9/22/16 4:15 PM' 55 | }, 56 | { 57 | id: 3, 58 | title: 'Getting started with Angular2', 59 | content: 'Content of Getting started with Angular2', 60 | createdAt: '9/22/16 4:15 PM' 61 | }, 62 | ]; 63 | 64 | mockBackend.connections.subscribe(connection => { 65 | connection.mockRespond(new Response( 66 | new ResponseOptions({ body: JSON.stringify(response), status: 200 }) 67 | )); 68 | }); 69 | 70 | apiService.get('/posts') 71 | .subscribe(posts => { 72 | expect(posts).toEqual(response); 73 | }); 74 | })); 75 | 76 | it('should make post request', async(() => { 77 | let connection; 78 | let requestBody = { 79 | title: 'Getting started with React', 80 | content: 'Content of Getting started with React' 81 | }; 82 | let response = {}; 83 | 84 | mockBackend.connections.subscribe(connection => { 85 | connection.mockRespond(new Response( 86 | new ResponseOptions({ body: JSON.stringify(response), status: 201 }) 87 | )); 88 | }); 89 | 90 | apiService.post('/posts', requestBody) 91 | .subscribe(post => { 92 | expect(post.json()).toEqual(response); 93 | expect(post.status).toBe(201); 94 | }); 95 | }) 96 | ); 97 | 98 | 99 | it('should make put request', async(() => { 100 | let connection; 101 | let requestBody = { 102 | title: 'Getting started with React', 103 | content: 'Content of Getting started with React' 104 | }; 105 | let response = {}; 106 | 107 | mockBackend.connections.subscribe(connection => { 108 | connection.mockRespond(new Response( 109 | new ResponseOptions({ body: JSON.stringify(response), status: 204 }) 110 | )); 111 | }); 112 | 113 | apiService.put('/posts/1', requestBody) 114 | .subscribe(post => { 115 | expect(post.json()).toEqual(response); 116 | expect(post.status).toBe(204); 117 | }); 118 | })); 119 | 120 | it('should make delete request', async(() => { 121 | let connection; 122 | let response = {}; 123 | 124 | mockBackend.connections.subscribe(connection => { 125 | connection.mockRespond(new Response( 126 | new ResponseOptions({ body: JSON.stringify(response), status: 204 }) 127 | )); 128 | }); 129 | 130 | apiService.delete('/posts/1') 131 | .subscribe(post => { 132 | expect(post.json()).toEqual(response); 133 | expect(post.status).toBe(204); 134 | }); 135 | })); 136 | 137 | }); 138 | -------------------------------------------------------------------------------- /src/app/core/api.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject} from '@angular/core'; 2 | import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http'; 3 | import { Observable } from 'rxjs/Rx'; 4 | 5 | import { APP_CONFIG, AppConfig } from '../app.config'; 6 | 7 | /** 8 | * ApiService: calling remote RESTful APIs. 9 | */ 10 | @Injectable() 11 | export class ApiService { 12 | 13 | private headers: Headers = new Headers({ 14 | 'Content-Type': 'application/json', 15 | 'Accept': 'application/json' 16 | }); 17 | 18 | private API_URL: string = 'http://localhost:8080/blog-api-cdi/api'; 19 | 20 | constructor(private http: Http/*, @Inject(APP_CONFIG) config: AppConfig*/) { 21 | //this.API_URL = config.apiEndpoint; 22 | } 23 | 24 | 25 | /** 26 | * @param path url path to get resource. 27 | * @param term optional search term. 28 | * @return A obserable object to wrap the response result. 29 | */ 30 | public get(path: string, term?: any): Observable { 31 | console.log('get resources from url:' + `${this.API_URL}${path}`); 32 | let search = new URLSearchParams(); 33 | 34 | if (term) { 35 | Object.keys(term).forEach(key => search.set(key, term[key])); 36 | } 37 | 38 | return this.http.get(`${this.API_URL}${path}`, { search, headers: this.headers }) 39 | .map(this.extractData) 40 | .catch(this.handleError); 41 | } 42 | 43 | public post(path: string, data: any): Observable { 44 | let body = JSON.stringify(data); 45 | return this.http.post(`${this.API_URL}${path}`, body, { headers: this.headers }) 46 | //.map(this.extractData) 47 | .catch(this.handleError); 48 | } 49 | 50 | public put(path: string, data: any): Observable { 51 | let body = JSON.stringify(data); 52 | 53 | return this.http.put(`${this.API_URL}${path}`, body, { headers: this.headers }) 54 | //.map(this.extractData) 55 | .catch(this.handleError); 56 | } 57 | 58 | public delete(path: string): Observable { 59 | return this.http.delete(`${this.API_URL}${path}`, { headers: this.headers }) 60 | //.map(this.extractData) 61 | .catch(this.handleError); 62 | } 63 | 64 | public setHeaders(headers) { 65 | Object.keys(headers).forEach(header => this.headers.set(header, headers[header])); 66 | } 67 | 68 | public setHeader(key: string, value: string) { 69 | this.headers.set(key, value); 70 | } 71 | 72 | public deleteHeader(key: string) { 73 | if (this.headers.has(key)) { 74 | this.headers.delete(key); 75 | } else { 76 | console.log(`header:${key} not found!`); 77 | } 78 | } 79 | 80 | private extractData(res: Response): Array | any { 81 | if (res.status >= 200 && res.status <= 300) { 82 | return res.json() || {}; 83 | } 84 | 85 | return res; 86 | } 87 | 88 | private handleError(error: any) { 89 | // In a real world app, we might use a remote logging infrastructure 90 | // We'd also dig deeper into the error to get a better message 91 | // let errMsg = (error.message) ? error.message : 92 | // error.status ? `${error.status} - ${error.statusText}` : 'Server error'; 93 | // console.error(errMsg); // log to console instead 94 | console.log(error); 95 | return Observable.throw(error); 96 | } 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/app/core/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | import { AuthService } from './auth.service'; 6 | 7 | @Injectable() 8 | export class AuthGuard implements CanActivate { 9 | 10 | constructor(private router: Router, private authService: AuthService) { } 11 | 12 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 13 | console.log('route.log' + route.url); 14 | console.log('state' + state.url); 15 | 16 | this 17 | .authService 18 | .isAuthenticated() 19 | .subscribe((auth) => { 20 | if (!auth) { 21 | this.authService.setDesiredUrl(state.url); 22 | console.log('desiredUrl@' + state.url); 23 | this 24 | .router 25 | .navigate(['', 'signin']); 26 | } 27 | }); 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/core/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { AuthService } from './auth.service'; 5 | 6 | xdescribe('Service: Auth', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [AuthService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([AuthService], (service: AuthService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/core/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, ActivatedRoute, UrlSegment } from '@angular/router'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { BehaviorSubject, ReplaySubject } from 'rxjs/Rx'; 5 | 6 | import { ApiService } from './api.service'; 7 | import { JWT } from './jwt'; 8 | import { User } from './user.model'; 9 | 10 | interface State { 11 | current: User; 12 | desiredUrl: string; 13 | } 14 | 15 | const defaultState: State = { 16 | current: null, 17 | desiredUrl: null 18 | }; 19 | 20 | const store$ = new BehaviorSubject(defaultState); 21 | 22 | class AuthStore { 23 | _store = store$; 24 | changes = this._store.distinctUntilChanged(); 25 | 26 | setState(state: State) { 27 | this._store.next(Object.assign({}, this.getState(), state)); 28 | } 29 | 30 | getState() { 31 | return this._store.value; 32 | } 33 | 34 | purge() { 35 | this._store.next(defaultState); 36 | } 37 | 38 | } 39 | 40 | @Injectable() 41 | export class AuthService { 42 | 43 | private currentUser$: BehaviorSubject = new BehaviorSubject(null); 44 | private authenticated$: ReplaySubject = new ReplaySubject(1); 45 | private desiredUrl: string = null; 46 | 47 | constructor( 48 | private api: ApiService, 49 | private jwt: JWT, 50 | private router: Router) { 51 | } 52 | 53 | attempAuth(type: string, credentials: any) { 54 | const path = (type === 'signin') ? '/login' : '/signup'; 55 | const url = '/auth' + path; 56 | 57 | this.api.post(url, credentials) 58 | .map(res => res.json()) 59 | .subscribe(res => { 60 | this.jwt.save(res.id_token); 61 | // set Authorization header 62 | this.setJwtHeader(res.id_token); 63 | 64 | this.setState(res.user); 65 | 66 | if (this.desiredUrl && !this.desiredUrl.startsWith('/signin')) { 67 | const _targetUrl = this.desiredUrl; 68 | this.desiredUrl = null; 69 | this.router.navigateByUrl(_targetUrl); 70 | } else { 71 | this.router.navigate(['']); 72 | } 73 | }); 74 | } 75 | 76 | 77 | verifyAuth(): void { 78 | 79 | // jwt token is not found in local storage. 80 | if (this.jwt.get()) { 81 | 82 | // set jwt header and try to refresh user info. 83 | this.setJwtHeader(this.jwt.get()); 84 | 85 | this.api.get('/me').subscribe( 86 | res => { 87 | this.currentUser$.next(res); 88 | this.authenticated$.next(true); 89 | }, 90 | err => { 91 | this.clearJwtHeader(); 92 | this.jwt.destroy(); 93 | this.currentUser$.next(null); 94 | this.authenticated$.next(false); 95 | } 96 | ); 97 | } else { 98 | this.clearJwtHeader(); 99 | this.jwt.destroy(); 100 | this.currentUser$.next(null); 101 | this.authenticated$.next(false); 102 | } 103 | } 104 | 105 | logout() { 106 | // reset the initial values 107 | this.setState(null); 108 | //this.desiredUrl = null; 109 | 110 | this.jwt.destroy(); 111 | this.clearJwtHeader(); 112 | this.desiredUrl = null; 113 | 114 | this.router.navigate(['']); 115 | } 116 | 117 | currentUser(): Observable { 118 | return this.currentUser$.distinctUntilChanged(); 119 | } 120 | 121 | isAuthenticated(): Observable { 122 | return this.authenticated$.asObservable(); 123 | } 124 | 125 | getDesiredUrl() { 126 | return this.desiredUrl; 127 | } 128 | 129 | setDesiredUrl(url: string) { 130 | this.desiredUrl = url; 131 | } 132 | 133 | private setState(state: User) { 134 | if (state) { 135 | this.currentUser$.next(state); 136 | this.authenticated$.next(true); 137 | } else { 138 | this.currentUser$.next(null); 139 | this.authenticated$.next(false); 140 | } 141 | } 142 | 143 | private setJwtHeader(jwt: string) { 144 | this.api.setHeaders({ Authorization: `Bearer ${jwt}` }); 145 | } 146 | 147 | private clearJwtHeader() { 148 | this.api.deleteHeader('Authorization'); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Optional, SkipSelf } from '@angular/core'; 2 | import { HttpModule } from '@angular/http'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { ApiService } from './api.service'; 6 | import { AuthService } from './auth.service'; 7 | import { JWT } from './jwt'; 8 | import { AuthGuard } from './auth.guard'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | HttpModule, 13 | RouterModule 14 | ], 15 | providers: [ 16 | ApiService, 17 | AuthService, 18 | JWT, 19 | AuthGuard 20 | ] 21 | }) 22 | export class CoreModule { 23 | 24 | //Prevent reimport of the CoreModule 25 | constructor (@Optional() @SkipSelf() parentModule: CoreModule) { 26 | if (parentModule) { 27 | throw new Error('CoreModule is already loaded. Import it in the AppModule only'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './jwt'; 2 | export * from './api.service'; 3 | export * from './auth.service'; 4 | export * from './auth.guard'; 5 | export * from './user.model'; 6 | -------------------------------------------------------------------------------- /src/app/core/jwt.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { APP_CONFIG, AppConfig } from '../app.config'; 3 | 4 | const JWT_KEY: string = 'id_token'; 5 | 6 | @Injectable() 7 | export class JWT { 8 | 9 | constructor(/*@Inject(APP_CONFIG) config: AppConfig*/) { 10 | //this.jwtKey = config.jwtKey; 11 | } 12 | 13 | save(token) { 14 | window.localStorage[JWT_KEY] = token; 15 | } 16 | 17 | get() { 18 | return window.localStorage[JWT_KEY]; 19 | } 20 | 21 | destroy() { 22 | window.localStorage.removeItem(JWT_KEY); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/app/core/user.model.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id?: number; 3 | username: string; 4 | firstName?: string; 5 | lastName?: string; 6 | email?: string; 7 | createdAt?: string; 8 | createdBy?: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { HomeComponent } from './home.component'; 4 | 5 | const routes: Routes = [ 6 | { path: 'home', component: HomeComponent } 7 | ]; 8 | 9 | @NgModule({ 10 | imports: [RouterModule.forChild(routes)], 11 | exports: [RouterModule], 12 | providers: [] 13 | }) 14 | export class HomeRoutingModule { } 15 | -------------------------------------------------------------------------------- /src/app/home/home.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/home/home.component.css -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 4 |

Using the cutting-edge technology stack.

5 |
6 |
7 |
8 |
9 |

{{s.name}}

10 |

{{s.description}}

11 | {{s.name}} 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { HomeComponent } from './home.component'; 5 | 6 | describe('Component: Home', () => { 7 | it('should create an instance', () => { 8 | let component = new HomeComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | class Skill { 4 | name: string; 5 | description: string; 6 | url: string; 7 | } 8 | 9 | @Component({ 10 | selector: 'app-home', 11 | templateUrl: './home.component.html', 12 | styleUrls: ['./home.component.css'] 13 | }) 14 | export class HomeComponent implements OnInit { 15 | private skills: Skill[]; 16 | 17 | constructor() { } 18 | 19 | ngOnInit() { 20 | this.skills = [ 21 | { 22 | name: 'Angular 2', 23 | description: 'Web component based MVVM framework.', 24 | url: 'http://angular.io' 25 | }, 26 | { 27 | name: 'Typescript', 28 | description: 'Typescript is a typed superset of JavaScript that compiles to plain JavaScript.', 29 | url: 'https://www.typescriptlang.org/' 30 | }, 31 | { 32 | name: 'RxJS', 33 | description: 'Reactive programming for JavaScript', 34 | url: 'http://reactivex.io/rxjs/' 35 | }, 36 | { 37 | name: 'Angular CLI', 38 | description: 'Official command line tooling for Angular 2', 39 | url: 'https://github.com/angular/angular-cli' 40 | }, 41 | { 42 | name: 'Webpack', 43 | description: 'The only build pack you should have.', 44 | url: 'https://webpack.github.io/' 45 | }, 46 | { 47 | name: 'Bootstrap', 48 | description: 'The most popular frontend CSS framework.', 49 | url: 'https://getbootstrap.org/' 50 | }, 51 | ]; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HomeRoutingModule } from './home-routing.module'; 4 | import { HomeComponent } from './home.component'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | CommonModule, 9 | HomeRoutingModule 10 | ], 11 | declarations: [HomeComponent] 12 | }) 13 | export class HomeModule { } 14 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/app/layout/footer.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/layout/footer.component.css -------------------------------------------------------------------------------- /src/app/layout/footer.component.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/app/layout/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { FooterComponent } from './footer.component'; 5 | 6 | describe('Component: Footer', () => { 7 | it('should create an instance', () => { 8 | let component = new FooterComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/layout/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { TranslateService } from '@ngx-translate/core'; 3 | 4 | @Component({ 5 | selector: 'app-footer', 6 | templateUrl: './footer.component.html', 7 | styleUrls: ['./footer.component.css'] 8 | }) 9 | export class FooterComponent implements OnInit { 10 | 11 | constructor(private translate: TranslateService) { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | setLanguage(lang: string) { 17 | this.translate.use(lang); 18 | } 19 | 20 | isLanguage(lang: string): boolean { 21 | return this.translate.currentLang === lang; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { SharedModule } from '../shared/shared.module'; 3 | import { NavbarComponent } from './navbar.component'; 4 | import { FooterComponent } from './footer.component'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | SharedModule 9 | ], 10 | declarations: [ 11 | NavbarComponent, 12 | FooterComponent, 13 | ], 14 | exports: [ 15 | NavbarComponent, 16 | FooterComponent, 17 | ] 18 | }) 19 | export class LayoutModule { } 20 | -------------------------------------------------------------------------------- /src/app/layout/navbar.component.css: -------------------------------------------------------------------------------- 1 | nav { 2 | background-color: #e3f2fd; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/layout/navbar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 45 | -------------------------------------------------------------------------------- /src/app/layout/navbar.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { RouterModule } from '@angular/router'; 5 | import { NavbarComponent } from './navbar.component'; 6 | 7 | xdescribe('Component: Navbar', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | imports: [RouterModule], 11 | declarations: [ 12 | NavbarComponent, 13 | ], 14 | }); 15 | }); 16 | 17 | it('should create the Navbar component', async(() => { 18 | let fixture = TestBed.createComponent(NavbarComponent); 19 | let component = fixture.debugElement.componentInstance; 20 | expect(component).toBeTruthy(); 21 | })); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/layout/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { AuthService } from '../core/auth.service'; 4 | 5 | @Component({ 6 | selector: 'app-navbar', 7 | templateUrl: './navbar.component.html', 8 | styleUrls: ['./navbar.component.css'] 9 | }) 10 | export class NavbarComponent implements OnInit { 11 | constructor(private authService: AuthService) { 12 | } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | logout() { 18 | console.log('calling onLogout...'); 19 | this.authService.logout(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/posts/edit-post/edit-post.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/edit-post/edit-post.component.css -------------------------------------------------------------------------------- /src/app/posts/edit-post/edit-post.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{'edit-post'}}

4 |
created at: {{post.createdAt| date:'short'}} • {{post.createdBy}}
5 |
all fields marked with star are required.
6 |
7 |
8 | 9 |
10 | 13 |
14 | -------------------------------------------------------------------------------- /src/app/posts/edit-post/edit-post.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { EditPostComponent } from './edit-post.component'; 5 | 6 | describe('Component: EditPost', () => { 7 | it('should create an instance', () => { 8 | let component = new EditPostComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/edit-post/edit-post.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | import { Post } from '../shared/post.model'; 5 | import { PostService } from '../shared/post.service'; 6 | 7 | @Component({ 8 | selector: 'app-edit-post', 9 | templateUrl: './edit-post.component.html', 10 | styleUrls: ['./edit-post.component.css'] 11 | }) 12 | export class EditPostComponent implements OnInit, OnDestroy { 13 | post: Post = { title: '', content: '' }; 14 | sub: Subscription; 15 | id: number; 16 | 17 | constructor(private postService: PostService, private router: Router, private route: ActivatedRoute) { } 18 | 19 | onPostUpdated(event) { 20 | console.log('post was updated!' + event); 21 | if (event) { 22 | this.router.navigate(['', 'posts']); 23 | } 24 | } 25 | 26 | ngOnInit() { 27 | this.sub = this.route.params 28 | .flatMap(params => { 29 | this.id = +params['id']; 30 | return this.postService.getPost(this.id); 31 | }) 32 | .subscribe((res: Post) => this.post = res); 33 | } 34 | 35 | ngOnDestroy() { 36 | if (this.sub) { 37 | this.sub.unsubscribe(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/posts/home/home.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/home/home.component.css -------------------------------------------------------------------------------- /src/app/posts/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 | new-post 18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |

{{post.title}}

28 |
{{post.createdAt|date:'short'}}
29 |

{{post.content}}

30 | 34 |
35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /src/app/posts/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { PostsComponent } from './posts.component'; 5 | 6 | xdescribe('Component: Posts', () => { 7 | xit('should create an instance', () => { 8 | let component = new PostsComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { Post } from '../shared/post.model'; 4 | import { PostService } from '../shared/post.service'; 5 | import { Subscription } from 'rxjs/Subscription'; 6 | 7 | @Component({ 8 | selector: 'app-posts-home', 9 | templateUrl: './home.component.html', 10 | styleUrls: ['./home.component.css'] 11 | }) 12 | export class PostsHomeComponent implements OnInit, OnDestroy { 13 | q = ''; 14 | posts: Post[]; 15 | sub: Subscription; 16 | 17 | constructor(private postService: PostService) { 18 | } 19 | 20 | 21 | //
22 | //
23 | // 24 | //
25 | // 26 | //
27 | // search(term: string) { 28 | // this.sub = this.postService.getPosts({ q: term }).subscribe( 29 | // res => this.posts = res 30 | // ); 31 | // } 32 | 33 | search() { 34 | this.sub = this.postService.getPosts({ q: this.q }).subscribe( 35 | res => this.posts = res 36 | ); 37 | } 38 | 39 | // clearTerm() { 40 | // this.q.setValue(null); 41 | // } 42 | 43 | ngOnInit() { 44 | this.search(); 45 | // this.posts = [ 46 | // { 47 | // id: 1, 48 | // title: 'Getting started with REST', 49 | // content: 'Content of Getting started with REST', 50 | // createdAt: '9/22/16 4:15 PM' 51 | // }, 52 | // { 53 | // id: 2, 54 | // title: 'Getting started with AngularJS 1.x', 55 | // content: 'Content of Getting started with AngularJS 1.x', 56 | // createdAt: '9/22/16 4:15 PM' 57 | // }, 58 | // { 59 | // id: 3, 60 | // title: 'Getting started with Angular2', 61 | // content: 'Content of Getting started with Angular2', 62 | // createdAt: '9/22/16 4:15 PM' 63 | // }, 64 | // ]; 65 | } 66 | 67 | ngOnDestroy() { 68 | if (this.sub) { 69 | this.sub.unsubscribe(); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/app/posts/new-post/new-post.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/new-post/new-post.component.css -------------------------------------------------------------------------------- /src/app/posts/new-post/new-post.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{'new-post'}}

4 |
all fields marked with star are required.
5 |
6 |
7 | 8 |
9 | 12 |
13 | -------------------------------------------------------------------------------- /src/app/posts/new-post/new-post.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { NewPostComponent } from './new-post.component'; 5 | 6 | describe('Component: NewPost', () => { 7 | it('should create an instance', () => { 8 | let component = new NewPostComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/new-post/new-post.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | import { Post } from '../shared/post.model'; 5 | import { PostService } from '../shared/post.service'; 6 | 7 | @Component({ 8 | selector: 'app-new-post', 9 | templateUrl: './new-post.component.html', 10 | styleUrls: ['./new-post.component.css'] 11 | }) 12 | export class NewPostComponent implements OnInit, OnDestroy { 13 | post: Post = { title: '', content: '' }; 14 | sub: Subscription; 15 | 16 | constructor( private router: Router) { } 17 | 18 | onPostSaved(event) { 19 | console.log('post was saved!' + event); 20 | if (event) { 21 | this.router.navigate(['', 'posts']); 22 | } 23 | } 24 | 25 | ngOnInit() { 26 | } 27 | 28 | ngOnDestroy() { 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-form.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/post-details/comment/comment-form.component.css -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-form.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | 6 | 10 |
11 |
12 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { CommentFormComponent } from './comment-form.component'; 5 | 6 | xdescribe('Component: CommentForm', () => { 7 | it('should create an instance', () => { 8 | let component = new CommentFormComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms'; 3 | 4 | import { Comment } from '../../shared/comment.model'; 5 | import { Post } from '../../shared/post.model'; 6 | import { PostService } from '../../shared/post.service'; 7 | 8 | @Component({ 9 | selector: 'app-comment-form', 10 | templateUrl: './comment-form.component.html', 11 | styleUrls: ['./comment-form.component.css'] 12 | }) 13 | export class CommentFormComponent implements OnInit { 14 | @Input() post: Post; 15 | @Output() onSaved = new EventEmitter(); 16 | 17 | commentForm: FormGroup; 18 | content: FormControl; 19 | 20 | constructor(private postService: PostService, private fb: FormBuilder) { 21 | this.content = new FormControl('', [Validators.required, Validators.minLength(10)]); 22 | this.commentForm = fb.group({ 23 | content: this.content 24 | }); 25 | } 26 | 27 | ngOnInit() { 28 | 29 | } 30 | 31 | saveCommentForm() { 32 | console.log('submitting comment form @' + this.commentForm.value); 33 | 34 | this.postService.saveComment(this.post.id, this.commentForm.value) 35 | .subscribe((data) => { 36 | this.commentForm.controls['content'].setValue(''); 37 | this.commentForm.markAsPristine(); 38 | this.commentForm.markAsUntouched(); 39 | this.onSaved.emit(true); 40 | }, (error) => { 41 | this.onSaved.emit(false); 42 | }); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list-item.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/post-details/comment/comment-list-item.component.css -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list-item.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | ... 5 | 6 |
7 |
8 |
{{comment.createdBy}} • {{comment.createdAt|date:'short'}}
9 |

10 |
11 |
12 | 13 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { CommentListItemComponent } from './comment-list-item.component'; 5 | 6 | describe('Component: CommentListItem', () => { 7 | it('should create an instance', () => { 8 | let component = new CommentListItemComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | import { Comment } from '../../shared/comment.model'; 4 | 5 | @Component({ 6 | selector: 'app-comment-list-item', 7 | templateUrl: './comment-list-item.component.html', 8 | styleUrls: ['./comment-list-item.component.css'] 9 | }) 10 | export class CommentListItemComponent implements OnInit { 11 | 12 | @Input() comment: Comment; 13 | 14 | constructor() { } 15 | 16 | ngOnInit() { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/post-details/comment/comment-list.component.css -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { CommentListComponent } from './comment-list.component'; 5 | 6 | describe('Component: CommentList', () => { 7 | it('should create an instance', () => { 8 | let component = new CommentListComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnChanges, Input } from '@angular/core'; 2 | 3 | import { Post } from '../../shared/post.model'; 4 | import { Comment } from '../../shared/comment.model'; 5 | 6 | 7 | @Component({ 8 | selector: 'app-comment-list', 9 | templateUrl: './comment-list.component.html', 10 | styleUrls: ['./comment-list.component.css'] 11 | }) 12 | export class CommentListComponent implements OnInit, OnChanges { 13 | 14 | @Input() post: Post; 15 | @Input() comments: Comment[]; 16 | 17 | constructor() { } 18 | 19 | ngOnInit() { 20 | console.log('calling ngOnInit::CommentListComponent...'); 21 | } 22 | 23 | ngOnChanges(changes: any) { 24 | console.log('calling ngChanges::CommentListComponent...' + JSON.stringify(changes)); 25 | //if (changes['comments'].previousValue != changes['comments'].currentValue) { 26 | // this.comments = changes['comments'].currentValue; 27 | //} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-panel.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/post-details/comment/comment-panel.component.css -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-panel.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 |
8 |
Please signin and add your comments.
9 | 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-panel.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentPanelComponent } from './comment-panel.component'; 4 | 5 | describe('CommentPanelComponent', () => { 6 | let component: CommentPanelComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CommentPanelComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentPanelComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/posts/post-details/comment/comment-panel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { Post } from '../../shared/post.model'; 3 | import { Comment } from '../../shared/comment.model'; 4 | import { PostService } from '../../shared/post.service'; 5 | 6 | @Component({ 7 | selector: 'app-comment-panel', 8 | templateUrl: './comment-panel.component.html', 9 | styleUrls: ['./comment-panel.component.css'] 10 | }) 11 | export class CommentPanelComponent implements OnInit { 12 | 13 | @Input() post: Post; 14 | @Input() comments: Comment[]; 15 | 16 | constructor(private postService: PostService) { } 17 | 18 | ngOnInit() { 19 | } 20 | 21 | onCommentSaved(event) { 22 | console.log(event); 23 | if (event) { 24 | this.postService.getCommentsOfPost(this.post.id).subscribe((data) => this.comments = data); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details-panel/post-details-panel.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/post-details/post-details-panel/post-details-panel.component.css -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details-panel/post-details-panel.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{post.title}} 5 |

6 |
{{post.createdAt|date:'short'}}
7 |
8 |
9 |

10 | {{post.content}} 11 |

12 |
13 | 16 |
17 | -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details-panel/post-details-panel.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { PostDetailsCardComponent } from './post-details-card.component'; 5 | 6 | describe('Component: PostDetailsCard', () => { 7 | it('should create an instance', () => { 8 | let component = new PostDetailsCardComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details-panel/post-details-panel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnChanges, Input, SimpleChange } from '@angular/core'; 2 | 3 | import { Post } from '../../shared/post.model'; 4 | 5 | @Component({ 6 | selector: 'app-post-details-panel', 7 | templateUrl: './post-details-panel.component.html', 8 | styleUrls: ['./post-details-panel.component.css'] 9 | }) 10 | export class PostDetailsPanelComponent implements OnInit, OnChanges { 11 | 12 | @Input() post: Post; 13 | 14 | constructor() { } 15 | 16 | ngOnInit() { 17 | } 18 | 19 | ngOnChanges(changes: { [propertyName: string]: SimpleChange }) { 20 | 21 | // if (changes && changes['post'] && changes['post'].currentValue) { 22 | // this.post = Object.assign({}, changes['post'].currentValue); 23 | // } 24 | // for (let propName in changes) { 25 | // let chng = changes[propName]; 26 | // let cur = JSON.stringify(chng.currentValue); 27 | // let prev = JSON.stringify(chng.previousValue); 28 | 29 | // console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); 30 | // } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/post-details/post-details.component.css -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { PostDetailsComponent } from './post-details.component'; 5 | 6 | describe('Component: PostDetails', () => { 7 | it('should create an instance', () => { 8 | let component = new PostDetailsComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/posts/post-details/post-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, Input } from '@angular/core'; 2 | import { Router, ActivatedRoute} from '@angular/router'; 3 | 4 | import { Observable, Subscription} from 'rxjs/Rx'; 5 | import { PostService } from '../shared/post.service'; 6 | import { Post } from '../shared/post.model'; 7 | import { Comment } from '../shared/comment.model'; 8 | 9 | @Component({ 10 | selector: 'app-post-details', 11 | templateUrl: './post-details.component.html', 12 | styleUrls: ['./post-details.component.css'] 13 | }) 14 | export class PostDetailsComponent implements OnInit, OnDestroy { 15 | 16 | id: number; 17 | post: Post = { title: '', content: '' }; 18 | comments: Comment[] = []; 19 | sub: Subscription; 20 | 21 | constructor(private postService: PostService, private router: Router, private route: ActivatedRoute) { } 22 | 23 | 24 | 25 | ngOnInit() { 26 | this.sub = this.route.params 27 | .flatMap(params => { 28 | this.id = +params['id']; 29 | return Observable.forkJoin(this.postService.getPost(this.id), this.postService.getCommentsOfPost(this.id)); 30 | }).subscribe((res: Array) => { 31 | this.post = res[0]; 32 | this.comments = res[1]; 33 | console.log("post data:" + this.post + ", comments: " + this.comments); 34 | }); 35 | } 36 | 37 | ngOnDestroy() { 38 | if (this.sub) { this.sub.unsubscribe(); } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/app/posts/posts-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { AuthGuard } from '../core/auth.guard'; 5 | 6 | import { PostsHomeComponent } from './home/home.component'; 7 | import { NewPostComponent } from './new-post/new-post.component'; 8 | import { EditPostComponent } from './edit-post/edit-post.component'; 9 | import { PostDetailsComponent } from './post-details/post-details.component'; 10 | 11 | const routes: Routes = [ 12 | { path: '', redirectTo: 'home' }, 13 | { path: 'home', component: PostsHomeComponent }, 14 | { path: 'new', component: NewPostComponent, canActivate: [AuthGuard] }, 15 | { path: 'edit/:id', component: EditPostComponent, canActivate: [AuthGuard] }, 16 | { path: 'view/:id', component: PostDetailsComponent }, 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [RouterModule.forChild(routes)], 21 | exports: [RouterModule], 22 | providers: [] 23 | }) 24 | export class PostsRoutingModule { } 25 | -------------------------------------------------------------------------------- /src/app/posts/posts.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { SharedModule } from '../shared/shared.module'; 6 | import { PostsRoutingModule } from './posts-routing.module'; 7 | import { PostsHomeComponent } from './home/home.component'; 8 | import { NewPostComponent } from './new-post/new-post.component'; 9 | import { EditPostComponent } from './edit-post/edit-post.component'; 10 | import { PostDetailsComponent } from './post-details/post-details.component'; 11 | import { PostDetailsPanelComponent } from './post-details/post-details-panel/post-details-panel.component'; 12 | import { CommentListComponent } from './post-details/comment/comment-list.component'; 13 | import { CommentListItemComponent } from './post-details/comment/comment-list-item.component'; 14 | import { CommentFormComponent } from './post-details/comment/comment-form.component'; 15 | import { CommentPanelComponent } from './post-details/comment/comment-panel.component'; 16 | import { PostFormComponent } from './shared/post-form/post-form.component'; 17 | import { PostService } from './shared/post.service'; 18 | 19 | @NgModule({ 20 | imports: [ 21 | SharedModule, 22 | PostsRoutingModule 23 | ], 24 | declarations: [ 25 | PostsHomeComponent, 26 | NewPostComponent, 27 | EditPostComponent, 28 | PostDetailsComponent, 29 | PostDetailsPanelComponent, 30 | CommentListComponent, 31 | CommentListItemComponent, 32 | CommentFormComponent, 33 | PostFormComponent, 34 | CommentPanelComponent 35 | ], 36 | exports: [ 37 | PostDetailsPanelComponent, 38 | CommentListComponent, 39 | CommentListItemComponent, 40 | CommentFormComponent, 41 | PostFormComponent, 42 | CommentPanelComponent 43 | ], 44 | providers: [ 45 | PostService, 46 | ] 47 | }) 48 | export class PostsModule { } 49 | -------------------------------------------------------------------------------- /src/app/posts/shared/comment.model.ts: -------------------------------------------------------------------------------- 1 | export interface Comment { 2 | id?: number; 3 | content: string; 4 | createdBy?: string; 5 | createdAt?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/posts/shared/post-form/post-form.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/posts/shared/post-form/post-form.component.css -------------------------------------------------------------------------------- /src/app/posts/shared/post-form/post-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 8 |
9 | 10 |
11 | 12 | 15 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/app/posts/shared/post-form/post-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PostFormComponent } from './post-form.component'; 4 | 5 | describe('PostFormComponent', () => { 6 | let component: PostFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PostFormComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PostFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/posts/shared/post-form/post-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; 2 | import { Router, ActivatedRoute } from '@angular/router'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | import { Post } from '../post.model'; 5 | import { PostService } from '../post.service'; 6 | 7 | @Component({ 8 | selector: 'app-post-form', 9 | templateUrl: './post-form.component.html', 10 | styleUrls: ['./post-form.component.css'] 11 | }) 12 | export class PostFormComponent implements OnInit, OnDestroy { 13 | 14 | @Input() post: Post = { title: '', content: '' }; 15 | @Output() onSaved: EventEmitter = new EventEmitter(); 16 | sub: Subscription; 17 | 18 | constructor(private postService: PostService) { } 19 | 20 | save() { 21 | const _body = { title: this.post.title, content: this.post.content }; 22 | 23 | if (this.post.id) { 24 | this.postService 25 | .updatePost(this.post.id, _body) 26 | .subscribe((data) => { 27 | this.onSaved.emit(true); 28 | }, 29 | (error) => { 30 | this.onSaved.emit(false); 31 | } 32 | ); 33 | } else { 34 | this.postService 35 | .savePost(_body) 36 | .subscribe((data) => { 37 | this.onSaved.emit(true); 38 | }, 39 | (error) => { 40 | this.onSaved.emit(false); 41 | } 42 | ); 43 | } 44 | 45 | } 46 | 47 | ngOnInit() { 48 | console.log('calling ngOnInit::PostFormComponent...'); 49 | } 50 | 51 | ngOnDestroy() { 52 | console.log('calling ngOnDestroy::PostFormComponent...'); 53 | if (this.sub) { 54 | this.sub.unsubscribe(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/posts/shared/post.model.ts: -------------------------------------------------------------------------------- 1 | export interface Post { 2 | id?: number; 3 | title: string; 4 | content: string; 5 | createdAt?: string; 6 | createdBy?: string; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/app/posts/shared/post.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { HttpModule, BaseRequestOptions, Http, Response, ResponseOptions } from '@angular/http'; 5 | import { Observable} from 'rxjs/Rx'; 6 | 7 | import { ApiService } from './api.service'; 8 | import { PostService } from './post.service'; 9 | 10 | const posts = [ 11 | { 12 | id: 1, 13 | title: 'Getting started with REST', 14 | content: 'Content of Getting started with REST', 15 | createdAt: '9/22/16 4:15 PM' 16 | }, 17 | { 18 | id: 2, 19 | title: 'Getting started with AngularJS 1.x', 20 | content: 'Content of Getting started with AngularJS 1.x', 21 | createdAt: '9/22/16 4:15 PM' 22 | }, 23 | { 24 | id: 3, 25 | title: 'Getting started with Angular2', 26 | content: 'Content of Getting started with Angular2', 27 | createdAt: '9/22/16 4:15 PM' 28 | }, 29 | ]; 30 | 31 | class ApiServiceStub { 32 | get(path) { 33 | return Observable.of(posts); 34 | } 35 | } 36 | 37 | describe('Service: Post', () => { 38 | let apiService: ApiService; 39 | let postService: PostService; 40 | 41 | beforeEach(() => { 42 | TestBed.configureTestingModule({ 43 | imports: [HttpModule], 44 | providers: [{ provide: ApiService, useValue: new ApiServiceStub() }, PostService] 45 | }); 46 | }); 47 | 48 | beforeEach(inject([ApiService, PostService], (_apiService, _postService) => { 49 | apiService = _apiService; 50 | postService = _postService; 51 | })); 52 | 53 | it('should not be null...', () => { 54 | expect(postService).toBeTruthy(); 55 | }); 56 | 57 | it('should get posts...', async(() => { 58 | postService.getPosts() 59 | .subscribe(res => { 60 | expect(res).toEqual(posts); 61 | }); 62 | })); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /src/app/posts/shared/post.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { ApiService } from '../../core/api.service'; 4 | import { Post } from './post.model'; 5 | import { Comment } from './comment.model'; 6 | import { Observable } from 'rxjs/Observable'; 7 | 8 | @Injectable() 9 | export class PostService { 10 | 11 | private path = '/posts'; 12 | 13 | constructor(private api: ApiService) { } 14 | 15 | getPosts(term?: any): Observable { 16 | return this.api.get(`${this.path}`, term); 17 | } 18 | 19 | getPost(id: number): Observable { 20 | return this.api.get(`${this.path}/${id}`); 21 | } 22 | 23 | savePost(data: Post) { 24 | console.log('saving post:' + data); 25 | return this.api.post(`${this.path}`, data); 26 | } 27 | 28 | updatePost(id: number, data: Post) { 29 | console.log('updating post:' + data); 30 | return this.api.put(`${this.path}/${id}`, data); 31 | } 32 | 33 | deletePost(id: number) { 34 | return this.api.delete(`${this.path}/${id}`); 35 | } 36 | 37 | saveComment(id: number, data: Comment) { 38 | return this.api.post(`${this.path}/${id}/comments`, data); 39 | } 40 | 41 | getCommentsOfPost(id: number) { 42 | return this.api.get(`${this.path}/${id}/comments`); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/app/shared/email-validator.directive.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { EmailValidatorDirective } from './email-validator.directive'; 5 | 6 | describe('Directive: EmailValidator', () => { 7 | it('should create an instance', () => { 8 | let directive = new EmailValidatorDirective(); 9 | expect(directive).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/shared/email-validator.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, forwardRef } from '@angular/core'; 2 | import { NG_VALIDATORS, FormControl } from '@angular/forms'; 3 | 4 | function validateEmailFactory(/* emailBlackList: EmailBlackList*/) { 5 | return (c: FormControl) => { 6 | let EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 7 | 8 | return EMAIL_REGEXP.test(c.value) ? null : { 9 | validateEmail: { 10 | valid: false 11 | } 12 | }; 13 | }; 14 | } 15 | //http://blog.thoughtram.io/angular/2016/03/14/custom-validators-in-angular-2.html 16 | //http://blog.ng-book.com/the-ultimate-guide-to-forms-in-angular-2/ 17 | @Directive({ 18 | selector: '[validateEmail][ngModel], [validateEmail][formControl], [validateEmail][formControlName]', 19 | providers: [ 20 | { provide: NG_VALIDATORS, useExisting: forwardRef(() => EmailValidatorDirective), multi: true } 21 | ] 22 | }) 23 | export class EmailValidatorDirective { 24 | 25 | validator: Function; 26 | 27 | constructor(/*emailBlackList: EmailBlackList*/) { 28 | this.validator = validateEmailFactory(); 29 | } 30 | 31 | validate(c: FormControl) { 32 | return this.validator(c); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/app/shared/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/shared/index.ts -------------------------------------------------------------------------------- /src/app/shared/nl2br.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { Nl2brPipe } from './nl2br.pipe'; 5 | 6 | describe('Pipe: Nl2br', () => { 7 | it('create an instance', () => { 8 | let pipe = new Nl2brPipe(); 9 | expect(pipe).toBeTruthy(); 10 | 11 | expect(pipe.transform('hello\nworld')).toEqual('hello
world'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/app/shared/nl2br.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'nl2br' 5 | }) 6 | export class Nl2brPipe implements PipeTransform { 7 | 8 | transform(value: any, args?: any): any { 9 | 10 | if (value !== void 0) { 11 | return value.replace(/\n/g, '
'); 12 | } 13 | return value; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | //import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 6 | import { TranslateModule} from '@ngx-translate/core'; 7 | import { ShowAuthedDirective } from './show-authed.directive'; 8 | 9 | import { Nl2brPipe } from './nl2br.pipe'; 10 | import { EmailValidatorDirective } from './email-validator.directive'; 11 | 12 | 13 | @NgModule({ 14 | imports: [ 15 | CommonModule, 16 | RouterModule, 17 | FormsModule, 18 | ReactiveFormsModule, 19 | //3rd party modules 20 | //NgbModule, 21 | TranslateModule 22 | ], 23 | declarations: [ 24 | ShowAuthedDirective, 25 | Nl2brPipe, 26 | EmailValidatorDirective 27 | ], 28 | exports: [ 29 | CommonModule, 30 | RouterModule, 31 | FormsModule, 32 | ReactiveFormsModule, 33 | //NgbModule, 34 | ShowAuthedDirective, 35 | Nl2brPipe, 36 | EmailValidatorDirective, 37 | TranslateModule 38 | ], 39 | }) 40 | export class SharedModule { } 41 | -------------------------------------------------------------------------------- /src/app/shared/show-authed.directive.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { ShowAuthedDirective } from './show-authed.directive'; 5 | 6 | describe('Directive: ShowAuthed', () => { 7 | it('should create an instance', () => { 8 | let directive = new ShowAuthedDirective(); 9 | expect(directive).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/shared/show-authed.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, Input, Renderer, HostBinding, Attribute, OnInit, OnDestroy } from '@angular/core'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | import { AuthService } from '../core/auth.service'; 4 | 5 | @Directive({ 6 | selector: '[appShowAuthed]' 7 | }) 8 | export class ShowAuthedDirective implements OnInit, OnDestroy { 9 | 10 | @Input() appShowAuthed: boolean; 11 | sub: Subscription; 12 | 13 | constructor(private authService: AuthService, private el: ElementRef, private renderer: Renderer) { 14 | console.log('[appShowAuthed] value:' + this.appShowAuthed); 15 | } 16 | 17 | ngOnInit() { 18 | console.log('[appShowAuthed] ngOnInit:'); 19 | this.sub = this.authService.currentUser().subscribe((res) => { 20 | if (res) { 21 | if (this.appShowAuthed) { 22 | this.renderer.setElementStyle(this.el.nativeElement, 'display', 'inherit'); 23 | } else { 24 | this.renderer.setElementStyle(this.el.nativeElement, 'display', 'none'); 25 | } 26 | 27 | } else { 28 | if (this.appShowAuthed) { 29 | this.renderer.setElementStyle(this.el.nativeElement, 'display', 'none'); 30 | } else { 31 | this.renderer.setElementStyle(this.el.nativeElement, 'display', 'inherit'); 32 | } 33 | } 34 | }); 35 | } 36 | 37 | ngOnDestroy() { 38 | console.log('[appShowAuthed] ngOnDestroy:'); 39 | if (this.sub) { this.sub.unsubscribe(); } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/signin/signin-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { SigninComponent } from './signin.component'; 4 | 5 | const routes: Routes = [ 6 | { path: '', component: SigninComponent } 7 | ]; 8 | 9 | @NgModule({ 10 | imports: [RouterModule.forChild(routes)], 11 | exports: [RouterModule], 12 | providers: [] 13 | }) 14 | export class SigninRoutingModule { } 15 | -------------------------------------------------------------------------------- /src/app/signin/signin.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/signin/signin.component.css -------------------------------------------------------------------------------- /src/app/signin/signin.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

{{'signin' }}

6 |
7 |
8 |
9 |
10 | 11 | 12 | 15 |
16 |
17 | 18 | 19 | 22 |
23 |
24 | 25 |
26 |
27 |
28 | 31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /src/app/signin/signin.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { SigninComponent } from './signin.component'; 5 | 6 | describe('Component: Signin', () => { 7 | it('should create an instance', () => { 8 | let component = new SigninComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/signin/signin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | 4 | import { AuthService } from '../core/auth.service'; 5 | 6 | @Component({ 7 | selector: 'app-signin', 8 | templateUrl: './signin.component.html', 9 | styleUrls: ['./signin.component.css'] 10 | }) 11 | export class SigninComponent implements OnInit, OnDestroy { 12 | 13 | data = { 14 | username: '', 15 | password: '' 16 | }; 17 | 18 | sub: Subscription; 19 | 20 | constructor(private authServcie: AuthService) { } 21 | 22 | ngOnInit() { } 23 | 24 | submit() { 25 | console.log('signin with credentials:' + this.data); 26 | this 27 | .authServcie 28 | .attempAuth('signin', this.data); 29 | 30 | } 31 | 32 | ngOnDestroy() { 33 | //if (this.sub) { this.sub.unsubscribe(); } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/app/signin/signin.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { SharedModule } from '../shared/shared.module'; 3 | import { SigninRoutingModule } from './signin-routing.module'; 4 | import { SigninComponent } from './signin.component'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | SharedModule, 9 | SigninRoutingModule 10 | ], 11 | declarations: [SigninComponent] 12 | }) 13 | export class SigninModule { } 14 | -------------------------------------------------------------------------------- /src/app/signup/signup-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { SignupComponent } from './signup.component'; 5 | 6 | const routes: Routes = [ 7 | { path: '', component: SignupComponent } 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forChild(routes)], 12 | exports: [RouterModule], 13 | providers: [] 14 | }) 15 | export class SignupRoutingModule { } 16 | -------------------------------------------------------------------------------- /src/app/signup/signup.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/app/signup/signup.component.css -------------------------------------------------------------------------------- /src/app/signup/signup.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

{{ 'signup' }}

6 |
7 |
8 |
9 |
10 |
11 |
12 | 13 | 14 | 17 |
18 |
19 |
20 |
21 | 22 | 23 | 26 |
27 |
28 |
29 | 30 |
31 | 32 | 33 | 34 | 38 |
39 | 40 |
41 | 42 | 43 | 48 |
49 |
50 |
51 |
52 | 53 | 54 | 58 |
59 |
60 |
61 |
62 | 63 | 64 | 68 |
69 |
70 |
71 | 74 |
75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 | 85 |
86 |
87 |
88 | -------------------------------------------------------------------------------- /src/app/signup/signup.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { SignupComponent } from './signup.component'; 5 | 6 | xdescribe('Component: Signup', () => { 7 | xit('should create an instance', () => { 8 | // let component = new SignupComponent(); 9 | // expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/signup/signup.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; 3 | import { AuthService } from '../core/auth.service'; 4 | 5 | @Component({ 6 | selector: 'app-signup', 7 | templateUrl: './signup.component.html', 8 | styleUrls: ['./signup.component.css'] 9 | }) 10 | export class SignupComponent implements OnInit { 11 | 12 | signupForm: FormGroup; 13 | firstName: FormControl; 14 | lastName: FormControl; 15 | email: FormControl; 16 | username: FormControl; 17 | password: FormControl; 18 | passwordConfirm: FormControl; 19 | passwordGroup: FormGroup; 20 | 21 | /* 22 | * const form = new FormGroup({ 23 | * password: new FormControl('', Validators.minLength(2)), 24 | * passwordConfirm: new FormControl('', Validators.minLength(2)), 25 | * }, passwordMatchValidator); 26 | * 27 | * 28 | * function passwordMatchValidator(g: FormGroup) { 29 | * return g.get('password').value === g.get('passwordConfirm').value 30 | * ? null : {'mismatch': true}; 31 | * } 32 | */ 33 | // data = { username: '', password: '', firstName: '', lastName: '' }; 34 | 35 | constructor(private authService: AuthService, private fb: FormBuilder) { 36 | this.firstName = new FormControl('', [Validators.required]); 37 | this.lastName = new FormControl('', [Validators.required]); 38 | this.email = new FormControl('', [Validators.required]); 39 | this.username = new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(20)]); 40 | this.password = new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(20)]); 41 | this.passwordConfirm = new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(20)]); 42 | 43 | this.passwordGroup = fb.group( 44 | { 45 | password: this.password, 46 | passwordConfirm: this.passwordConfirm 47 | }, 48 | { validator: this.passwordMatchValidator } 49 | ); 50 | 51 | this.signupForm = fb.group({ 52 | firstName: this.firstName, 53 | lastName: this.lastName, 54 | email: this.email, 55 | username: this.username, 56 | passwordGroup: this.passwordGroup 57 | }); 58 | } 59 | 60 | passwordMatchValidator(g: FormGroup) { 61 | return g.get('password').value === g.get('passwordConfirm').value 62 | ? null : { 'mismatch': true }; 63 | } 64 | 65 | // validateEmail(c: FormControl) { 66 | // let EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 67 | 68 | // return EMAIL_REGEXP.test(c.value) ? null : { 69 | // validateEmail: { 70 | // valid: false 71 | // } 72 | // }; 73 | // } 74 | 75 | ngOnInit() { 76 | } 77 | 78 | submit() { 79 | console.log('saving signup form data@' + this.signupForm.value); 80 | let value = this.signupForm.value; 81 | let data = { 82 | firstName: value.firstName, 83 | lastName: value.lastName, 84 | username: value.username, 85 | password: value.passwordGroup.password 86 | }; 87 | this.authService.attempAuth('signup', data); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/app/signup/signup.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { SharedModule } from '../shared/shared.module'; 3 | import { SignupRoutingModule } from './signup-routing.module'; 4 | import { SignupComponent } from './signup.component'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | SharedModule, 9 | SignupRoutingModule 10 | ], 11 | declarations: [SignupComponent] 12 | }) 13 | export class SignupModule { } 14 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/assets/.npmignore -------------------------------------------------------------------------------- /src/assets/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": "Posts", 3 | "new-post": "New Post", 4 | "welcome-back":"Welcome back,{{name}}" 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/i18n/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": "博文", 3 | "new-post": "新建博文", 4 | "welcome-back":"欢迎回来,{{name}}" 5 | } 6 | -------------------------------------------------------------------------------- /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/hantsy/angular2-sample/2db972a8a875e2aef0fd6ea160934f073d69d6a5/src/favicon.ico -------------------------------------------------------------------------------- /src/forms.css: -------------------------------------------------------------------------------- 1 | .ng-valid[required], .ng-valid.required { 2 | border-left: 5px solid #42A948; 3 | /* green */ 4 | } 5 | 6 | .ng-invalid:not(form){ 7 | border-left: 5px solid #a94442; 8 | /* red */ 9 | } 10 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular2Sample 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | Loading... 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /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 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/set'; 35 | 36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 37 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 38 | 39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 41 | 42 | 43 | /** Evergreen browsers require these. **/ 44 | import 'core-js/es6/reflect'; 45 | import 'core-js/es7/reflect'; 46 | 47 | 48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | /* The html and body elements cannot have any padding or margin. */ 5 | } 6 | 7 | /* Wrapper for page content to push down footer */ 8 | #wrap { 9 | min-height: 100%; 10 | height: auto !important; 11 | height: 100%; 12 | /* Negative indent footer by its height */ 13 | margin: 0 auto -60px; 14 | /* Pad bottom by footer height */ 15 | padding: 0 0 60px; 16 | } 17 | 18 | /* Set the fixed height of the footer here */ 19 | #footer { 20 | height: 60px; 21 | background-color: #f5f5f5; 22 | } 23 | 24 | 25 | /* Custom page CSS 26 | -------------------------------------------------- */ 27 | /* Not required for template or sticky footer method. */ 28 | 29 | #wrap > .container { 30 | padding: 60px 15px 0; 31 | } 32 | .container .credit { 33 | margin: 20px 0; 34 | } 35 | 36 | #footer > .container { 37 | padding-left: 15px; 38 | padding-right: 15px; 39 | } 40 | 41 | code { 42 | font-size: 80%; 43 | } 44 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 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 | const 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.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [true, "rxjs"], 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | "static-before-instance", 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-variable": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": [true, "ignore-params"], 50 | "no-shadowed-variable": true, 51 | "no-string-literal": false, 52 | "no-string-throw": true, 53 | "no-switch-case-fall-through": true, 54 | "no-trailing-whitespace": true, 55 | "no-unused-expression": true, 56 | "no-use-before-declare": true, 57 | "no-var-keyword": true, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-whitespace" 65 | ], 66 | "prefer-const": true, 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "radix": true, 72 | "semicolon": [ 73 | "always" 74 | ], 75 | "triple-equals": [ 76 | true, 77 | "allow-null-check" 78 | ], 79 | "typedef-whitespace": [ 80 | true, 81 | { 82 | "call-signature": "nospace", 83 | "index-signature": "nospace", 84 | "parameter": "nospace", 85 | "property-declaration": "nospace", 86 | "variable-declaration": "nospace" 87 | } 88 | ], 89 | "typeof-compare": true, 90 | "unified-signatures": true, 91 | "variable-name": false, 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ], 100 | 101 | "directive-selector": [true, "attribute", "app", "camelCase"], 102 | "component-selector": [true, "element", "app", "kebab-case"], 103 | "use-input-property-decorator": true, 104 | "use-output-property-decorator": true, 105 | "use-host-property-decorator": true, 106 | "no-input-rename": true, 107 | "no-output-rename": true, 108 | "use-life-cycle-interface": true, 109 | "use-pipe-transform-interface": true, 110 | "component-class-suffix": true, 111 | "directive-class-suffix": true, 112 | "no-access-missing-member": true, 113 | "templates-use-public": true, 114 | "invoke-injectable": true 115 | } 116 | } 117 | --------------------------------------------------------------------------------