Commit 5f26f0d7 authored by jakkree kongpha's avatar jakkree kongpha

Create ProjectTopic1-63

parent 68e0be9e
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
# Heroes
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.2.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"heroes": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/heroes",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "heroes:build"
},
"configurations": {
"production": {
"browserTarget": "heroes:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "heroes:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "heroes:serve"
},
"configurations": {
"production": {
"devServerTarget": "heroes:serve:production"
}
}
}
}
}},
"defaultProject": "heroes"
}
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};
\ No newline at end of file
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('heroes app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/heroes'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
This diff is collapsed.
{
"name": "heroes",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~10.0.3",
"@angular/common": "~10.0.3",
"@angular/compiler": "~10.0.3",
"@angular/core": "~10.0.3",
"@angular/forms": "~10.0.3",
"@angular/platform-browser": "~10.0.3",
"@angular/platform-browser-dynamic": "~10.0.3",
"@angular/router": "~10.0.3",
"rxjs": "~6.5.5",
"tslib": "^2.0.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1000.2",
"@angular/cli": "~10.0.2",
"@angular/compiler-cli": "~10.0.3",
"@types/node": "^12.11.1",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~3.3.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~3.9.5"
}
}
<h1>{{title}}</h1>
<app-heroes></app-heroes>
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes li.selected {
background-color: #CFD8DC;
color: white;
}
.heroes li.selected:hover {
background-color: #BBD8DC;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'heroes'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('heroes');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('heroes app is running!');
});
});
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'heroes';
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';
@NgModule({
declarations: [
AppComponent,
HeroesComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
export interface Hero {
id: number;
name: string;
hp: number;
}
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span>
<span>{{hero.name}} - </span>
<span class="badge">{{hero.hp}} Hp</span>
</li>
</ul>
<div *ngIf="selectedHero">
<h2>{{selectedHero.name | uppercase}} {{selectedHero.hp }} Details</h2>
<div><span>id: </span>{{selectedHero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="selectedHero.name" placeholder="name" />
</label>
<label>hp:
<input [(ngModel)]="selectedHero.hp" placeholder="Hp" />
</label>
</div>
</div>
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes li.selected {
background-color: #CFD8DC;
color: white;
}
.heroes li.selected:hover {
background-color: #BBD8DC;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroesComponent } from './heroes.component';
describe('HeroesComponent', () => {
let component: HeroesComponent;
let fixture: ComponentFixture<HeroesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeroesComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HeroesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.scss'],
})
export class HeroesComponent implements OnInit {
heroes = HEROES;
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
constructor() {}
ngOnInit() {}
}
import { Hero } from './hero';
export const HEROES: Hero[] = [
{ id: 11, name: 'Dr Nice', hp: 100 },
{ id: 12, name: 'Narco', hp: 100 },
{ id: 13, name: 'Bombasto', hp: 100 },
{ id: 14, name: 'Celeritas', hp: 100 },
{ id: 15, name: 'Magneta', hp: 100 },
{ id: 16, name: 'RubberMan', hp: 100 },
{ id: 17, name: 'Dynama', hp: 100 },
{ id: 18, name: 'Dr IQ', hp: 100 },
{ id: 19, name: 'Magma', hp: 100 },
{ id: 20, name: 'Tornado', hp: 100 },
];
export const environment = {
production: true
};
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Heroes</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/* You can add global styles to this file, and also import other style files */
/* Application-wide Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[type="text"], button {
color: #333;
font-family: Cambria, Georgia;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
}
}
/*
This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience.
It is not intended to be used to perform a compilation.
To learn more about this file see: https://angular.io/config/solution-tsconfig.
*/
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./e2e/tsconfig.json"
}
]
}
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
{
"extends": "tslint:recommended",
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef": [
true,
"call-signature"
],
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
},
"rulesDirectory": [
"codelyzer"
]
}
\ No newline at end of file
.git/
dist/
examples/
node_modules/
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
NODE_ENV=development
SERVER_PORT=4040
JWT_SECRET=0a6b944d-d2fb-46fc-a85e-0295c986cd9f
MONGO_HOST=mongodb://localhost/mean
MEAN_FRONTEND=angular
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/dist-server
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db
# Env file
*.env
FROM node:lts-buster
WORKDIR /usr/src/app
ADD . /usr/src/app
RUN yarn
RUN yarn build
EXPOSE 4040
CMD ["yarn", "serve"]
## Welcome to the mean stack
The mean stack is intended to provide a simple and fun starting point for cloud native fullstack javascript applications.
MEAN is a set of Open Source components that together, provide an end-to-end framework for building dynamic web applications; starting from the top (code running in the browser) to the bottom (database). The stack is made up of:
- **M**ongoDB : Document database – used by your back-end application to store its data as JSON (JavaScript Object Notation) documents
- **E**xpress (sometimes referred to as Express.js): Back-end web application framework running on top of Node.js
- **A**ngular (formerly Angular.js): Front-end web app framework; runs your JavaScript code in the user's browser, allowing your application UI to be dynamic
- **N**ode.js : JavaScript runtime environment – lets you implement your application back-end in JavaScript
### Pre-requisites
* git - [Installation guide](https://www.linode.com/docs/development/version-control/how-to-install-git-on-linux-mac-and-windows/) .
* node.js - [Download page](https://nodejs.org/en/download/) .
* npm - comes with node or download yarn - [Download page](https://yarnpkg.com/lang/en/docs/install) .
* mongodb - [Download page](https://www.mongodb.com/download-center/community) .
### Installation
```
git clone https://github.com/linnovate/mean
cd mean
cp .env.example .env
yarn
yarn start (for development)
```
### Docker based
```
git clone https://github.com/linnovate/mean
cd mean
cp .env.example .env
docker-compose up -d
```
### Credits
- The MEAN name was coined by Valeri Karpov.
- Initial concept and development was done by Amos Haviv and sponsered by Linnovate.
- Inspired by the great work of Madhusudhan Srinivasa.
theme: jekyll-theme-minimal
logo: https://www.linnovate.net/sites/all/themes/linnovate/images/mean-picture.png
<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% seo %}
<link rel="stylesheet" href="{{ "/assets/css/style.css?v=" | append: site.github.build_revision | relative_url }}">
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
<![endif]-->
</head>
<body>
<div class="wrapper">
<header>
{% if site.logo %}
<img class="logo" src="{{site.logo | relative_url}}" alt="Logo" />
{% endif %}
<p>{{ site.description | default: site.github.project_tagline }}</p>
{% if site.github.is_project_page %}
<p class="view"><a href="{{ site.github.repository_url }}">View the Project on GitHub <small>{{ site.github.repository_nwo }}</small></a></p>
{% endif %}
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/linnovate/mean" data-show-count="true" aria-label="Star ntkme/github-buttons on GitHub">Star</a>
{% if site.github.is_user_page %}
<p class="view"><a href="{{ site.github.owner_url }}">View My GitHub Profile</a></p>
{% endif %}
{% if site.show_downloads %}
<ul class="downloads">
<li><a href="{{ site.github.zip_url }}">Download <strong>ZIP File</strong></a></li>
<li><a href="{{ site.github.tar_url }}">Download <strong>TAR Ball</strong></a></li>
<li><a href="{{ site.github.repository_url }}">View On <strong>GitHub</strong></a></li>
</ul>
{% endif %}
<img class="ninja" src="/assets/img/ninja.jpg"/>
</header>
<section>
{{ content }}
{% if site.github.is_project_page %}
<p>This project is maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
{% endif %}
</section>
</div>
<script src="{{ "/assets/js/scale.fix.js" | relative_url }}"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-36499287-4"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
// gtag('config', 'UA-XXXXXX-XX'); // change this to your own UA config
</script>
<!-- Place this tag in your head or just before your close body tag. -->
<script async defer src="https://buttons.github.io/buttons.js"></script>
</body>
</html>
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"mean": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "./tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
"src/favicon.ico"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "mean:build"
},
"configurations": {
"production": {
"browserTarget": "mean:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "mean:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "./tsconfig.spec.json",
"scripts": [],
"styles": [
"src/styles.scss"
],
"assets": [
"src/assets",
"src/favicon.ico"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"./tsconfig.app.json",
"./tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "mean",
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
"styleext": "scss"
},
"@schematics/angular:directive": {
"prefix": "app"
}
}
}
---
---
@import "{{ site.theme }}";
h1 {
a {
color:#00758f;
text-size:40px;
}
}
header {
img.logo {
margin-left:30px;
display:block;
height: auto;
width: auto;
max-width: 150px;
max-height: 200px;
margin-bottom: 17%;
}
img.ninja {
margin-top: 20px;
height: auto;
width: auto;
max-width: 350px;
max-height: 200px;
margin-bottom: 50px;
}
}
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11
version: '3'
services:
app:
build: ./
image: mean
ports:
- 80:4040
expose:
- 4040
environment:
NODE_ENV: production
SERVER_PORT: 4040
JWT_SECRET: 0a6b944d-d2fb-46fc-a85e-0295c986cd9f
MONGO_HOST: mongodb://mongo/odmp
restart: always
depends_on:
- mongo
mongo:
image: mongo:3.6
mongo-express:
image: mongo-express
restart: always
depends_on:
- mongo
ports:
- 8081:8081
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
{
"name": "mean",
"version": "2.0.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"yarn": {
"version": "1.22.5",
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.5.tgz",
"integrity": "sha512-5uzKXwdMc++mYktXqkfpNYT9tY8ViWegU58Hgbo+KXzrzzhEyP1Ip+BTtXloLrXNcNlxFJbLiFKGaS9vK9ym6Q=="
}
}
}
{
"name": "mean",
"version": "2.0.2",
"license": "MIT",
"scripts": {
"ng": "ng",
"serve": "node server",
"start": "concurrently -c \"yellow.bold,green.bold\" -n \"SERVER,BUILD\" \"nodemon server\" \"ng build --watch\"",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points"
},
"private": true,
"dependencies": {
"@angular/animations": "^9.1.4",
"@angular/cdk": "^9.2.1",
"@angular/common": "^9.1.4",
"@angular/compiler": "^9.1.4",
"@angular/core": "^9.1.4",
"@angular/forms": "^9.1.4",
"@angular/material": "^9.2.1",
"@angular/platform-browser": "^9.1.4",
"@angular/platform-browser-dynamic": "^9.1.4",
"@angular/router": "^9.1.4",
"bcrypt": "^3.0.2",
"body-parser": "^1.18.2",
"compression": "^1.7.2",
"cookie-parser": "^1.4.3",
"cors": "^2.8.4",
"dotenv": "^6.0.0",
"events": "^3.0.0",
"express": "^4.16.3",
"express-async-handler": "^1.1.3",
"express-jwt": "^5.3.1",
"express-validation": "^1.0.2",
"formidable": "^1.2.1",
"helmet": "^3.21.1",
"http-errors": "^1.6.3",
"joi": "^13.3.0",
"jsonwebtoken": "^8.2.1",
"method-override": "^2.3.10",
"mongoose": "^5.7.5",
"morgan": "^1.9.1",
"nodemon": "^1.17.5",
"passport": "^0.4.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"rxjs": "^6.5.5",
"swagger-ui-express": "^3.0.9",
"yarn": "^1.22.5",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.4",
"@angular/cli": "^9.1.4",
"@angular/compiler-cli": "^9.1.4",
"@angular/language-service": "^9.1.4",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~12.12.14",
"codelyzer": "^5.2.0",
"concurrently": "^3.5.1",
"jasmine-core": "~3.1.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "^3.1.3",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "2.0.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "1.1.0",
"ts-node": "~6.1.0",
"tslint": "^5.20.1",
"typescript": "3.8.3"
}
}
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
const Joi = require('joi');
// require and configure dotenv, will load vars in .env in PROCESS.ENV
require('dotenv').config();
// define validation for all the env vars
const envVarsSchema = Joi.object({
NODE_ENV: Joi.string()
.allow(['development', 'production', 'test', 'provision'])
.default('development'),
SERVER_PORT: Joi.number()
.default(4040),
MONGOOSE_DEBUG: Joi.boolean()
.when('NODE_ENV', {
is: Joi.string().equal('development'),
then: Joi.boolean().default(true),
otherwise: Joi.boolean().default(false)
}),
JWT_SECRET: Joi.string().required()
.description('JWT Secret required to sign'),
MONGO_HOST: Joi.string().required()
.description('Mongo DB host url'),
MONGO_PORT: Joi.number()
.default(27017)
}).unknown()
.required();
const { error, value: envVars } = Joi.validate(process.env, envVarsSchema);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
const config = {
env: envVars.NODE_ENV,
port: envVars.SERVER_PORT,
mongooseDebug: envVars.MONGOOSE_DEBUG,
jwtSecret: envVars.JWT_SECRET,
frontend: envVars.MEAN_FRONTEND || 'angular',
mongo: {
host: envVars.MONGO_HOST,
port: envVars.MONGO_PORT
}
};
module.exports = config;
const path = require('path');
const express = require('express');
const httpError = require('http-errors');
const logger = require('morgan');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const compress = require('compression');
const methodOverride = require('method-override');
const cors = require('cors');
const helmet = require('helmet');
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
const routes = require('../routes/index.route');
const config = require('./config');
const passport = require('./passport')
const app = express();
if (config.env === 'development') {
app.use(logger('dev'));
}
// Choose what fronten framework to serve the dist from
var distDir = '../../dist/';
if (config.frontend == 'react'){
distDir ='../../node_modules/material-dashboard-react/dist'
}else{
distDir ='../../dist/' ;
}
//
app.use(express.static(path.join(__dirname, distDir)))
app.use(/^((?!(api)).)*/, (req, res) => {
res.sendFile(path.join(__dirname, distDir + '/index.html'));
});
console.log(distDir);
//React server
app.use(express.static(path.join(__dirname, '../../node_modules/material-dashboard-react/dist')))
app.use(/^((?!(api)).)*/, (req, res) => {
res.sendFile(path.join(__dirname, '../../dist/index.html'));
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(compress());
app.use(methodOverride());
// secure apps by setting various HTTP headers
app.use(helmet());
// enable CORS - Cross Origin Resource Sharing
app.use(cors());
app.use(passport.initialize());
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// API router
app.use('/api/', routes);
// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new httpError(404)
return next(err);
});
// error handler, send stacktrace only during development
app.use((err, req, res, next) => {
// customize Joi validation errors
if (err.isJoi) {
err.message = err.details.map(e => e.message).join("; ");
err.status = 400;
}
res.status(err.status || 500).json({
message: err.message
});
next(err);
});
module.exports = app;
const mongoose = require('mongoose');
const util = require('util');
const debug = require('debug')('express-mongoose-es6-rest-api:index');
const config = require('./config');
// connect to mongo db
const mongoUri = config.mongo.host;
mongoose.connect(mongoUri, { keepAlive: 1 });
mongoose.connection.on('error', () => {
throw new Error(`unable to connect to database: ${mongoUri}`);
});
// print mongoose logs in dev env
if (config.MONGOOSE_DEBUG) {
mongoose.set('debug', (collectionName, method, query, doc) => {
debug(`${collectionName}.${method}`, util.inspect(query, false, 20), doc);
});
}
const passport = require('passport');
const LocalStrategy = require('passport-local');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const bcrypt = require('bcrypt');
const User = require('../models/user.model');
const config = require('./config');
const localLogin = new LocalStrategy({
usernameField: 'email'
}, async (email, password, done) => {
let user = await User.findOne({ email });
if (!user || !bcrypt.compareSync(password, user.hashedPassword)) {
return done(null, false, { error: 'Your login details could not be verified. Please try again.' });
}
user = user.toObject();
delete user.hashedPassword;
done(null, user);
});
const jwtLogin = new JwtStrategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.jwtSecret
}, async (payload, done) => {
let user = await User.findById(payload._id);
if (!user) {
return done(null, false);
}
user = user.toObject();
delete user.hashedPassword;
done(null, user);
});
passport.use(jwtLogin);
passport.use(localLogin);
module.exports = passport;
{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Mean Application API",
"description": "Mean Application API",
"license": {
"name": "MIT",
"url": "https://opensource.org/licenses/MIT"
}
},
"host": "localhost:4040",
"basePath": "/api/",
"tags": [
{
"name": "Users",
"description": "API for users in the system"
},
{
"name": "Auth",
"description": "API for auth in the system"
}
],
"schemes": [
"http"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"securityDefinitions": {
"AuthHeader": {
"type": "apiKey",
"in": "header",
"name": "Authorization"
}
},
"paths": {
"/auth/login": {
"post": {
"tags": ["Auth"],
"description": "Login to the system",
"parameters": [{
"name": "auth",
"in": "body",
"description": "User auth details",
"schema": {
"type": "object",
"required": ["email", "password"],
"properties": {
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
}
}],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "User is loggedin",
"schema": {
"$ref": "#/definitions/User"
}
}
}
}
}
},
"definitions": {
"User": {
"required": [
"email",
"fullname"
],
"properties": {
"_id": {
"type": "string",
"uniqueItems": true
},
"email": {
"type": "string",
"uniqueItems": true
},
"fullname": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"roles": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"Users": {
"type": "array",
"$ref": "#/definitions/User"
},
"Auth": {
"type": "object",
"properties": [{
"token": {
"type": "string"
},
"user": {
"$ref": "#/definitions/User"
}
}]
}
}
}
\ No newline at end of file
const jwt = require('jsonwebtoken');
const config = require('../config/config');
module.exports = {
generateToken
}
function generateToken(user) {
const payload = JSON.stringify(user);
return jwt.sign(payload, config.jwtSecret);
}
const bcrypt = require('bcrypt');
const Joi = require('joi');
const User = require('../models/user.model');
const userSchema = Joi.object({
fullname: Joi.string().required(),
email: Joi.string().email(),
mobileNumber: Joi.string().regex(/^[1-9][0-9]{9}$/),
password: Joi.string().required(),
repeatPassword: Joi.string().required().valid(Joi.ref('password'))
})
module.exports = {
insert
}
async function insert(user) {
user = await Joi.validate(user, userSchema, { abortEarly: false });
user.hashedPassword = bcrypt.hashSync(user.password, 10);
delete user.password;
return await new User(user).save();
}
// config should be imported before importing any other file
const config = require('./config/config');
const app = require('./config/express');
require('./config/mongoose');
// module.parent check is required to support mocha watch
// src: https://github.com/mochajs/mocha/issues/1912
if (!module.parent) {
app.listen(config.port, () => {
console.info(`server started on port ${config.port} (${config.env})`);
});
}
module.exports = app;
const httpError = require('http-errors');
const requireAdmin = function (req, res, next) {
if (req.user && req.user.roles.indexOf('admin') > -1)
return next();
const err = new httpError(401);
return next(err);
}
module.exports = requireAdmin;
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
fullname: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true,
// Regexp to validate emails with more strict rules as added in tests/users.js which also conforms mostly with RFC2822 guide lines
match: [/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 'Please enter a valid email'],
},
hashedPassword: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
roles: [{
type: String,
}]
}, {
versionKey: false
});
module.exports = mongoose.model('User', UserSchema);
const express = require('express');
const asyncHandler = require('express-async-handler')
const passport = require('passport');
const userCtrl = require('../controllers/user.controller');
const authCtrl = require('../controllers/auth.controller');
const config = require('../config/config');
const router = express.Router();
module.exports = router;
router.post('/register', asyncHandler(register), login);
router.post('/login', passport.authenticate('local', { session: false }), login);
router.get('/me', passport.authenticate('jwt', { session: false }), login);
async function register(req, res, next) {
let user = await userCtrl.insert(req.body);
user = user.toObject();
delete user.hashedPassword;
req.user = user;
next()
}
function login(req, res) {
let user = req.user;
let token = authCtrl.generateToken(user);
res.json({ user, token });
}
const express = require('express');
const userRoutes = require('./user.route');
const authRoutes = require('./auth.route');
const router = express.Router(); // eslint-disable-line new-cap
/** GET /health-check - Check service health */
router.get('/health-check', (req, res) =>
res.send('OK')
);
router.use('/auth', authRoutes);
router.use('/user', userRoutes);
module.exports = router;
const express = require('express');
const passport = require('passport');
const asyncHandler = require('express-async-handler');
const userCtrl = require('../controllers/user.controller');
const router = express.Router();
module.exports = router;
router.use(passport.authenticate('jwt', { session: false }))
router.route('/')
.post(asyncHandler(insert));
async function insert(req, res) {
let user = await userCtrl.insert(req.body);
res.json(user);
}
/*-----------------------------------------------
Variables
-----------------------------------------------*/
$linesColor: #dbdbdb;
$categoryTitleColor: #686868;
$categoryEntityColor: #3F3F3F;
\ No newline at end of file
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminComponent } from './admin.component';
import { OnlyAdminUsersGuard } from './admin-user-guard';
const routes: Routes = [{
path: 'admin',
canActivate: [OnlyAdminUsersGuard],
children: [{
path: '',
component: AdminComponent,
}]
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule {}
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '@app/shared/services';
@Injectable()
export class OnlyAdminUsersGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate(): Observable<boolean> {
return this.authService.getUser().pipe(map(user => !!user?.isAdmin));
}
}
<h4>HELLO FROM ADMIN PAGE</h4>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
})
export class AdminComponent implements OnInit {
constructor() {}
public ngOnInit() {
}
}
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module';
import {AdminComponent} from './admin.component';
import {OnlyAdminUsersGuard} from './admin-user-guard';
@NgModule({
declarations: [
AdminComponent
],
imports: [
CommonModule,
AdminRoutingModule,
],
providers: [
OnlyAdminUsersGuard
]})
export class AdminModule {}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './shared/guards';
import { HomeComponent } from './home/home.component';
const routes: Routes = [
{
path: '',
component: HomeComponent,
canActivate: [AuthGuard],
},
{
path: 'auth',
loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule),
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
<app-header [user]="user$ | async"></app-header>
<div class="wrapper-app">
<router-outlet></router-outlet>
</div>
<footer></footer>
.wrapper-app {
}
\ No newline at end of file
import { Component } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { merge, Observable } from 'rxjs';
import { User } from './shared/interfaces';
import { AuthService } from './shared/services';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
user$: Observable<User | null> = merge(
// Init on startup
this.authService.me(),
// Update after login/register/logout
this.authService.getUser()
);
constructor(
private domSanitizer: DomSanitizer,
private matIconRegistry: MatIconRegistry,
private authService: AuthService
) {
this.registerSvgIcons();
}
registerSvgIcons() {
[
'close',
'add',
'add-blue',
'airplane-front-view',
'air-station',
'balloon',
'boat',
'cargo-ship',
'car',
'catamaran',
'clone',
'convertible',
'delete',
'drone',
'fighter-plane',
'fire-truck',
'horseback-riding',
'motorcycle',
'railcar',
'railroad-train',
'rocket-boot',
'sailing-boat',
'segway',
'shuttle',
'space-shuttle',
'steam-engine',
'suv',
'tour-bus',
'tow-truck',
'transportation',
'trolleybus',
'water-transportation',
].forEach(icon => {
this.matIconRegistry.addSvgIcon(
icon,
this.domSanitizer.bypassSecurityTrustResourceUrl(`assets/icons/${icon}.svg`)
);
});
}
}
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { SharedModule } from './shared/shared.module';
import { AppComponent } from './app.component';
import { AuthHeaderInterceptor } from './interceptors/header.interceptor';
import { CatchErrorInterceptor } from './interceptors/http-error.interceptor';
import { AppRoutingModule } from './app-routing.module';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
import { AuthService } from './shared/services';
export function appInitializerFactory(authService: AuthService) {
return () => authService.checkTheUserOnTheFirstLoad();
}
@NgModule({
imports: [BrowserAnimationsModule, HttpClientModule, SharedModule, AppRoutingModule],
declarations: [AppComponent, HeaderComponent, HomeComponent],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthHeaderInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: CatchErrorInterceptor,
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: appInitializerFactory,
multi: true,
deps: [AuthService],
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
const routes: Routes = [
{
path: '',
children: [
{
path: '',
redirectTo: '/auth/login',
pathMatch: 'full',
},
{
path: 'login',
component: LoginComponent,
},
{
path: 'register',
component: RegisterComponent,
},
],
},
];
export const AuthRoutingModule = RouterModule.forChild(routes);
.example-icon {
padding: 0 14px;
}
.example-spacer {
flex: 1 1 auto;
}
.example-card {
width: 400px;
margin: 10% auto;
}
.mat-card-title {
font-size: 16px;
}
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { AuthRoutingModule } from './auth-routing.module';
@NgModule({
imports: [SharedModule, AuthRoutingModule],
declarations: [LoginComponent, RegisterComponent],
})
export class AuthModule {}
<mat-card class="example-card">
<mat-card-header>
<mat-card-title>Login</mat-card-title>
</mat-card-header>
<mat-card-content>
<form class="example-form">
<table cellspacing="0">
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Email" [(ngModel)]="email" name="email" required>
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Password" [(ngModel)]="password" type="password" name="password" required>
</mat-form-field>
</td>
</tr>
</table>
</form>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button (click)="login()" color="primary">Login</button>
<span>Don't have an account ? <a [routerLink]="['/auth/register']" >register</a> here</span>
</mat-card-actions>
</mat-card>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '@app/shared/services';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['../auth.component.scss'],
})
export class LoginComponent {
email: string | null = null;
password: string | null = null;
constructor(private router: Router, private authService: AuthService) {}
login(): void {
this.authService.login(this.email!, this.password!).subscribe(() => {
this.router.navigateByUrl('/');
});
}
}
<mat-card class="example-card">
<mat-card-header>
<mat-card-title>Register</mat-card-title>
</mat-card-header>
<mat-card-content>
<form class="example-form">
<table cellspacing="0" [formGroup]="userForm">
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Fullname" formControlName="fullname" name="fullname" required>
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Email" formControlName="email" name="email" required>
<mat-error *ngIf="email.invalid && email.hasError('email')">Invalid email address</mat-error>
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Password" formControlName="password" type="password" name="password" required>
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Repeat Password" formControlName="repeatPassword" type="password" name="repeatPassword" required>
<mat-error *ngIf="repeatPassword.invalid && repeatPassword.hasError('passwordMatch')">Password mismatch</mat-error>
</mat-form-field>
</td>
</tr>
</table>
</form>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button (click)="register()" color="primary">Register</button>
<span>Allrady have an account ? <a [routerLink]="['/auth/login']">login</a> here</span>
</mat-card-actions>
</mat-card>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RegisterComponent } from './register.component';
describe('RegisterComponent', () => {
let component: RegisterComponent;
let fixture: ComponentFixture<RegisterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RegisterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RegisterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import {
FormGroup,
FormControl,
Validators,
ValidationErrors,
AbstractControl,
} from '@angular/forms';
import { AuthService } from '@app/shared/services';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['../auth.component.scss'],
})
export class RegisterComponent {
constructor(private router: Router, private authService: AuthService) {}
passwordsMatchValidator(control: FormControl): ValidationErrors | null {
const password = control.root.get('password');
return password && control.value !== password.value
? {
passwordMatch: true,
}
: null;
}
userForm = new FormGroup({
fullname: new FormControl('', [Validators.required]),
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required]),
repeatPassword: new FormControl('', [Validators.required, this.passwordsMatchValidator]),
});
get fullname(): AbstractControl {
return this.userForm.get('fullname')!;
}
get email(): AbstractControl {
return this.userForm.get('email')!;
}
get password(): AbstractControl {
return this.userForm.get('password')!;
}
get repeatPassword(): AbstractControl {
return this.userForm.get('repeatPassword')!;
}
register(): void {
if (this.userForm.invalid) {
return;
}
const { fullname, email, password, repeatPassword } = this.userForm.getRawValue();
this.authService.register(fullname, email, password, repeatPassword).subscribe(data => {
this.router.navigate(['']);
});
}
}
<header>
<mat-toolbar color="primary">
<a routerLink="/" class="logo"></a>
<span class="example-spacer"></span>
<a class="links side" routerLink="/auth/login" *ngIf="!user">Login</a>
<div>
<a class="links side" *ngIf="user" [matMenuTriggerFor]="menu">
<mat-icon>account_circle</mat-icon>{{ user.fullname }}
</a>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngIf="user?.isAdmin" routerLink="/admin">admin</button>
<button mat-menu-item (click)="logout()">logout</button>
</mat-menu>
</div>
</mat-toolbar>
</header>
header {
width: 100%;
.logo {
background-image: url('../../assets/logo.png');
width: 50px;
height: 50px;
background-size: contain;
background-repeat: no-repeat;
}
.example-spacer {
flex: 1 1 auto;
}
.links {
color: white;
font-family: 'Helvetica Neue', sans-serif;
font-size: 15px;
font-weight: initial;
letter-spacing: -1px;
line-height: 1;
text-align: center;
padding: 15px;
&.side {
padding: 0 14px;
}
}
.mat-toolbar {
background: black;
}
.mat-icon {
vertical-align: middle;
margin: 0 5px;
}
a {
cursor: pointer;
}
}
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input } from '@angular/core';
import { Router } from '@angular/router';
import { User } from '@app/shared/interfaces';
import { AuthService } from '@app/shared/services';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent {
@Input() user: User | null = null;
constructor(private router: Router, private authService: AuthService) {}
logout(): void {
this.authService.signOut();
this.router.navigateByUrl('/auth/login');
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from '@app/shared/services';
@Injectable()
export class AuthHeaderInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
setHeaders: this.authService.getAuthorizationHeaders(),
});
return next.handle(req);
}
}
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpErrorResponse,
} from '@angular/common/http';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class CatchErrorInterceptor implements HttpInterceptor {
constructor(private snackBar: MatSnackBar) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(catchError(this.showSnackBar));
}
private showSnackBar = (response: HttpErrorResponse): Observable<never> => {
const text: string | undefined = response.error?.message ?? response.error.statusText;
if (text) {
this.snackBar.open(text, 'Close', {
duration: 2000,
});
}
return throwError(response);
};
}
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '../services';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) {}
canActivate(): Observable<boolean> {
return this.authService.getUser().pipe(
map(user => {
if (user !== null) {
return true;
}
this.router.navigateByUrl('/auth/login');
return false;
})
);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment