├── .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 |
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 |
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 |
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 | //
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
7 |
8 |
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 |
7 |
8 |
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 |
--------------------------------------------------------------------------------