mirror of
https://github.com/nestjs/docs.nestjs.com.git
synced 2026-02-21 20:31:32 +00:00
Initial commit
This commit is contained in:
62
.angular-cli.json
Normal file
62
.angular-cli.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "scalio-base2-front"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"styles.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"assets/js/prism.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "scss",
|
||||
"component": {}
|
||||
}
|
||||
}
|
||||
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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
|
||||
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
/.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
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Mock API
|
||||
/api/node_modules
|
||||
28
README.md
Normal file
28
README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# ScalioBase2Front
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.3.
|
||||
|
||||
## 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|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/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
## 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).
|
||||
14
e2e/app.e2e-spec.ts
Normal file
14
e2e/app.e2e-spec.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DocsPage } from './app.po';
|
||||
|
||||
describe('docs App', () => {
|
||||
let page: DocsPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new DocsPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('Welcome to app!');
|
||||
});
|
||||
});
|
||||
11
e2e/app.po.ts
Normal file
11
e2e/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class DocsPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
||||
14
e2e/tsconfig.e2e.json
Normal file
14
e2e/tsconfig.e2e.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"baseUrl": "./",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
33
karma.conf.js
Normal file
33
karma.conf.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular/cli'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular/cli/plugins/karma')
|
||||
],
|
||||
client:{
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
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
|
||||
});
|
||||
};
|
||||
7620
package-lock.json
generated
Normal file
7620
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
66
package.json
Normal file
66
package.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "scalio-base2-front",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --sourcemap=false",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^4.3.1",
|
||||
"@angular/cdk": "^2.0.0-beta.8",
|
||||
"@angular/common": "^4.3.1",
|
||||
"@angular/compiler": "^4.3.1",
|
||||
"@angular/core": "^4.3.1",
|
||||
"@angular/flex-layout": "^2.0.0-beta.8",
|
||||
"@angular/forms": "^4.3.1",
|
||||
"@angular/http": "^4.3.1",
|
||||
"@angular/material": "^2.0.0-beta.8",
|
||||
"@angular/platform-browser": "^4.3.1",
|
||||
"@angular/platform-browser-dynamic": "^4.3.1",
|
||||
"@angular/router": "^4.3.1",
|
||||
"@ngrx/core": "^1.2.0",
|
||||
"@ngrx/effects": "^4.0.1",
|
||||
"@ngrx/router-store": "^4.0.0",
|
||||
"@ngrx/store": "^4.0.0",
|
||||
"@ngrx/store-devtools": "^4.0.0",
|
||||
"@ultimate/ngxerrors": "^1.3.0",
|
||||
"angular2-highlight-js": "^5.0.2",
|
||||
"core-js": "^2.4.1",
|
||||
"hammerjs": "^2.0.8",
|
||||
"lodash": "^4.17.4",
|
||||
"ng2-prism": "^2.3.2",
|
||||
"ng2-progressbar": "^1.3.0",
|
||||
"ngx-perfect-scrollbar": "^4.5.3",
|
||||
"rxjs": "^5.4.1",
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "1.2.3",
|
||||
"@angular/compiler-cli": "^4.0.0",
|
||||
"@angular/language-service": "^4.0.0",
|
||||
"@types/jasmine": "~2.5.53",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/lodash": "^4.14.71",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "~3.0.1",
|
||||
"concurrently": "^3.5.0",
|
||||
"jasmine-core": "~2.6.2",
|
||||
"jasmine-spec-reporter": "~4.1.0",
|
||||
"karma": "~1.7.0",
|
||||
"karma-chrome-launcher": "~2.1.1",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.1.2",
|
||||
"ts-node": "~3.0.4",
|
||||
"tslint": "~5.3.2",
|
||||
"typescript": "~2.3.3"
|
||||
}
|
||||
}
|
||||
28
protractor.conf.js
Normal file
28
protractor.conf.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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 } }));
|
||||
}
|
||||
};
|
||||
77
src/app/app-routing.module.ts
Normal file
77
src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { environment } from '../environments/environment';
|
||||
import { HomepageComponent } from './homepage/homepage.component';
|
||||
import { IntroductionComponent } from './homepage/pages/introduction/introduction.component';
|
||||
import { FirstStepsComponent } from './homepage/pages/first-steps/first-steps.component';
|
||||
import { ControllersComponent } from './homepage/pages/controllers/controllers.component';
|
||||
import { ComponentsComponent } from './homepage/pages/components/components.component';
|
||||
import { ModulesComponent } from './homepage/pages/modules/modules.component';
|
||||
import { MiddlewaresComponent } from './homepage/pages/middlewares/middlewares.component';
|
||||
import { PipesComponent } from './homepage/pages/pipes/pipes.component';
|
||||
import { GuardsComponent } from './homepage/pages/guards/guards.component';
|
||||
import { ExceptionFiltersComponent } from './homepage/pages/exception-filters/exception-filters.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomepageComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: IntroductionComponent,
|
||||
},
|
||||
{
|
||||
path: 'first-steps',
|
||||
component: FirstStepsComponent,
|
||||
},
|
||||
{
|
||||
path: 'controllers',
|
||||
component: ControllersComponent,
|
||||
},
|
||||
{
|
||||
path: 'components',
|
||||
component: ComponentsComponent,
|
||||
},
|
||||
{
|
||||
path: 'modules',
|
||||
component: ModulesComponent,
|
||||
},
|
||||
{
|
||||
path: 'middlewares',
|
||||
component: MiddlewaresComponent,
|
||||
},
|
||||
{
|
||||
path: 'pipes',
|
||||
component: PipesComponent,
|
||||
},
|
||||
{
|
||||
path: 'guards',
|
||||
component: GuardsComponent,
|
||||
},
|
||||
{
|
||||
path: 'exception-filters',
|
||||
component: ExceptionFiltersComponent,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: '',
|
||||
pathMatch: 'full',
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(
|
||||
routes,
|
||||
{
|
||||
enableTracing: !environment.production,
|
||||
},
|
||||
),
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
0
src/app/app.component.css
Normal file
0
src/app/app.component.css
Normal file
1
src/app/app.component.html
Normal file
1
src/app/app.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
||||
32
src/app/app.component.spec.ts
Normal file
32
src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
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', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
}));
|
||||
});
|
||||
17
src/app/app.component.ts
Normal file
17
src/app/app.component.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(private readonly router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.router.events
|
||||
.filter((ev) => ev instanceof NavigationEnd)
|
||||
.subscribe(() => window.scroll(0, 0));
|
||||
}
|
||||
}
|
||||
59
src/app/app.module.ts
Normal file
59
src/app/app.module.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { HomepageComponent } from './homepage/homepage.component';
|
||||
import { HeaderComponent } from './homepage/header/header.component';
|
||||
import { FooterComponent } from './homepage/footer/footer.component';
|
||||
import { MenuComponent } from './homepage/menu/menu.component';
|
||||
import { MenuItemComponent } from './homepage/menu/menu-item/menu-item.component';
|
||||
import { IntroductionComponent } from './homepage/pages/introduction/introduction.component';
|
||||
import { FirstStepsComponent } from './homepage/pages/first-steps/first-steps.component';
|
||||
import { ControllersComponent } from './homepage/pages/controllers/controllers.component';
|
||||
import { BasePageComponent } from './homepage/pages/page/page.component';
|
||||
import { MatchHeightDirective } from './common/directives/match-height.directive';
|
||||
import { ComponentsComponent } from './homepage/pages/components/components.component';
|
||||
import { ModulesComponent } from './homepage/pages/modules/modules.component';
|
||||
import { MiddlewaresComponent } from './homepage/pages/middlewares/middlewares.component';
|
||||
import { PipesComponent } from './homepage/pages/pipes/pipes.component';
|
||||
import { ExceptionFiltersComponent } from './homepage/pages/exception-filters/exception-filters.component';
|
||||
import { GuardsComponent } from './homepage/pages/guards/guards.component';
|
||||
import { DependencyInjectionComponent } from './homepage/pages/advanced/dependency-injection/dependency-injection.component';
|
||||
import { AsyncComponentsComponent } from './homepage/pages/advanced/async-components/async-components.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
CoreModule,
|
||||
PerfectScrollbarModule.forRoot({
|
||||
suppressScrollX: true,
|
||||
}),
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HomepageComponent,
|
||||
HeaderComponent,
|
||||
FooterComponent,
|
||||
MenuComponent,
|
||||
MenuItemComponent,
|
||||
IntroductionComponent,
|
||||
FirstStepsComponent,
|
||||
ControllersComponent,
|
||||
BasePageComponent,
|
||||
MatchHeightDirective,
|
||||
ComponentsComponent,
|
||||
ModulesComponent,
|
||||
MiddlewaresComponent,
|
||||
PipesComponent,
|
||||
ExceptionFiltersComponent,
|
||||
GuardsComponent,
|
||||
DependencyInjectionComponent,
|
||||
AsyncComponentsComponent,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
1
src/app/common/animations/index.ts
Normal file
1
src/app/common/animations/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './router.animations';
|
||||
18
src/app/common/animations/router.animations.ts
Normal file
18
src/app/common/animations/router.animations.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { trigger, animate, style, group, query, transition } from '@angular/animations';
|
||||
|
||||
export const routerTransition = trigger('routerTransition', [
|
||||
transition('* <=> *', [
|
||||
query(':enter, :leave', style({ position: 'fixed', width: '100%' })
|
||||
, { optional: true }),
|
||||
group([
|
||||
query(':enter', [
|
||||
style({ opacity: '0.0' }),
|
||||
animate('0.4s 0.6s ease-in-out', style({ opacity: '1.0' }))
|
||||
], { optional: true }),
|
||||
query(':leave', [
|
||||
style({ opacity: '1.0' }),
|
||||
animate('0.4s ease-in-out', style({ opacity: '0.0' }))
|
||||
], { optional: true }),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
39
src/app/common/directives/match-height.directive.ts
Normal file
39
src/app/common/directives/match-height.directive.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
Directive, ElementRef, AfterViewChecked,
|
||||
Input, HostListener, Renderer2, NgZone
|
||||
} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[matchHeight]'
|
||||
})
|
||||
export class MatchHeightDirective implements AfterViewChecked {
|
||||
constructor(
|
||||
private readonly zone: NgZone,
|
||||
private readonly rednerer: Renderer2,
|
||||
private readonly el: ElementRef) {}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
setTimeout(() => this.zone.run(() => this.matchHeight(this.el.nativeElement)), 800);
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
onResize() {
|
||||
setTimeout(() => this.matchHeight(this.el.nativeElement), 500);
|
||||
}
|
||||
|
||||
matchHeight(parent: HTMLElement) {
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
const children = Array.from(parent.children);
|
||||
children.forEach((x: HTMLElement) => {
|
||||
this.rednerer.setStyle(x, 'height', 'initial');
|
||||
});
|
||||
const itemHeights = children.map(x => x.getBoundingClientRect().height);
|
||||
const maxHeight = itemHeights.reduce((prev, curr) => {
|
||||
return curr > prev ? curr : prev;
|
||||
}, 0);
|
||||
|
||||
children.forEach((x: HTMLElement) => this.rednerer.setStyle(x, 'height', `${maxHeight}px`));
|
||||
}
|
||||
}
|
||||
1
src/app/common/index.ts
Normal file
1
src/app/common/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './animations';
|
||||
0
src/app/constants.ts
Normal file
0
src/app/constants.ts
Normal file
6
src/app/core/config/config.service.ts
Normal file
6
src/app/core/config/config.service.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export abstract class ConfigService {
|
||||
abstract readonly API_URL: string;
|
||||
}
|
||||
5
src/app/core/config/dev-config.service.ts
Normal file
5
src/app/core/config/dev-config.service.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
export class DevelopmentConfigService extends ConfigService {
|
||||
readonly API_URL = 'http://localhost:3001';
|
||||
}
|
||||
5
src/app/core/config/prod-config.service.ts
Normal file
5
src/app/core/config/prod-config.service.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
export class ProductionConfigService extends ConfigService {
|
||||
readonly API_URL = 'http://prod-api.scali.io';
|
||||
}
|
||||
21
src/app/core/core.module.ts
Normal file
21
src/app/core/core.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { ConfigService } from './config/config.service';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { DevelopmentConfigService } from './config/dev-config.service';
|
||||
import { ProductionConfigService } from './config/prod-config.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
{
|
||||
provide: ConfigService,
|
||||
useFactory() {
|
||||
return !environment.production ?
|
||||
new DevelopmentConfigService() : new ProductionConfigService();
|
||||
},
|
||||
}
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
export class CoreModule { }
|
||||
18
src/app/homepage/footer/footer.component.html
Normal file
18
src/app/homepage/footer/footer.component.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<footer>
|
||||
<div class="credits">
|
||||
<p>
|
||||
Made by <a
|
||||
href="https://kamilmysliwiec.com"
|
||||
target="blank"
|
||||
title="Kamil Mysliwiec Blog | Full-Stack Software Engineer">
|
||||
Kamil Myśliwiec</a> ©2017,
|
||||
MIT License
|
||||
</p>
|
||||
</div>
|
||||
<div class="social-wrapper">
|
||||
<a href="https://www.facebook.com/kamil.myslik" target="blank"><i class="fa fa-facebook"></i></a>
|
||||
<a href="https://twitter.com/kammysliwiec" target="blank"><i class="fa fa-twitter"></i></a>
|
||||
<a href="https://github.com/kamilmysliwiec" target="blank"><i class="fa fa-github"></i></a>
|
||||
<a href="https://kamilmysliwiec.com" title="Kamil Mysliwiec Blog | Full-Stack Software Engineer" target="blank"><i class="fa fa-globe"></i></a>
|
||||
</div>
|
||||
</footer>
|
||||
67
src/app/homepage/footer/footer.component.scss
Normal file
67
src/app/homepage/footer/footer.component.scss
Normal file
@@ -0,0 +1,67 @@
|
||||
@import "../../../scss/variables.scss";
|
||||
@import "../../../scss/utils.scss";
|
||||
|
||||
:host {
|
||||
@extend .transition;
|
||||
@extend .box-sizing;
|
||||
background: $black-color;
|
||||
display: block;
|
||||
height: 85px;
|
||||
padding: 16px 2px 16px 52px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
|
||||
@include media(medium) {
|
||||
padding: 15px;
|
||||
margin: 30px -30px 0;
|
||||
width: calc(100% + 60px);
|
||||
position: static;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
@extend .transition;
|
||||
color: $grey-color;
|
||||
&:hover {
|
||||
color: $white-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.social-wrapper {
|
||||
@extend .center-top;
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
|
||||
@include media(medium) {
|
||||
@include transform(translateX(0) translateY(0));
|
||||
position: static;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
@extend .transition;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
color: $darksilver-color;
|
||||
font-size: 22px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-of-type {
|
||||
font-size: 20px;
|
||||
}
|
||||
&:hover {
|
||||
color: $white-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/app/homepage/footer/footer.component.spec.ts
Normal file
25
src/app/homepage/footer/footer.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FooterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
9
src/app/homepage/footer/footer.component.ts
Normal file
9
src/app/homepage/footer/footer.component.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FooterComponent {}
|
||||
13
src/app/homepage/header/header.component.html
Normal file
13
src/app/homepage/header/header.component.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<header>
|
||||
<div class="icon-wrapper" (click)="toggle.emit()" [class.opened]="isSidebarOpened">
|
||||
<div class="nav-icon">
|
||||
<img src="assets/menu.png" alt="Open navigation sidebar" />
|
||||
</div>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
<div class="social-wrapper">
|
||||
<a href="https://twitter.com/nestframework" target="blank"><i class="fa fa-twitter"></i></a>
|
||||
<a href="https://github.com/kamilmysliwiec/nest" target="blank"><i class="fa fa-github"></i></a>
|
||||
<a href="http://nestjs.com" title="Nest - A node.js framework built on top of TypeScript" target="blank"><i class="fa fa-globe"></i></a>
|
||||
</div>
|
||||
</header>
|
||||
80
src/app/homepage/header/header.component.scss
Normal file
80
src/app/homepage/header/header.component.scss
Normal file
@@ -0,0 +1,80 @@
|
||||
@import "../../../scss/variables.scss";
|
||||
@import "../../../scss/utils.scss";
|
||||
|
||||
:host {
|
||||
background: $black-color;
|
||||
border-bottom: 6px solid $red-color;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 85px;
|
||||
}
|
||||
|
||||
header {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
@extend .transition;
|
||||
height: 100%;
|
||||
width: 80px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: $darkgrey-color;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: #212121;
|
||||
}
|
||||
&.opened {
|
||||
width: 345px;
|
||||
@include media(large) { width: 300px; }
|
||||
@include media(normal) { width: 80px; }
|
||||
}
|
||||
.nav-icon {
|
||||
@extend .center-element;
|
||||
}
|
||||
@include media(medium) { background: transparent; }
|
||||
}
|
||||
|
||||
:host /deep/ .logo-wrapper {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
display: inline-block;;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.social-wrapper,
|
||||
.icon-wrapper {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.social-wrapper {
|
||||
@extend .center-top;
|
||||
right: 40px;
|
||||
|
||||
a {
|
||||
@extend .transition;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
color: $darksilver-color;
|
||||
font-size: 22px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-of-type {
|
||||
font-size: 20px;
|
||||
}
|
||||
&:hover {
|
||||
color: $white-color;
|
||||
}
|
||||
}
|
||||
@include media(medium) { display: none; }
|
||||
}
|
||||
25
src/app/homepage/header/header.component.spec.ts
Normal file
25
src/app/homepage/header/header.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
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 be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
12
src/app/homepage/header/header.component.ts
Normal file
12
src/app/homepage/header/header.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component, Output, EventEmitter, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderComponent {
|
||||
@Output() toggle = new EventEmitter<void>();
|
||||
@Input() isSidebarOpened = true;
|
||||
}
|
||||
26
src/app/homepage/homepage.component.html
Normal file
26
src/app/homepage/homepage.component.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<app-header
|
||||
(toggle)="toggleSidebar()"
|
||||
[isSidebarOpened]="isSidebarOpened"
|
||||
>
|
||||
<div class="logo-wrapper">
|
||||
<a href="http://docs.nestjs.com/" title="Documentation | Nest - A node.js framework built on top of TypeScript">
|
||||
<img src="assets/logo.png" alt="Nest - A node.js framework built on top of TypeScript" />
|
||||
</a>
|
||||
</div>
|
||||
</app-header>
|
||||
<div class="container-fluid" matchHeight>
|
||||
<app-menu [class.opened]="isSidebarOpened" perfect-scrollbar></app-menu>
|
||||
<div class="container" [class.wide]="!isSidebarOpened" (click)="checkWindowWidth()">
|
||||
<div class="page-wrapper">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<app-footer>
|
||||
<p>
|
||||
Made by
|
||||
<a href="https://kamilmysliwiec.com" title="Kamil Mysliwiec Blog | Full-Stack Software Engineer">
|
||||
Kamil Myśliwiec
|
||||
</a>
|
||||
</p>
|
||||
</app-footer>
|
||||
</div>
|
||||
</div>
|
||||
62
src/app/homepage/homepage.component.scss
Normal file
62
src/app/homepage/homepage.component.scss
Normal file
@@ -0,0 +1,62 @@
|
||||
@import "../../scss/utils.scss";
|
||||
@import "../../scss/variables.scss";
|
||||
|
||||
.container-fluid {
|
||||
position: relative;
|
||||
display: block;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
@extend .transition;
|
||||
@extend .box-sizing;
|
||||
@include transform(translateX(345px));
|
||||
width: calc(100% - 345px);
|
||||
padding: 45px 50px 100px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
|
||||
&.wide {
|
||||
@include transform(translateX(0));
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@include media(large) {
|
||||
@include transform(translateX(300px));
|
||||
width: calc(100% - 300px);
|
||||
&.wide { width: 100%; }
|
||||
}
|
||||
@include media(medium) {
|
||||
@include transform(translateX(0));
|
||||
width: 100%;
|
||||
padding: 20px 30px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
color: $grey-color;
|
||||
line-height: 26px;
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: $red-color;
|
||||
&:hover {
|
||||
color: #0894e2;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: $black-color;
|
||||
}
|
||||
h3 { font-size: 24px; }
|
||||
|
||||
@include media(normal) {
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/app/homepage/homepage.component.spec.ts
Normal file
25
src/app/homepage/homepage.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomepageComponent } from './homepage.component';
|
||||
|
||||
describe('HomepageComponent', () => {
|
||||
let component: HomepageComponent;
|
||||
let fixture: ComponentFixture<HomepageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HomepageComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HomepageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
31
src/app/homepage/homepage.component.ts
Normal file
31
src/app/homepage/homepage.component.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Component, ViewEncapsulation, HostListener, AfterViewInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-homepage',
|
||||
templateUrl: './homepage.component.html',
|
||||
styleUrls: ['./homepage.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HomepageComponent implements AfterViewInit {
|
||||
isSidebarOpened = true;
|
||||
toggleSidebar() {
|
||||
this.isSidebarOpened = !this.isSidebarOpened;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.checkWindowWidth(window.innerWidth);
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
this.checkWindowWidth(event.target.innerWidth);
|
||||
}
|
||||
|
||||
checkWindowWidth(innerWidth?: number) {
|
||||
innerWidth = innerWidth ? innerWidth : window.innerWidth;
|
||||
if (innerWidth <= 768) {
|
||||
this.isSidebarOpened = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/app/homepage/menu/menu-item/menu-item.component.html
Normal file
22
src/app/homepage/menu/menu-item/menu-item.component.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="nav-item" [class.opened]="isOpen" *ngIf="children; else withoutChildren">
|
||||
<div class="heading" (click)="toggle()">
|
||||
<h3>{{ title }}</h3>
|
||||
<i class="fa fa-angle-right arrow-icon"></i>
|
||||
</div>
|
||||
<ul class="sub-nav">
|
||||
<li *ngFor="let item of children">
|
||||
<a
|
||||
[routerLink]="item.path"
|
||||
routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{ exact: true }">{{ item.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ng-template #withoutChildren>
|
||||
<div class="heading">
|
||||
<a
|
||||
[routerLink]="path"
|
||||
routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{ exact: true }"><h3>{{ title }}</h3></a>
|
||||
</div>
|
||||
</ng-template>
|
||||
72
src/app/homepage/menu/menu-item/menu-item.component.scss
Normal file
72
src/app/homepage/menu/menu-item/menu-item.component.scss
Normal file
@@ -0,0 +1,72 @@
|
||||
@import "../../../../scss/variables.scss";
|
||||
@import "../../../../scss/utils.scss";
|
||||
|
||||
:host {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@extend .transition;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
color: $black-color;
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.2px;
|
||||
&:hover {
|
||||
color: $red-color;
|
||||
}
|
||||
}
|
||||
|
||||
.active h3 {
|
||||
color: $red-color;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
@extend .transition;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 0;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
|
||||
a {
|
||||
color: $black-color;
|
||||
font-size: 15px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $red-color;
|
||||
}
|
||||
&.active { font-weight: 500; }
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
ul {
|
||||
display: none;
|
||||
}
|
||||
&.opened {
|
||||
ul {
|
||||
display: block;
|
||||
}
|
||||
.arrow-icon {
|
||||
@include transform(rotate(90deg));
|
||||
color: $red-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
.arrow-icon {
|
||||
color: $red-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/app/homepage/menu/menu-item/menu-item.component.spec.ts
Normal file
25
src/app/homepage/menu/menu-item/menu-item.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MenuItemComponent } from './menu-item.component';
|
||||
|
||||
describe('MenuItemComponent', () => {
|
||||
let component: MenuItemComponent;
|
||||
let fixture: ComponentFixture<MenuItemComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MenuItemComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MenuItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
18
src/app/homepage/menu/menu-item/menu-item.component.ts
Normal file
18
src/app/homepage/menu/menu-item/menu-item.component.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-menu-item',
|
||||
templateUrl: './menu-item.component.html',
|
||||
styleUrls: ['./menu-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MenuItemComponent {
|
||||
@Input() isOpen = false;
|
||||
@Input() children = [];
|
||||
@Input() path: string;
|
||||
@Input() title: string;
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
}
|
||||
9
src/app/homepage/menu/menu.component.html
Normal file
9
src/app/homepage/menu/menu.component.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<nav class="nav-container">
|
||||
<app-menu-item
|
||||
*ngFor="let item of items"
|
||||
[isOpen]="item.isOpened"
|
||||
[title]="item.title"
|
||||
[path]="item?.path"
|
||||
[children]="item.children"
|
||||
></app-menu-item>
|
||||
</nav>
|
||||
23
src/app/homepage/menu/menu.component.scss
Normal file
23
src/app/homepage/menu/menu.component.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
@import "../../../scss/variables.scss";
|
||||
@import "../../../scss/utils.scss";
|
||||
|
||||
:host {
|
||||
@extend .transition;
|
||||
@extend .box-sizing;
|
||||
@include transform(translateX(-100%));
|
||||
padding: 55px 30px 40px 45px;
|
||||
width: 345px;
|
||||
background: $white-color;
|
||||
box-shadow: 0 0 25px rgba(0, 0, 0, 0.1);
|
||||
position: absolute !important;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
|
||||
&.opened {
|
||||
@include transform(translateX(0));
|
||||
}
|
||||
@include media(large) { width: 300px; }
|
||||
@include media(medium) { padding: 20px 25px 20px 25px; }
|
||||
}
|
||||
25
src/app/homepage/menu/menu.component.spec.ts
Normal file
25
src/app/homepage/menu/menu.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MenuComponent } from './menu.component';
|
||||
|
||||
describe('MenuComponent', () => {
|
||||
let component: MenuComponent;
|
||||
let fixture: ComponentFixture<MenuComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MenuComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
94
src/app/homepage/menu/menu.component.ts
Normal file
94
src/app/homepage/menu/menu.component.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-menu',
|
||||
templateUrl: './menu.component.html',
|
||||
styleUrls: ['./menu.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MenuComponent {
|
||||
@Input() isSidebarOpened = true;
|
||||
readonly items = [
|
||||
{
|
||||
title: 'Introduction',
|
||||
isOpened: false,
|
||||
path: '/'
|
||||
},
|
||||
{
|
||||
title: 'Overview',
|
||||
isOpened: true,
|
||||
children: [
|
||||
{ title: 'First Steps', path: '/first-steps' },
|
||||
{ title: 'Controllers', path: '/controllers' },
|
||||
{ title: 'Components', path: '/components' },
|
||||
{ title: 'Modules', path: '/modules' },
|
||||
{ title: 'Middlewares', path: '/middlewares' },
|
||||
{ title: 'Exception Filters', path: '/exception-filters' },
|
||||
{ title: 'Pipes', path: '/pipes' },
|
||||
{ title: 'Guards', path: '/guards' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Advanced',
|
||||
isOpened: false,
|
||||
children: [
|
||||
{ title: 'Dependency Injection', path: '/advanced/dependency-injection' },
|
||||
{ title: 'Async Components', path: '/advanced/async-components' },
|
||||
{ title: 'Hierarchical Injection', path: '/advanced/hierarchical-injection' },
|
||||
{ title: 'Unit Testing', path: '/advanced/unit-testing' },
|
||||
{ title: 'E2E Testing', path: '/advanced/e2e-testing' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'WebSockets',
|
||||
isOpened: false,
|
||||
children: [
|
||||
{ title: 'Gateways', path: '/websockets/gateways' },
|
||||
{ title: 'Adapter', path: '/websockets/adapter' },
|
||||
{ title: 'Pipes', path: '/websockets/pipes' },
|
||||
{ title: 'Exception Filters', path: '/websockets/exception-filters' },
|
||||
{ title: 'Guards', path: '/websockets/guards' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Microservices',
|
||||
isOpened: false,
|
||||
children: [
|
||||
{ title: 'Basics', path: '/microservices/basics' },
|
||||
{ title: 'Redis', path: '/websockets/redis' },
|
||||
{ title: 'Custom Transport', path: '/websockets/custom-transport' },
|
||||
{ title: 'Pipes', path: '/websockets/pipes' },
|
||||
{ title: 'Exception Filters', path: '/websockets/exception-filters' },
|
||||
{ title: 'Guards', path: '/websockets/guards' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Recipes',
|
||||
isOpened: false,
|
||||
children: [
|
||||
{ title: 'SQL (TypeORM) [TBC]', path: '/recipes/sql-typeorm' },
|
||||
{ title: 'NoSQL (MongoDB) [TBC]', path: '/recipes/nosql-mongodb' },
|
||||
{ title: 'CQRS + Event Sourcing [TBC]', path: '/recipes/nosql-mongodb' },
|
||||
{ title: 'Passport JWT integration [TBC]', path: '/recipes/nosql-mongodb' },
|
||||
{ title: 'Swagger [TBC]', path: '/recipes/nosql-mongodb' },
|
||||
{ title: 'Open Graph [TBC]', path: '/recipes/open-graph' },
|
||||
{ title: 'Redis Socket.io [TBC]', path: '/recipes/redis-socketio' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'FAQ',
|
||||
isOpened: false,
|
||||
children: [
|
||||
{ title: 'Best Practices', path: '/faq/best-practices' },
|
||||
{ title: 'Express Instance', path: '/faq/express-instance' },
|
||||
{ title: 'Module Reference', path: '/faq/module-reference' },
|
||||
{ title: 'Global Route Prefix', path: '/faq/global-prefix' },
|
||||
{ title: 'Lifecycle Events', path: '/faq/lifecycle-events' },
|
||||
{ title: 'Hybrid Application', path: '/faq/hybrid-application' },
|
||||
{ title: 'Lazy Microservice Client', path: '/faq/lazy-microservice' },
|
||||
{ title: 'Multiple Simultaneous Servers', path: '/faq/multiple-servers' },
|
||||
{ title: 'Examples', path: '/faq/examples' },
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<p>
|
||||
async-components works!
|
||||
</p>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AsyncComponentsComponent } from './async-components.component';
|
||||
|
||||
describe('AsyncComponentsComponent', () => {
|
||||
let component: AsyncComponentsComponent;
|
||||
let fixture: ComponentFixture<AsyncComponentsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AsyncComponentsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AsyncComponentsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-async-components',
|
||||
templateUrl: './async-components.component.html',
|
||||
styleUrls: ['./async-components.component.scss']
|
||||
})
|
||||
export class AsyncComponentsComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<p>
|
||||
dependency-injection works!
|
||||
</p>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DependencyInjectionComponent } from './dependency-injection.component';
|
||||
|
||||
describe('DependencyInjectionComponent', () => {
|
||||
let component: DependencyInjectionComponent;
|
||||
let fixture: ComponentFixture<DependencyInjectionComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DependencyInjectionComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DependencyInjectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dependency-injection',
|
||||
templateUrl: './dependency-injection.component.html',
|
||||
styleUrls: ['./dependency-injection.component.scss']
|
||||
})
|
||||
export class DependencyInjectionComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
67
src/app/homepage/pages/components/components.component.html
Normal file
67
src/app/homepage/pages/components/components.component.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<div class="content">
|
||||
<h3>Components</h3>
|
||||
<p>
|
||||
Almost everything is a component – Service, Repository, Factory [...] and they can be <strong>injected</strong> into controllers or to another components through <code>constructor</code>.
|
||||
</p>
|
||||
<figure><img src="/assets/Components_1.png" /></figure>
|
||||
<p>
|
||||
In the previous article, we've built a simple <code>CatsController</code>.
|
||||
</p>
|
||||
<p>
|
||||
The controllers should only handle HTTP requests and delegate more complex tasks to the <strong>components</strong>.
|
||||
The components are a plain TypeScript classes with <code>@Component()</code> decorator.
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Hint</strong> Since Nest enables the possibility to design, organize the dependencies in more OO-way, we strongly recommend
|
||||
to follow the <strong>SOLID</strong> principles.
|
||||
</blockquote>
|
||||
<p>
|
||||
Let's create a <code>CatsService</code> component:
|
||||
</p>
|
||||
<span class="filename">cats.service.ts</span>
|
||||
<pre><code class="language-typescript">{{ catsService }}</code></pre>
|
||||
<p>
|
||||
There's nothing specifically about components. Here's a <code>CatsService</code>, basic class with one property and two methods.
|
||||
The only difference is that it has the <code>@Component()</code> decorator. The <code>@Component()</code> attaches the metadata, so Nest knows that this class is a Nest component.
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Notice</strong> There's a <code>Cat</code> interface here. I didn't mention about it, because the schema is exact same as in <code>CreateCatDto</code> class.
|
||||
</blockquote>
|
||||
<p>
|
||||
Since we've the service class already done, let's use it inside the <code>CatsController</code>:
|
||||
</p>
|
||||
<span class="filename">cats.controller.ts</span>
|
||||
<pre><code class="language-typescript">{{ catsController }}</code></pre>
|
||||
<p>
|
||||
The <code>CatsService</code> is injected through the class constructor.
|
||||
Don't be afraid about the <code>private readonly</code> shortened syntax.
|
||||
It means that we've created and initialized the <code>catsService</code> member in the same location.
|
||||
</p>
|
||||
<h4>Dependency Injection</h4>
|
||||
<p>
|
||||
Nest is built around the strong design pattern, which is commonly known as a <strong>Dependency Injection</strong>.
|
||||
There's a great article about this concept in the <a href="https://angular.io/guide/dependency-injection" target="blank">offical Angular documentation</a>.
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Hint</strong> Learn more about the <strong>Dependency Injection</strong> in Nest <a href="http://docs.nestjs.com/advanced/dependency-injection" target="blank">here</a>.
|
||||
</blockquote>
|
||||
<p>
|
||||
It's extremely easy to manage dependencies with <strong>TypeScript</strong>, because Nest will recognize your dependencies just by <strong>type</strong>.
|
||||
This single line:
|
||||
</p>
|
||||
<pre><code class="language-typescript">{{ constructorSyntax }}</code></pre>
|
||||
<p>
|
||||
Is everything what you have to do. There is one important thing to know — you must have <code>emitDecoratorMetadata</code> option set to <code>true</code> in your <code>tsconfig.json</code> file.
|
||||
That's it.
|
||||
</p>
|
||||
<h4>Last step</h4>
|
||||
<p>
|
||||
The last thing is to tell the module that something called <code>CatsService</code> truly exists.
|
||||
The only way to do it is to open the <code>app.module.ts</code> file, and add the service into the <code>components</code> array of the <code>@Module()</code> decorator.
|
||||
</p>
|
||||
<span class="filename">app.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ appModule }}</code></pre>
|
||||
<p>
|
||||
Now Nest will smoothly resolve the dependencies of the <code>CatsController</code> class.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ComponentsComponent } from './components.component';
|
||||
|
||||
describe('ComponentsComponent', () => {
|
||||
let component: ComponentsComponent;
|
||||
let fixture: ComponentFixture<ComponentsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ComponentsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ComponentsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
73
src/app/homepage/pages/components/components.component.ts
Normal file
73
src/app/homepage/pages/components/components.component.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
import { BasePageComponent } from '../page/page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-components',
|
||||
templateUrl: './components.component.html',
|
||||
styleUrls: ['./components.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ComponentsComponent extends BasePageComponent {
|
||||
get catsService() {
|
||||
return `
|
||||
import { Component } from '@nestjs/common';
|
||||
import { Cat } from './interfaces/cat.interface';
|
||||
|
||||
@Component()
|
||||
export class CatsService {
|
||||
private readonly cats: Cat[] = [];
|
||||
|
||||
create(cat: Cat) {
|
||||
this.cats.push(cat);
|
||||
}
|
||||
|
||||
findAll(): Cat[] {
|
||||
return this.cats;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
get catsController() {
|
||||
return `
|
||||
import { Controller, Get, Post, Body } from '@nestjs/common';
|
||||
import { CreateCatDto } from './dto/create-cat.dto';
|
||||
import { CatsService } from './cats.service';
|
||||
import { Cat } from './interfaces/cat.interface';
|
||||
|
||||
@Controller('cats')
|
||||
export class CatsController {
|
||||
constructor(private readonly catsService: CatsService) {}
|
||||
|
||||
@Post()
|
||||
async create(@Body() createCatDto: CreateCatDto) {
|
||||
this.catsService.create(createCatDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
async findAll(): Promise<Cat[]> {
|
||||
return this.catsService.findAll();
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get constructorSyntax() {
|
||||
return `
|
||||
constructor(private readonly catsService: CatsService) {}`;
|
||||
}
|
||||
|
||||
get appModule() {
|
||||
return `
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CatsController } from './cats/cats.controller';
|
||||
import { CatsService } from './cats/cats.service';
|
||||
|
||||
@Module({
|
||||
controllers: [CatsController],
|
||||
components: [CatsService],
|
||||
})
|
||||
export class ApplicationModule {}
|
||||
`;
|
||||
}
|
||||
}
|
||||
198
src/app/homepage/pages/controllers/controllers.component.html
Normal file
198
src/app/homepage/pages/controllers/controllers.component.html
Normal file
@@ -0,0 +1,198 @@
|
||||
<div class="content">
|
||||
<h3>Controllers</h3>
|
||||
<p>The controllers layer is responsible for handling incoming <strong>requests</strong> (mostly HTTP).</p>
|
||||
<figure><img src="/assets/Controllers_1.png" /></figure>
|
||||
<p>
|
||||
To tell Nest that <code>CatsController</code> is a controller, you have to attach <strong>metadata</strong> to the class.
|
||||
Metadata can be attached using <strong>decorators</strong>.
|
||||
</p>
|
||||
<span class="filename">cats.controller.ts</span>
|
||||
<pre><code class="language-typescript">{{ catsController }}</code></pre>
|
||||
<h4>Metadata</h4>
|
||||
<p>
|
||||
We're using <code>@Controller('cats')</code> here. This decorator is <strong>obligatory</strong>.
|
||||
The <code>cats</code> is a prefix for each route in the class. The prefix is <strong>optional</strong>, but it reduces redundant boilerplate code,
|
||||
so you don't have to repeat yourself every time, when you'd decide to create new endpoint.
|
||||
</p>
|
||||
<p>
|
||||
There's a single public method, the <code>findAll()</code>, which returns an empty array.
|
||||
The <code>@Get()</code> decorator tells Nest that it's necessary to create an endpoint for this route path,
|
||||
and map every appropriate request to this handler. Since we declared the prefix for every route (<code>cats</code>),
|
||||
Nest will map every <code>/cats</code> GET request here.
|
||||
</p>
|
||||
<p>
|
||||
When client would call this endpoint, Nest will return with 200 status code, and the parsed <strong>JSON</strong>, so in this case - just an empty array.
|
||||
How is that possible?
|
||||
</p>
|
||||
<p>
|
||||
There're <strong>two possible approaches</strong> of manipulating the response:
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Standard (recommended)</td>
|
||||
<td>
|
||||
We're treating the handlers in the same way as a plain functions. When we return the JavaScript object or array, it'd be <strong>automatically</strong>
|
||||
transformed to JSON. When we return the string, Nest will send just a string.
|
||||
<br />
|
||||
<br />
|
||||
Furthermore, the response <strong>status code</strong> is always 200 by default, except POST requests, when it's 201.
|
||||
We can easily change this behaviour by adding the <code>@HttpCode(...)</code> decorator at a handler-level.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Express</td>
|
||||
<td>
|
||||
We can use the express <a href="http://expressjs.com/en/api.html#res" target="blank">response object</a>,
|
||||
which we can inject using <code>@Res()</code> decorator in the function signature, for example <code>findAll(@Res() response)</code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<blockquote>
|
||||
<strong>Notice!</strong> It's forbidden to use both two approaches at the same time. Nest detects whether handler is using <code>@Res()</code>, and if it's thruth - the standard way is disabled for this single route.
|
||||
</blockquote>
|
||||
<h4>Request object</h4>
|
||||
<p>
|
||||
A lot of endpoints need an access to the client <strong>request</strong> details.
|
||||
In fact, Nest is using express <a href="http://expressjs.com/en/api.html#req" target="blank">request object</a>.
|
||||
We can force Nest to inject the request object into handler using <code>@Req()</code> decorator.
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Hint</strong> There's a <code>@types/express</code> package and we strongly recommend to use it.
|
||||
</blockquote>
|
||||
<span class="filename">cats.controller.ts</span>
|
||||
<pre><code class="language-typescript">{{ requestObject }}</code></pre>
|
||||
<p>
|
||||
The request object contains headers, params, and e.g. body of the request, but in most cases, it's not necessary to grab them manually.
|
||||
We can use <strong>dedicated decorators</strong> instead, such as <code>@Body()</code> or <code>@Query()</code>, which are available out of the box.
|
||||
Below is a comparision of the decorators with the plain express objects.
|
||||
</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>@Request()</code></td>
|
||||
<td><code>req</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@Response()</code></td>
|
||||
<td><code>res</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@Next()</code></td>
|
||||
<td><code>next</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@Session()</code></td>
|
||||
<td><code>req.session</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@Param(param?: string)</code></td>
|
||||
<td><code>req.params</code> / <code>req.params[param]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@Body(param?: string)</code></td>
|
||||
<td><code>req.body</code> / <code>req.body[param]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@Query(param?: string)</code></td>
|
||||
<td><code>req.query</code> / <code>req.query[param]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@Headers(param?: string)</code></td>
|
||||
<td><code>req.headers</code> / <code>req.headers[param]</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>More endpoints</h4>
|
||||
<p>
|
||||
We've already created an endpoint to fetch the data. It'd be great to provide a way of creating the new records too.
|
||||
So.. What we're waiting for?! Let's create the <strong>POST</strong> handler:
|
||||
</p>
|
||||
<span class="filename">cats.controller.ts</span>
|
||||
<pre><code class="language-typescript">{{ postEndpoint }}</code></pre>
|
||||
<p>
|
||||
It's really easy. Nest provides the rest of those endpoints decorators in the same fashion -
|
||||
<code>@Put()</code>, <code>@Delete()</code> etc..
|
||||
</p>
|
||||
<h4>Async / await</h4>
|
||||
<p>
|
||||
We love modern JavaScript, and we know that the data extraction is mostly <strong>asynchronous</strong>.
|
||||
That's why Nest supports <code>async</code> functions, and works pretty well with them.
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Hint</strong> Learn more about <code>async / await</code> <a href="https://kamilmysliwiec.com/typescript-2-1-introduction-async-await" target="blank">here</a>!
|
||||
</blockquote>
|
||||
<p>
|
||||
Every async function has to return the <code>Promise</code>. It means that you can return deffered value and
|
||||
Nest will resolve it by itself. Let's have a look on the below example:
|
||||
</p>
|
||||
<span class="filename">cats.controller.ts</span>
|
||||
<pre><code class="language-typescript">{{ asyncExample }}</code></pre>
|
||||
<p>
|
||||
It's fully valid.
|
||||
</p>
|
||||
<p>
|
||||
Furthermore, Nest route handlers are even more powerful. They can return the RxJS
|
||||
<a href="http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html" target="blank">observable streams</a>.
|
||||
It makes the migration between simple web application and the Nest microservice much easier.
|
||||
</p>
|
||||
<span class="filename">cats.controller.ts</span>
|
||||
<pre><code class="language-typescript">{{ observableExample }}</code></pre>
|
||||
<h4>POST handler</h4>
|
||||
<p>
|
||||
That's weird that the POST route handler doesn't accept any client params. We should definitely
|
||||
expect the <code>@Body()</code> argument here.
|
||||
</p>
|
||||
<p>
|
||||
Firstly, we need to establish the <strong>DTO</strong> (Data Transfer Object) schema. We could do it using <strong>TypeScript</strong>
|
||||
interfaces, or by simple classes. What may be surprising, we recommend to use <strong>classes</strong>. Why?
|
||||
The classes are the part of the JavaScript ES6 standard, so they're just plain functions. On the other hand, TypeScript interfaces are removed
|
||||
during the transpilation, Nest can't refer to them. It's important, because features such as <strong>Pipes</strong> enables additional possibilities when they've access to the metatype
|
||||
of the variable.
|
||||
</p>
|
||||
<p>
|
||||
Let's create the <code>CreateCatDto</code>:
|
||||
</p>
|
||||
<span class="filename">dto/create-cat.dto.ts</span>
|
||||
<pre><code class="language-typescript">{{ createCatSchema }}</code></pre>
|
||||
<p>
|
||||
It has only three basic properties. All of them are marked as a <code>readonly</code>, because we should
|
||||
always try to make our functions as pure as possible.
|
||||
</p>
|
||||
<p>
|
||||
Now we can use the schema inside the <code>CatsController</code>:
|
||||
</p>
|
||||
<span class="filename">cats.controller.ts</span>
|
||||
<pre><code class="language-typescript">{{ exampleWithBody }}</code></pre>
|
||||
<p>
|
||||
<a href="http://expressjs.com/" target="blank">Expressjs</a> doesn't parse the body by default.
|
||||
We need the middleware, which name is <a href="https://github.com/expressjs/body-parser" target="blank">body-parser</a>.
|
||||
The usage is really simple, because Nest instance provides the <code>use()</code> method. It's a wrapper to the native express <code>use()</code> function:
|
||||
</p>
|
||||
<span class="filename">server.ts</span>
|
||||
<pre><code class="language-typescript">{{ bodyParser }}</code></pre>
|
||||
<h4>Last step</h4>
|
||||
<p>
|
||||
The controller is prepared, and ready to use, but Nest doesn't know that <code>CatsController</code> exists,
|
||||
so it won't create an instance of this class. We need to tell about it.
|
||||
</p>
|
||||
<p>
|
||||
The controller always belongs to the module, which mentioned about its in <code>controllers</code> array within <code>@Module()</code> decorator.
|
||||
Since we don't have any other modules except the root <code>AppModule</code>, let's use it for now:
|
||||
</p>
|
||||
<span class="filename">app.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ appModule }}</code></pre>
|
||||
<p>
|
||||
Tada! We attached the metadata to the module class, so now Nest can easily reflect which controllers have to be initialized.
|
||||
</p>
|
||||
<h4>Express approach</h4>
|
||||
<p>
|
||||
The second way of manipulating the response is to use express <a href="http://expressjs.com/en/api.html#res" target="blank">response object</a>.
|
||||
It was the only available option until <strong>Nest 4</strong>. To inject the response object, we need to use <code>@Res()</code> decorator.
|
||||
To show the differences, i'm going to rewrite the <code>CatsController</code>:
|
||||
</p>
|
||||
<pre><code class="language-typescript">{{ expressWay }}</code></pre>
|
||||
<p>
|
||||
This manner is much less clear from my point of view. I definitely prefer the first approach, but to make the Nest <strong>backward compatible</strong>
|
||||
with the previous versions, this method is still available. Also, the <strong>response object</strong> gives more flexibility - you've full control of the response.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ControllersComponent } from './controllers.component';
|
||||
|
||||
describe('ControllersComponent', () => {
|
||||
let component: ControllersComponent;
|
||||
let fixture: ComponentFixture<ControllersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ControllersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ControllersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
132
src/app/homepage/pages/controllers/controllers.component.ts
Normal file
132
src/app/homepage/pages/controllers/controllers.component.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BasePageComponent } from '../page/page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-controllers',
|
||||
templateUrl: './controllers.component.html',
|
||||
styleUrls: ['./controllers.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ControllersComponent extends BasePageComponent {
|
||||
get catsController(): string {
|
||||
return `
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
|
||||
@Controller('cats')
|
||||
export class CatsController {
|
||||
@Get()
|
||||
findAll() {
|
||||
return [];
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get requestObject(): string {
|
||||
return `
|
||||
import { Controller, Get, Req } from '@nestjs/common';
|
||||
|
||||
@Controller('cats')
|
||||
export class CatsController {
|
||||
@Get()
|
||||
findAll(@Req() request) {
|
||||
return [];
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get postEndpoint() {
|
||||
return `
|
||||
import { Controller, Get, Post } from '@nestjs/common';
|
||||
|
||||
@Controller('cats')
|
||||
export class CatsController {
|
||||
@Post()
|
||||
create() {
|
||||
// TODO: Add some logic here
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return [];
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get asyncExample() {
|
||||
return `
|
||||
@Get()
|
||||
async findAll(): Promise<any[]> {
|
||||
return [];
|
||||
}`;
|
||||
}
|
||||
|
||||
get observableExample() {
|
||||
return `
|
||||
@Get()
|
||||
findAll(): Observable<any[]> {
|
||||
return Observable.of([]);
|
||||
}`;
|
||||
}
|
||||
|
||||
get createCatSchema() {
|
||||
return `
|
||||
export class CreateCatDto {
|
||||
readonly name: string;
|
||||
readonly age: number;
|
||||
readonly breed: string;
|
||||
}`;
|
||||
}
|
||||
|
||||
get exampleWithBody() {
|
||||
return `
|
||||
@Post()
|
||||
async create(@Body() createCatDto: CreateCatDto) {
|
||||
// TODO: Add some logic here
|
||||
}`;
|
||||
}
|
||||
|
||||
get appModule() {
|
||||
return `
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CatsController } from './cats/cats.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [CatsController],
|
||||
})
|
||||
export class ApplicationModule {}`;
|
||||
}
|
||||
|
||||
get bodyParser() {
|
||||
return `
|
||||
import * as bodyParser from 'body-parser';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ApplicationModule } from './modules/app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(ApplicationModule);
|
||||
app.use(bodyParser.json());
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();`;
|
||||
}
|
||||
|
||||
get expressWay() {
|
||||
return `
|
||||
import { Controller, Get, Post, Res, Body, HttpStatus } from '@nestjs/common';
|
||||
import { CreateCatDto } from './dto/create-cat.dto';
|
||||
|
||||
@Controller('cats')
|
||||
export class CatsController {
|
||||
@Post()
|
||||
create(@Res() res, @Body() createCatDto: CreateCatDto) {
|
||||
// TODO: Add some logic here
|
||||
res.status(HttpStatus.CREATED).send();
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Res() res) {
|
||||
res.status(HttpStatus.OK).json([]);
|
||||
}
|
||||
}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="content">
|
||||
<h3>Exception Filters</h3>
|
||||
<p>
|
||||
In Nest there's an <strong>exceptions layer</strong>, which responsibility is to catch the unhandled exceptions and
|
||||
return the appropriate response to the end-user.
|
||||
</p>
|
||||
<figure><img src="/assets/Filter_1.png" /></figure>
|
||||
<p>
|
||||
Every unrecognized exception is handled, and when it was HTTP request, user receives the below response:
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ExceptionFiltersComponent } from './exception-filters.component';
|
||||
|
||||
describe('ExceptionFiltersComponent', () => {
|
||||
let component: ExceptionFiltersComponent;
|
||||
let fixture: ComponentFixture<ExceptionFiltersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ExceptionFiltersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ExceptionFiltersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-exception-filters',
|
||||
templateUrl: './exception-filters.component.html',
|
||||
styleUrls: ['./exception-filters.component.scss']
|
||||
})
|
||||
export class ExceptionFiltersComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="content">
|
||||
<h3>First Steps</h3>
|
||||
<p>
|
||||
In this set of articles you'll learn the <strong>core fundamentals</strong> of Nest.
|
||||
The main idea is to get familiar with essential Nest application building blocks.
|
||||
You'll build a basic CRUD application which features covers a lot of ground at an introductory level.
|
||||
</p>
|
||||
<h3>Setup</h3>
|
||||
<p>
|
||||
Setting up a new project is quite simple with <a href="https://github.com/kamilmysliwiec/nest-typescript-starter" target="blank">Starter repository</a>.
|
||||
Just make sure that you have <a href="https://www.npmjs.com/" target="blank">npm</a> installed then use following commands in your OS terminal:
|
||||
</p>
|
||||
<pre><code class="language-bash">
|
||||
$ git clone https://github.com/kamilmysliwiec/nest-typescript-starter.git project
|
||||
$ cd project
|
||||
$ npm install
|
||||
$ npm run start</code></pre>
|
||||
<p>The <code>project</code> directory will contain several core files inside <code>src</code> directory.
|
||||
Following the convention, newly created modules should be placed inside <code>modules</code> directory.
|
||||
<table>
|
||||
<tr>
|
||||
<td><code>server.ts</code></td>
|
||||
<td>The entry file of the application. It uses <code>NestFactory</code> to create the Nest application instance.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>modules/app.module.ts</code></td>
|
||||
<td>Defines <code>AppModule</code>, the root module of the application. Right now it doesn't declare any metadata, because we didn't create any component and controller yet.</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
The <code>server.ts</code> contains single async function, which responsibility is to <strong>bootstrap</strong> our application:
|
||||
</p>
|
||||
<span class="filename">server.ts</span>
|
||||
<pre><code class="language-typescript">{{ bootstrap }}</code></pre>
|
||||
<p>
|
||||
We should always create the Nest application instance using the <code>NestFactory</code>.
|
||||
The <code>create()</code> method returns object, which fulfils <code>INestApplication</code> interface, and provides a set of usable methods, which are well described in the next articles.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FirstStepsComponent } from './first-steps.component';
|
||||
|
||||
describe('FirstStepsComponent', () => {
|
||||
let component: FirstStepsComponent;
|
||||
let fixture: ComponentFixture<FirstStepsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FirstStepsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FirstStepsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
23
src/app/homepage/pages/first-steps/first-steps.component.ts
Normal file
23
src/app/homepage/pages/first-steps/first-steps.component.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BasePageComponent } from '../page/page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-first-steps',
|
||||
templateUrl: './first-steps.component.html',
|
||||
styleUrls: ['./first-steps.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FirstStepsComponent extends BasePageComponent {
|
||||
get bootstrap(): string {
|
||||
return `
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ApplicationModule } from './modules/app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(ApplicationModule);
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
`;
|
||||
}
|
||||
}
|
||||
3
src/app/homepage/pages/guards/guards.component.html
Normal file
3
src/app/homepage/pages/guards/guards.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<p>
|
||||
guards works!
|
||||
</p>
|
||||
0
src/app/homepage/pages/guards/guards.component.scss
Normal file
0
src/app/homepage/pages/guards/guards.component.scss
Normal file
25
src/app/homepage/pages/guards/guards.component.spec.ts
Normal file
25
src/app/homepage/pages/guards/guards.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GuardsComponent } from './guards.component';
|
||||
|
||||
describe('GuardsComponent', () => {
|
||||
let component: GuardsComponent;
|
||||
let fixture: ComponentFixture<GuardsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ GuardsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GuardsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
15
src/app/homepage/pages/guards/guards.component.ts
Normal file
15
src/app/homepage/pages/guards/guards.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-guards',
|
||||
templateUrl: './guards.component.html',
|
||||
styleUrls: ['./guards.component.scss']
|
||||
})
|
||||
export class GuardsComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<div class="content">
|
||||
<h3>Introduction</h3>
|
||||
<p>Nest is a powerful web framework for <a href="http://nodejs.org" target="_blank">Node.js</a>, which helps you effortlessly build efficient, scalable applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> and combines best concepts of both OOP (Object Oriented Progamming) and FP (Functional Programming).</p>
|
||||
<p>Nest is using well-known—<a href="https://expressjs.com/" target="_blank">Express</a> library under the hood. This means that you can quickly start using Nest without worrying about third party plugins.</p>
|
||||
<h3>Installation</h3>
|
||||
<p>Install the TypeScript Starter Project with <strong>Git</strong>:</p>
|
||||
<pre><code class="language-bash">
|
||||
$ git clone https://github.com/kamilmysliwiec/nest-typescript-starter.git project
|
||||
$ cd project
|
||||
$ npm install
|
||||
$ npm run start</code></pre>
|
||||
<p>Start a New Project from Scratch with <strong>NPM</strong>:</p>
|
||||
<pre><code class="language-bash">
|
||||
$ npm i --save @nestjs/core @nestjs/common @nestjs/microservices @nestjs/websockets @nestjs/testing reflect-metadata rxjs</code></pre>
|
||||
<h3>Philosophy</h3>
|
||||
<p>JavaScript is awesome. Now, the front end world is rich in variety of tools. We have a lot of amazing projects / libraries such as <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a> or <a href="https://github.com/vuejs/vue" target="_blank">Vue</a>, which improves our development process and makes our applications fast and extensible.</p>
|
||||
<p><a href="http://nodejs.org" target="_blank">Node.js</a> gave us a possibility to use this language also on the server side. There are a lot of superb libraries, helpers and tools for node, but non of them do not solve the main problem - the architecture.</p>
|
||||
<p>We want to create scalable, loosely coupled and easy to maintain applications. That's why the <strong>Nest</strong> has been introduced. Let's show the entire world node.js potential together!</p>
|
||||
<h3>Features</h3>
|
||||
<ul>
|
||||
<li>Built on top of <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a></li>
|
||||
<li>Easy to learn - syntax is similar to <a href="https://angular.io/" target="_blank">Angular</a></li>
|
||||
<li>Based on well-known libraries (<a href="https://github.com/expressjs/express" target="_blank">Express</a> / <a href="https://github.com/socketio/socket.io" target="_blank">socket.io</a>) so it’s a familiar experience</li>
|
||||
<li>Supremely useful Dependency Injection support, built-in asynchronous <strong>Inversion of Control</strong> container</li>
|
||||
<li><strong>Hierarchical injector</strong> - increase abstraction in your application by creating reusable, loosely coupled modules with type injection</li>
|
||||
<li><strong>WebSockets</strong> module (based on <a href="https://github.com/socketio/socket.io" target="_blank">socket.io</a>, although you can use any other library using <code>WsAdapter</code>)</li>
|
||||
<li>Own modularity system (split your system into reusable modules)</li>
|
||||
<li>Reactive <strong>microservices</strong> support with message patterns (built-in transport via TCP / <a href="https://redis.io/" target="_blank">Redis</a>, but you can use any other type of communication using <code>CustomTransportStrategy</code>)</li>
|
||||
<li>Exceptions handler layer, exception filters, <strong>synchronous & asynchronous pipes</strong> layer (e.g. validation purposes)</li>
|
||||
<li><strong>Guards</strong> & Reflector - attach additional logic in more declaritive way (e.g. role-based access control)</li>
|
||||
<li>Testing utilities (both <strong>e2e & unit</strong> tests)</li>
|
||||
</ul>
|
||||
<h3>People</h3>
|
||||
<ul>
|
||||
<li>Author - <a href="https://kamilmysliwiec.com" title="Kamil Mysliwiec Blog | Full-Stack Software Engineer" target="blank">Kamil Myśliwiec</a></li>
|
||||
<li>Website - <a href="http://nestjs.com" title="Nest - A node.js framework built on top of TypeScript" target="blank">http://nestjs.com</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { IntroductionComponent } from './introduction.component';
|
||||
|
||||
describe('IntroductionComponent', () => {
|
||||
let component: IntroductionComponent;
|
||||
let fixture: ComponentFixture<IntroductionComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ IntroductionComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IntroductionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BasePageComponent } from '../page/page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-introduction',
|
||||
templateUrl: './introduction.component.html',
|
||||
styleUrls: ['./introduction.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class IntroductionComponent extends BasePageComponent {}
|
||||
@@ -0,0 +1,89 @@
|
||||
<div class="content">
|
||||
<h3>Middlewares</h3>
|
||||
<p>
|
||||
The middleware is a function, which is called <strong>before</strong> route handler.
|
||||
Middleware functions have an access to the <a href="http://expressjs.com/en/4x/api.html#req" target="blank">request</a>
|
||||
and <a href="http://expressjs.com/en/4x/api.html#res" target="blank">response</a> objects, so they can modify them. They can also be something like a <strong>barrier</strong> - if middleware function won't call <code>next()</code>,
|
||||
the request will never be handled by the route handler.
|
||||
</p>
|
||||
<figure><img src="/assets/Middlewares_1.png" /></figure>
|
||||
<p>
|
||||
Middleware is a class with <code>@Middleware()</code> decorator. This class should implement the <code>NestMiddleware</code> interface.
|
||||
Let's create an example, the <code>LoggerMiddleware</code> class.
|
||||
</p>
|
||||
<span class="filename">logger.middleware.ts</span>
|
||||
<pre><code class="language-typescript">{{ loggerMiddleware }}</code></pre>
|
||||
<p>
|
||||
The <code>resolve()</code> method has to return the regular expressjs middleware <code>(req, res, next) => void</code>.
|
||||
</p>
|
||||
<h4>Dependency Injection</h4>
|
||||
<p>
|
||||
There's no exception when it comes to the middlewares. Same as components and controllers, they can inject dependencies which belong to the same module.
|
||||
</p>
|
||||
<h4>Where to put the middlewares?</h4>
|
||||
<p>
|
||||
The middlewares shouldn't be listed in the <code>@Module()</code> decorator.
|
||||
We've to setup them using <code>configure()</code> method of the module class.
|
||||
Modules which include middlewares have to implement the <code>NestModule</code> interface.
|
||||
Let's setup the <code>LoggerMiddleware</code> at the <code>ApplicationModule</code> level.
|
||||
</p>
|
||||
<span class="filename">app.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ applicationModule }}</code></pre>
|
||||
<blockquote>
|
||||
<strong>Hint</strong> We could pass here (inside <code>forRoutes()</code>) the single object and just use <code>RequestMethod.ALL</code>.
|
||||
</blockquote>
|
||||
<p>
|
||||
In above example we setuped <code>LoggerMiddleware</code> for <code>/cats</code> route handlers, which we've registered inside <code>CatsController</code>.
|
||||
The <code>MiddlewareConsumer</code> is a helper class. It provides several methods to work with the middlewares. All of them can be simply <strong>chained</strong>.
|
||||
Let's go through those methods.
|
||||
</p>
|
||||
<p>
|
||||
The <code>forRoutes()</code> can take single object, multiple objects, controller class and even multiple controller classes.
|
||||
In most cases you'll probably just pass the <strong>controllers</strong> and separate them by comma. Below is an example with the single controller:
|
||||
</p>
|
||||
<span class="filename">app.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ applicationModuleByControllers }}</code></pre>
|
||||
<blockquote>
|
||||
<strong>Hint</strong> The <code>apply()</code> method can take single middleware or an <strong>array of middlewares</strong>.
|
||||
</blockquote>
|
||||
<h4>Pass arguments to the middleware</h4>
|
||||
<p>
|
||||
Sometimes the behaviour of the middleware depends on the custom values e.g. array of user roles, options object etc.
|
||||
We can pass additional arguments to the <code>resolve()</code> using <code>with()</code> method. See below example:
|
||||
</p>
|
||||
<span class="filename">app.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ applicationModuleWithMethod }}</code></pre>
|
||||
<p>
|
||||
We passed custom string - <code>ApplicationModule</code> to the <code>with()</code> method.
|
||||
Now we've to adjust the <code>resolve()</code> method of the <code>LoggerMiddleware</code>.
|
||||
</p>
|
||||
<span class="filename">logger.middleware.ts</span>
|
||||
<pre><code class="language-typescript">{{ loggerMiddlewareWithArgs }}</code></pre>
|
||||
<p>
|
||||
The value of the <code>name</code> property would be <code>ApplicationModule</code>.
|
||||
</p>
|
||||
<h4>Async Middlewares</h4>
|
||||
<p>
|
||||
There's no contraindications to return the <code>async</code> function from the <code>resolve()</code> method.
|
||||
Also, it's possible to make the <code>resolve()</code> method <code>async</code> too. This pattern is called <strong>deffered middleware</strong>.
|
||||
</p>
|
||||
<span class="filename">logger.middleware.ts</span>
|
||||
<pre><code class="language-typescript">{{ defferedMiddleware }}</code></pre>
|
||||
<h4>Functional Middlewares</h4>
|
||||
<p>
|
||||
The <code>LoggerMiddleware</code> is quite short. It has no members, no additional methods, no dependencies.
|
||||
Why we can't just use simple function? It's a good question, cause in fact - we can do it.
|
||||
This type of the middleware is called <strong>functional middleware</strong>.
|
||||
Let's transform the logger into the function.
|
||||
</p>
|
||||
<span class="filename">logger.middleware.ts</span>
|
||||
<pre><code class="language-typescript">{{ functionalMiddleware }}</code></pre>
|
||||
<p>
|
||||
Now use it within the <code>ApplicationModule</code>.
|
||||
</p>
|
||||
<span class="filename">app.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ applyFunctionalMiddleware }}</code></pre>
|
||||
<blockquote>
|
||||
<strong>Notice</strong> Let's consider to use <strong>functional middlewares</strong> everytime when your middleware doesn't have any dependencies.
|
||||
</blockquote>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MiddlewaresComponent } from './middlewares.component';
|
||||
|
||||
describe('MiddlewaresComponent', () => {
|
||||
let component: MiddlewaresComponent;
|
||||
let fixture: ComponentFixture<MiddlewaresComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MiddlewaresComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MiddlewaresComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
135
src/app/homepage/pages/middlewares/middlewares.component.ts
Normal file
135
src/app/homepage/pages/middlewares/middlewares.component.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { BasePageComponent } from '../page/page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-middlewares',
|
||||
templateUrl: './middlewares.component.html',
|
||||
styleUrls: ['./middlewares.component.scss']
|
||||
})
|
||||
export class MiddlewaresComponent extends BasePageComponent {
|
||||
get loggerMiddleware() {
|
||||
return `
|
||||
import { Middleware, NestMiddleware, ExpressMiddleware } from '@nestjs/common';
|
||||
|
||||
@Middleware()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
resolve(...args: any[]): ExpressMiddleware {
|
||||
return (req, res, next) => {
|
||||
console.log('Request...');
|
||||
next();
|
||||
};
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get applicationModule() {
|
||||
return `
|
||||
import { Module, NestModule, MiddlewaresConsumer, RequestMethod } from '@nestjs/common';
|
||||
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
|
||||
import { CatsModule } from './cats/cats.module';
|
||||
|
||||
@Module({
|
||||
modules: [CatsModule],
|
||||
})
|
||||
export class ApplicationModule implements NestModule {
|
||||
configure(consumer: MiddlewaresConsumer): void {
|
||||
consumer.apply(LoggerMiddleware).forRoutes(
|
||||
{ path: '/cats', method: RequestMethod.GET },
|
||||
{ path: '/cats', method: RequestMethod.POST },
|
||||
);
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get applicationModuleByControllers() {
|
||||
return `
|
||||
import { Module, NestModule, MiddlewaresConsumer, RequestMethod } from '@nestjs/common';
|
||||
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
|
||||
import { CatsModule } from './cats/cats.module';
|
||||
|
||||
@Module({
|
||||
modules: [CatsModule],
|
||||
})
|
||||
export class ApplicationModule implements NestModule {
|
||||
configure(consumer: MiddlewaresConsumer): void {
|
||||
consumer.apply(LoggerMiddleware).forRoutes(CatsController);
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get applicationModuleWithMethod() {
|
||||
return `
|
||||
import { Module, NestModule, MiddlewaresConsumer } from '@nestjs/common';
|
||||
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
|
||||
import { CatsModule } from './cats/cats.module';
|
||||
import { CatsController } from './cats/cats.controller';
|
||||
|
||||
@Module({
|
||||
modules: [CatsModule],
|
||||
})
|
||||
export class ApplicationModule implements NestModule {
|
||||
configure(consumer: MiddlewaresConsumer): void {
|
||||
consumer.apply(LoggerMiddleware)
|
||||
.with('ApplicationModule')
|
||||
.forRoutes(CatsController);
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get loggerMiddlewareWithArgs() {
|
||||
return `
|
||||
import { Middleware, NestMiddleware, ExpressMiddleware } from '@nestjs/common';
|
||||
|
||||
@Middleware()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
resolve(name: string): ExpressMiddleware {
|
||||
return (req, res, next) => {
|
||||
console.log(\`[\${name}\] Request...\`); // [ApplicationModule] Request...
|
||||
next();
|
||||
};
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
get defferedMiddleware() {
|
||||
return `
|
||||
import { Middleware, NestMiddleware, ExpressMiddleware } from '@nestjs/common';
|
||||
|
||||
@Middleware()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
async resolve(name: string): Promise<ExpressMiddleware> {
|
||||
await someAsyncFn();
|
||||
|
||||
return async (req, res, next) => {
|
||||
await someAsyncFn();
|
||||
console.log(\`[\${name}\] Request...\`); // [ApplicationModule] Request...
|
||||
next();
|
||||
};
|
||||
}
|
||||
}`;
|
||||
}
|
||||
get functionalMiddleware() {
|
||||
return `
|
||||
export const loggerMiddleware = (req, res, next) => {
|
||||
console.log(\`Request...\`);
|
||||
next();
|
||||
};`;
|
||||
}
|
||||
|
||||
get applyFunctionalMiddleware() {
|
||||
return `
|
||||
import { Module, NestModule, MiddlewaresConsumer } from '@nestjs/common';
|
||||
import { loggerMiddleware } from './common/middlewares/logger.middleware';
|
||||
import { CatsModule } from './cats/cats.module';
|
||||
import { CatsController } from './cats/cats.controller';
|
||||
|
||||
@Module({
|
||||
modules: [CatsModule],
|
||||
})
|
||||
export class ApplicationModule implements NestModule {
|
||||
configure(consumer: MiddlewaresConsumer): void {
|
||||
consumer.apply(loggerMiddleware).forRoutes(CatsController);
|
||||
}
|
||||
}`;
|
||||
}
|
||||
}
|
||||
89
src/app/homepage/pages/modules/modules.component.html
Normal file
89
src/app/homepage/pages/modules/modules.component.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<div class="content">
|
||||
<h3>Modules</h3>
|
||||
<p>
|
||||
Module is a class with <code>@Module()</code> decorator. The <code>@Module()</code> decorator provides metadata, which <strong>Nest</strong> is using to organize the application structure.
|
||||
</p>
|
||||
<figure><img src="/assets/Modules_1.png" /></figure>
|
||||
<p>
|
||||
Every Nest application has at least one module, the <strong>root module</strong>.
|
||||
The root module is the place, where Nest is starting to arrange the application tree.
|
||||
In fact, the root module could be the only module in your application, especially when the app is small, but it doesn't make sense.
|
||||
In most cases you'll have several modules, each with closely related set of <strong>capabilities</strong>.
|
||||
</p>
|
||||
<p>
|
||||
The <code>@Module()</code> decorator takes the single object whose properties describe the module.
|
||||
Read about them in the below table:
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<td><code>components</code></td>
|
||||
<td>the components that will be instantiated by the Nest injector and may be shared at least across this module.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>controllers</code></td>
|
||||
<td>the set of controllers which have to be created</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>modules</code></td>
|
||||
<td>the list of imported modules that export the components which are necessary in this module</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>exports</code></td>
|
||||
<td>the subset of <code>components</code> that should be available in the other modules</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
The module <strong>encapsulates</strong> components by default. It means that it isn't possible to inject the components which aren't directly the part of the current module, or they're not exported from the imported modules.
|
||||
</p>
|
||||
<h3>CatsModule</h3>
|
||||
<p>
|
||||
The <code>CatsController</code> and <code>CatsService</code> belong to the same application domain.
|
||||
They should be moved to the feature module, the <code>CatsModule</code>.
|
||||
</p>
|
||||
<span class="filename">cats/cats.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ catsModule }}</code></pre>
|
||||
<p>
|
||||
I've created the <code>cats.module.ts</code> file and moved everything related to this module into the
|
||||
<code>cats</code> directory. The last thing we need to do is to import this module into the root module which name is <code>ApplicationModule</code>.
|
||||
</p>
|
||||
<span class="filename">app.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ appModule }}</code></pre>
|
||||
<p>
|
||||
It's everything. Now Nest knows that besides <code>ApplicationModule</code>, it's essential to register the <code>CatsModule</code> too.
|
||||
</p>
|
||||
<h3>Shared Module</h3>
|
||||
<p>
|
||||
In Nest, modules <strong>are singletons</strong> by default, so you can share the <strong>same instance</strong> of the component between 2..* modules without any effort.
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Notice</strong> In the previous versions of the Nest (< 4), modules weren't singletons, we had to use <code>@Shared()</code> decorator which now is <strong>deprecated</strong>.
|
||||
</blockquote>
|
||||
<figure><img src="/assets/Shared_Module_1.png" /></figure>
|
||||
<p>
|
||||
Every module is a <strong>Shared Module</strong> in fact. Once created is reused by the each module.
|
||||
Let's imagine that we're gonna share the <code>CatsService</code> instance between few modules.
|
||||
</p>
|
||||
<span class="filename">cats.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ catsModuleShared }}</code></pre>
|
||||
<p>
|
||||
Now each module which would import the <code>CatsModule</code> has an access to the <code>CatsService</code>, and will share the same instance with all of the modules which import this module too.
|
||||
</p>
|
||||
<blockquote>
|
||||
<strong>Notice</strong> Never export the controller!
|
||||
</blockquote>
|
||||
<h3>Single Scope</h3>
|
||||
<p>
|
||||
They're modules which shouldn't be shared at all. To prevent module from being singleton, you can use <code>@SingleScope()</code>
|
||||
decorator, which makes that Nest will always create the new instance of the module, when it's imported by another one.
|
||||
</p>
|
||||
<pre><code class="language-typescript">{{ singleScope }}</code></pre>
|
||||
<h3>Dependency Injection</h3>
|
||||
<p>
|
||||
It's natural that module can <strong>inject</strong> components, which belongs to it (e.g. for the configuration purposes):
|
||||
</p>
|
||||
<span class="filename">cats.module.ts</span>
|
||||
<pre><code class="language-typescript">{{ catsModuleDi }}</code></pre>
|
||||
<p>
|
||||
However, modules can't be injected by the components, because it creates a <a href="/advanced/circular-dependency">circular dependency</a>.
|
||||
</p>
|
||||
</div>
|
||||
25
src/app/homepage/pages/modules/modules.component.spec.ts
Normal file
25
src/app/homepage/pages/modules/modules.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModulesComponent } from './modules.component';
|
||||
|
||||
describe('ModulesComponent', () => {
|
||||
let component: ModulesComponent;
|
||||
let fixture: ComponentFixture<ModulesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ModulesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ModulesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
81
src/app/homepage/pages/modules/modules.component.ts
Normal file
81
src/app/homepage/pages/modules/modules.component.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BasePageComponent } from '../page/page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modules',
|
||||
templateUrl: './modules.component.html',
|
||||
styleUrls: ['./modules.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ModulesComponent extends BasePageComponent {
|
||||
get catsModule() {
|
||||
return `
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CatsController } from './cats.controller';
|
||||
import { CatsService } from './cats.service';
|
||||
|
||||
@Module({
|
||||
controllers: [CatsController],
|
||||
components: [CatsService],
|
||||
})
|
||||
export class CatsModule {}
|
||||
`;
|
||||
}
|
||||
|
||||
get appModule() {
|
||||
return `
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CatsModule } from './cats/cats.module';
|
||||
|
||||
@Module({
|
||||
modules: [CatsModule],
|
||||
})
|
||||
export class ApplicationModule {}
|
||||
`;
|
||||
}
|
||||
|
||||
get catsModuleShared() {
|
||||
return `
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CatsController } from './cats.controller';
|
||||
import { CatsService } from './cats.service';
|
||||
|
||||
@Module({
|
||||
controllers: [CatsController],
|
||||
components: [CatsService],
|
||||
exports: [CatsService]
|
||||
})
|
||||
export class CatsModule {}
|
||||
`;
|
||||
}
|
||||
|
||||
get singleScope() {
|
||||
return `
|
||||
import { Module, SingleScope } from '@nestjs/common';
|
||||
import { CatsController } from './cats.controller';
|
||||
import { CatsService } from './cats.service';
|
||||
|
||||
@SingleScope()
|
||||
@Module({
|
||||
controllers: [CatsController],
|
||||
components: [CatsService],
|
||||
exports: [CatsService]
|
||||
})
|
||||
export class CatsModule {}`;
|
||||
}
|
||||
|
||||
get catsModuleDi() {
|
||||
return `
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CatsController } from './cats.controller';
|
||||
import { CatsService } from './cats.service';
|
||||
|
||||
@Module({
|
||||
controllers: [CatsController],
|
||||
components: [CatsService],
|
||||
})
|
||||
export class CatsModule {
|
||||
constructor(private readonly catsService: CatsService) {}
|
||||
}`;
|
||||
}
|
||||
}
|
||||
33
src/app/homepage/pages/page/page.component.ts
Normal file
33
src/app/homepage/pages/page/page.component.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
declare var Prism;
|
||||
import { ChangeDetectorRef, ElementRef, AfterViewChecked, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-base-page',
|
||||
template: ``,
|
||||
})
|
||||
export class BasePageComponent implements AfterViewChecked {
|
||||
private isHljsInitialized = false;
|
||||
|
||||
constructor(
|
||||
private readonly cd: ChangeDetectorRef,
|
||||
private readonly el: ElementRef) {}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
this.initHljs();
|
||||
}
|
||||
|
||||
private initHljs() {
|
||||
if (this.isHljsInitialized) {
|
||||
return;
|
||||
}
|
||||
const tags = this.el.nativeElement.querySelectorAll('code');
|
||||
[].forEach.call(tags, (code: HTMLElement) => {
|
||||
if (code.className) {
|
||||
Prism.highlightElement(code);
|
||||
this.isHljsInitialized = true;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
3
src/app/homepage/pages/pipes/pipes.component.html
Normal file
3
src/app/homepage/pages/pipes/pipes.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<p>
|
||||
pipes works!
|
||||
</p>
|
||||
0
src/app/homepage/pages/pipes/pipes.component.scss
Normal file
0
src/app/homepage/pages/pipes/pipes.component.scss
Normal file
25
src/app/homepage/pages/pipes/pipes.component.spec.ts
Normal file
25
src/app/homepage/pages/pipes/pipes.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PipesComponent } from './pipes.component';
|
||||
|
||||
describe('PipesComponent', () => {
|
||||
let component: PipesComponent;
|
||||
let fixture: ComponentFixture<PipesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PipesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PipesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
15
src/app/homepage/pages/pipes/pipes.component.ts
Normal file
15
src/app/homepage/pages/pipes/pipes.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pipes',
|
||||
templateUrl: './pipes.component.html',
|
||||
styleUrls: ['./pipes.component.scss']
|
||||
})
|
||||
export class PipesComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
27
src/app/store/app-store.module.ts
Normal file
27
src/app/store/app-store.module.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { rootReducers } from './root-reducers';
|
||||
import { rootInitialState } from './initial-state';
|
||||
import { AppState } from './common';
|
||||
import { rootEffects } from './root-effects';
|
||||
|
||||
const options = {
|
||||
initialState: rootInitialState,
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
StoreModule.forRoot<AppState>(
|
||||
rootReducers,
|
||||
options,
|
||||
),
|
||||
EffectsModule.forRoot(rootEffects),
|
||||
],
|
||||
exports: [
|
||||
StoreModule,
|
||||
EffectsModule,
|
||||
]
|
||||
})
|
||||
export class AppStoreModule { }
|
||||
1
src/app/store/common/index.ts
Normal file
1
src/app/store/common/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './interfaces';
|
||||
4
src/app/store/common/interfaces/action.interface.ts
Normal file
4
src/app/store/common/interfaces/action.interface.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Action<T> {
|
||||
type: string;
|
||||
payload?: T;
|
||||
}
|
||||
8
src/app/store/common/interfaces/app-state.interface.ts
Normal file
8
src/app/store/common/interfaces/app-state.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { RouterReducerState } from '@ngrx/router-store';
|
||||
|
||||
import { UserState } from '../../user/interfaces/user-state.interface';
|
||||
|
||||
export interface AppState {
|
||||
router: RouterReducerState;
|
||||
user: UserState;
|
||||
}
|
||||
2
src/app/store/common/interfaces/index.ts
Normal file
2
src/app/store/common/interfaces/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './action.interface';
|
||||
export * from './app-state.interface';
|
||||
7
src/app/store/initial-state.ts
Normal file
7
src/app/store/initial-state.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AppState } from './common';
|
||||
|
||||
type Partial<T> = {
|
||||
[P in keyof T]?: T[P];
|
||||
};
|
||||
|
||||
export const rootInitialState: Partial<AppState> = {};
|
||||
3
src/app/store/root-effects.ts
Normal file
3
src/app/store/root-effects.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const rootEffects = [
|
||||
|
||||
];
|
||||
8
src/app/store/root-reducers.ts
Normal file
8
src/app/store/root-reducers.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { routerReducer } from '@ngrx/router-store';
|
||||
|
||||
import { userReducer } from './user/reducer';
|
||||
|
||||
export const rootReducers: any = {
|
||||
router: routerReducer,
|
||||
user: userReducer,
|
||||
};
|
||||
3
src/app/store/user/interfaces/user-state.interface.ts
Normal file
3
src/app/store/user/interfaces/user-state.interface.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface UserState {
|
||||
accessToken: string;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user