Skip to content

Commit

Permalink
feat: add ngx-admi to Angular app, add swagger to NestJS
Browse files Browse the repository at this point in the history
  • Loading branch information
wlucha committed Jan 7, 2022
1 parent 5f4f443 commit a23b77b
Show file tree
Hide file tree
Showing 68 changed files with 7,652 additions and 8,538 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ This is a Angular 13 + NestJS 8 boilerplate to easily start your own frontend +
- a delightful JavaScript Testing Framework with a focus on simplicity<br />
- https://jestjs.io/<br />

✅ Ngx-admin<br />
- Customizable admin dashboard template based on Angular 13+<br />
- https://github.com/akveo/ngx-admin<br />

## Demo
- [StackBlitz Demo](https://stackblitz.com/github/wlucha/angular-nest)

Expand Down
14 changes: 12 additions & 2 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,19 @@
"src/angular/assets"
],
"styles": [
"src/angular/styles.css"
"src/angular/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.css",
"node_modules/typeface-exo/index.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css",
"node_modules/ionicons/scss/ionicons.scss",
"node_modules/socicon/css/socicon.css",
"node_modules/nebular-icons/scss/nebular-icons.scss",
"node_modules/pace-js/templates/pace-theme-flash.tmpl.css",
"src/angular/app/@theme/styles/styles.scss"
],
"scripts": [
"node_modules/pace-js/pace.min.js"
],
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
Expand Down
12,782 changes: 4,823 additions & 7,959 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,40 @@
},
"dependencies": {
"@angular/animations": "~13.1.1",
"@angular/cdk": "^13.1.1",
"@angular/common": "~13.1.1",
"@angular/compiler": "~13.1.1",
"@angular/core": "~13.1.1",
"@angular/forms": "~13.1.1",
"@angular/platform-browser": "~13.1.1",
"@angular/platform-browser-dynamic": "~13.1.1",
"@angular/router": "~13.1.1",
"@nebular/auth": "9.0.0",
"@nebular/eva-icons": "9.0.0",
"@nebular/security": "9.0.0",
"@nebular/theme": "9.0.0",
"@nestjs/common": "^8.2.4",
"@nestjs/core": "^8.2.4",
"@nestjs/platform-express": "^8.2.4",
"@nestjs/swagger": "^5.1.5",
"bootstrap": "4.3.1",
"classlist.js": "1.1.20150312",
"eva-icons": "^1.1.3",
"intl": "1.2.5",
"ionicons": "2.0.1",
"nebular-icons": "1.1.0",
"node-sass": "^4.12.0",
"normalize.css": "6.0.0",
"pace-js": "1.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"roboto-fontface": "0.8.0",
"rxjs": "~6.5.4",
"socicon": "3.0.5",
"style-loader": "^1.1.3",
"swagger-ui-express": "^4.3.0",
"tslib": "^2.0.0",
"typeface-exo": "0.0.22",
"zone.js": "~0.11.4"
},
"devDependencies": {
Expand Down
106 changes: 106 additions & 0 deletions src/angular/app/@core/core.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NbAuthModule, NbDummyAuthStrategy } from '@nebular/auth';
import { NbSecurityModule, NbRoleProvider } from '@nebular/security';
import { of as observableOf } from 'rxjs';

import { throwIfAlreadyLoaded } from './module-import-guard';
import { AnalyticsService, SeoService } from './utils';
import { UserData } from './data/users';
import { UserService } from './mock/users.service';
import { MockDataModule } from './mock/mock-data.module';

const socialLinks = [
{
url: 'https://github.com/akveo/nebular',
target: '_blank',
icon: 'github',
},
{
url: 'https://www.facebook.com/akveo/',
target: '_blank',
icon: 'facebook',
},
{
url: 'https://twitter.com/akveo_inc',
target: '_blank',
icon: 'twitter',
},
];

const DATA_SERVICES = [
{ provide: UserData, useClass: UserService },
];

export class NbSimpleRoleProvider extends NbRoleProvider {
getRole() {
// here you could provide any role based on any auth flow
return observableOf('guest');
}
}

export const NB_CORE_PROVIDERS = [
...MockDataModule.forRoot().providers,
...DATA_SERVICES,
...NbAuthModule.forRoot({

strategies: [
NbDummyAuthStrategy.setup({
name: 'email',
delay: 3000,
}),
],
forms: {
login: {
socialLinks: socialLinks,
},
register: {
socialLinks: socialLinks,
},
},
}).providers,

NbSecurityModule.forRoot({
accessControl: {
guest: {
view: '*',
},
user: {
parent: 'guest',
create: '*',
edit: '*',
remove: '*',
},
},
}).providers,

{
provide: NbRoleProvider, useClass: NbSimpleRoleProvider,
},
AnalyticsService,
SeoService,
];

@NgModule({
imports: [
CommonModule,
],
exports: [
NbAuthModule,
],
declarations: [],
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
throwIfAlreadyLoaded(parentModule, 'CoreModule');
}

static forRoot(): ModuleWithProviders<CoreModule> {
return {
ngModule: CoreModule,
providers: [
...NB_CORE_PROVIDERS,
],
};
}
}
File renamed without changes.
1 change: 1 addition & 0 deletions src/angular/app/@core/data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Application-wise data providers.
21 changes: 21 additions & 0 deletions src/angular/app/@core/data/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Observable } from 'rxjs';

export interface User {
name: string;
picture: string;
}

export interface Contacts {
user: User;
type: string;
}

export interface RecentUsers extends Contacts {
time: number;
}

export abstract class UserData {
abstract getUsers(): Observable<User[]>;
abstract getContacts(): Observable<Contacts[]>;
abstract getRecentUsers(): Observable<RecentUsers[]>;
}
1 change: 1 addition & 0 deletions src/angular/app/@core/mock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Application-wise data providers.
27 changes: 27 additions & 0 deletions src/angular/app/@core/mock/mock-data.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';

import { UserService } from './users.service';

const SERVICES = [
UserService,
];

@NgModule({
imports: [
CommonModule,
],
providers: [
...SERVICES,
],
})
export class MockDataModule {
static forRoot(): ModuleWithProviders<MockDataModule> {
return {
ngModule: MockDataModule,
providers: [
...SERVICES,
],
};
}
}
53 changes: 53 additions & 0 deletions src/angular/app/@core/mock/users.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { of as observableOf, Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { Contacts, RecentUsers, UserData } from '../data/users';

@Injectable()
export class UserService extends UserData {

private time: Date = new Date;

private users = {
nick: { name: 'Nick Jones', picture: 'assets/images/nick.png' },
eva: { name: 'Eva Moor', picture: 'assets/images/eva.png' },
jack: { name: 'Jack Williams', picture: 'assets/images/jack.png' },
lee: { name: 'Lee Wong', picture: 'assets/images/lee.png' },
alan: { name: 'Alan Thompson', picture: 'assets/images/alan.png' },
kate: { name: 'Kate Martinez', picture: 'assets/images/kate.png' },
};
private types = {
mobile: 'mobile',
home: 'home',
work: 'work',
};
private contacts: Contacts[] = [
{ user: this.users.nick, type: this.types.mobile },
{ user: this.users.eva, type: this.types.home },
{ user: this.users.jack, type: this.types.mobile },
{ user: this.users.lee, type: this.types.mobile },
{ user: this.users.alan, type: this.types.home },
{ user: this.users.kate, type: this.types.work },
];
private recentUsers: RecentUsers[] = [
{ user: this.users.alan, type: this.types.home, time: this.time.setHours(21, 12)},
{ user: this.users.eva, type: this.types.home, time: this.time.setHours(17, 45)},
{ user: this.users.nick, type: this.types.mobile, time: this.time.setHours(5, 29)},
{ user: this.users.lee, type: this.types.mobile, time: this.time.setHours(11, 24)},
{ user: this.users.jack, type: this.types.mobile, time: this.time.setHours(10, 45)},
{ user: this.users.kate, type: this.types.work, time: this.time.setHours(9, 42)},
{ user: this.users.kate, type: this.types.work, time: this.time.setHours(9, 31)},
{ user: this.users.jack, type: this.types.mobile, time: this.time.setHours(8, 0)},
];

getUsers(): Observable<any> {
return observableOf(this.users);
}

getContacts(): Observable<Contacts[]> {
return observableOf(this.contacts);
}

getRecentUsers(): Observable<RecentUsers[]> {
return observableOf(this.recentUsers);
}
}
5 changes: 5 additions & 0 deletions src/angular/app/@core/module-import-guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
if (parentModule) {
throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
}
}
Empty file.
32 changes: 32 additions & 0 deletions src/angular/app/@core/utils/analytics.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { Location } from '@angular/common';
import { filter } from 'rxjs/operators';

declare const ga: any;

@Injectable()
export class AnalyticsService {
private enabled: boolean;

constructor(private location: Location, private router: Router) {
this.enabled = false;
}

trackPageViews() {
if (this.enabled) {
this.router.events.pipe(
filter((event) => event instanceof NavigationEnd),
)
.subscribe(() => {
ga('send', {hitType: 'pageview', page: this.location.path()});
});
}
}

trackEvent(eventName: string) {
if (this.enabled) {
ga('send', 'event', eventName);
}
}
}
2 changes: 2 additions & 0 deletions src/angular/app/@core/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AnalyticsService } from './analytics.service';
export { SeoService } from './seo.service';
58 changes: 58 additions & 0 deletions src/angular/app/@core/utils/seo.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Injectable, Inject, PLATFORM_ID, OnDestroy } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { NavigationEnd, Router } from '@angular/router';
import { NB_DOCUMENT } from '@nebular/theme';
import { filter, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';

@Injectable()
export class SeoService implements OnDestroy {

private readonly destroy$ = new Subject<void>();
private readonly dom: Document;
private readonly isBrowser: boolean;
private linkCanonical: HTMLLinkElement;

constructor(
private router: Router,
@Inject(NB_DOCUMENT) document,
@Inject(PLATFORM_ID) platformId,
) {
this.isBrowser = isPlatformBrowser(platformId);
this.dom = document;

if (this.isBrowser) {
this.createCanonicalTag();
}
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}

createCanonicalTag() {
this.linkCanonical = this.dom.createElement('link');
this.linkCanonical.setAttribute('rel', 'canonical');
this.dom.head.appendChild(this.linkCanonical);
this.linkCanonical.setAttribute('href', this.getCanonicalUrl());
}

trackCanonicalChanges() {
if (!this.isBrowser) {
return;
}

this.router.events.pipe(
filter((event) => event instanceof NavigationEnd),
takeUntil(this.destroy$),
)
.subscribe(() => {
this.linkCanonical.setAttribute('href', this.getCanonicalUrl());
});
}

private getCanonicalUrl(): string {
return this.dom.location.origin + this.dom.location.pathname;
}
}
Loading

0 comments on commit a23b77b

Please sign in to comment.