├── src
├── app
│ ├── home
│ │ ├── page-error
│ │ │ ├── page-error.component.scss
│ │ │ ├── page-error.component.html
│ │ │ ├── page-error.component.spec.ts
│ │ │ └── page-error.component.ts
│ │ ├── page-not-found
│ │ │ ├── page-not-found.component.scss
│ │ │ ├── page-not-found.component.html
│ │ │ ├── page-not-found.component.ts
│ │ │ └── page-not-found.component.spec.ts
│ │ ├── home.component.html
│ │ ├── home.component.ts
│ │ ├── log-out
│ │ │ ├── log-out.component.html
│ │ │ ├── log-out.component.scss
│ │ │ ├── log-out.component.ts
│ │ │ └── log-out.component.spec.ts
│ │ └── home.component.scss
│ ├── shared
│ │ ├── directives
│ │ │ ├── calendar
│ │ │ │ ├── calendar.component.css
│ │ │ │ ├── calendar.component.html
│ │ │ │ ├── calendar.component.spec.ts
│ │ │ │ └── calendar.component.ts
│ │ │ ├── confirm-dialog
│ │ │ │ ├── confirm-dialog.component.css
│ │ │ │ ├── confirm-dialog.component.html
│ │ │ │ ├── confirm-dialog.component.ts
│ │ │ │ └── parts-confirm-dialog.component.spec.ts
│ │ │ ├── alert
│ │ │ │ ├── alert.component.css
│ │ │ │ ├── alert.component.ts
│ │ │ │ └── alert.component.html
│ │ │ ├── error-message
│ │ │ │ ├── error-message.component.scss
│ │ │ │ ├── error-message.component.ts
│ │ │ │ ├── error-message.component.html
│ │ │ │ └── error-message.component.spec.ts
│ │ │ ├── accordion
│ │ │ │ ├── accordion.component.html
│ │ │ │ ├── accordion.component.ts
│ │ │ │ └── accordion.component.css
│ │ │ ├── dialog
│ │ │ │ ├── dialog.component.html
│ │ │ │ ├── dialog.component.css
│ │ │ │ └── dialog.component.ts
│ │ │ ├── form-list
│ │ │ │ ├── form-list.component.scss
│ │ │ │ └── form-list.component.html
│ │ │ ├── menu
│ │ │ │ ├── menu.component.html
│ │ │ │ ├── menu.component.spec.ts
│ │ │ │ ├── menu.component.scss
│ │ │ │ └── menu.component.ts
│ │ │ ├── hide-if-unauthorized
│ │ │ │ └── hide-if-unauthorized.directive.ts
│ │ │ ├── disable-if-unauthorized
│ │ │ │ └── disable-if-unauthorized.directive.ts
│ │ │ ├── data-table
│ │ │ │ └── data-table-models.ts
│ │ │ ├── input
│ │ │ │ └── input.component.scss
│ │ │ └── form-field
│ │ │ │ └── form-field.component.scss
│ │ ├── models
│ │ │ ├── confirm-choices.enum.ts
│ │ │ └── app-monitor.ts
│ │ └── pipes
│ │ │ ├── display-error.pipe.ts
│ │ │ ├── date-to-string.pipe.ts
│ │ │ └── date-to-string.pipe.spec.ts
│ ├── demo
│ │ ├── accounts
│ │ │ ├── child-component1
│ │ │ │ ├── child-component1.component.scss
│ │ │ │ ├── child-component1-resolver.service.ts
│ │ │ │ └── child-component1.component.ts
│ │ │ ├── child-component3
│ │ │ │ ├── child-component3.component.scss
│ │ │ │ ├── child-component3-resolver.service.ts
│ │ │ │ ├── child-component3.component.ts
│ │ │ │ └── child-component3.component.spec.ts
│ │ │ ├── shared
│ │ │ │ ├── components
│ │ │ │ │ └── navigation-error
│ │ │ │ │ │ ├── navigation-error.component.scss
│ │ │ │ │ │ ├── navigation-error.component.html
│ │ │ │ │ │ ├── navigation-error.component.ts
│ │ │ │ │ │ └── navigation-error.component.spec.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── account.models.ts
│ │ │ │ │ ├── child2-entity.models.ts
│ │ │ │ │ ├── child3-entity.models.ts
│ │ │ │ │ └── child1-entity.models.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── demo-account-resolver.service.ts
│ │ │ │ │ ├── child1-data.service.ts
│ │ │ │ │ ├── child2-data.service.ts
│ │ │ │ │ ├── child3-data.service.ts
│ │ │ │ │ ├── account-data.service.ts
│ │ │ │ │ └── menu.service.ts
│ │ │ │ └── directives
│ │ │ │ │ ├── header-menu
│ │ │ │ │ ├── header-menu.component.html
│ │ │ │ │ └── header-menu.component.ts
│ │ │ │ │ └── account-header
│ │ │ │ │ ├── account-header.component.html
│ │ │ │ │ ├── account-header.component.scss
│ │ │ │ │ ├── account-header.component.spec.ts
│ │ │ │ │ ├── account-header.service.spec.ts
│ │ │ │ │ ├── account-header.component.ts
│ │ │ │ │ └── account-header.service.ts
│ │ │ └── child-component2
│ │ │ │ ├── child-component2.component.scss
│ │ │ │ ├── child-component2.component.ts
│ │ │ │ ├── child-component2-resolver.service.ts
│ │ │ │ ├── child-component2.component.html
│ │ │ │ └── child-component2.component.spec.ts
│ │ ├── search
│ │ │ └── search-results
│ │ │ │ ├── search-results.component.scss
│ │ │ │ ├── search-results.component.html
│ │ │ │ ├── search-results-resolver.service.ts
│ │ │ │ ├── search-results.component.spec.ts
│ │ │ │ └── search-results.component.ts
│ │ └── demo.module.ts
│ ├── demo-common
│ │ ├── directives
│ │ │ ├── transaction-confirm-dialog
│ │ │ │ ├── transaction-confirm-dialog.component.css
│ │ │ │ ├── transaction-confirm-dialog.component.spec.ts
│ │ │ │ ├── transaction-confirm-dialog.component.html
│ │ │ │ └── transaction-confirm-dialog.component.ts
│ │ │ ├── top-nav
│ │ │ │ ├── top-nav.component.ts
│ │ │ │ ├── top-nav.component.html
│ │ │ │ ├── top-nav.component.spec.ts
│ │ │ │ └── top-nav.component.scss
│ │ │ ├── component-header
│ │ │ │ ├── component-header.component.scss
│ │ │ │ ├── component-header.component.spec.ts
│ │ │ │ ├── component-header.component.html
│ │ │ │ └── component-header.component.ts
│ │ │ └── header-bar
│ │ │ │ ├── header-bar.component.spec.ts
│ │ │ │ ├── header-bar.component.html
│ │ │ │ ├── header-bar.component.ts
│ │ │ │ └── header-bar.component.scss
│ │ ├── models
│ │ │ ├── account-search-result.models.ts
│ │ │ ├── transaction.models.ts
│ │ │ └── app-config.model.ts
│ │ ├── services
│ │ │ ├── search-data.service.ts
│ │ │ ├── user-session.service.spec.ts
│ │ │ ├── user-session.service.ts
│ │ │ ├── demo-common-data.service.ts
│ │ │ ├── search.service.spec.ts
│ │ │ ├── transaction-data.service.ts
│ │ │ ├── search.service.ts
│ │ │ ├── transaction-entity-data.service.ts
│ │ │ └── demo-resolver.service.ts
│ │ └── demo-common.module.ts
│ ├── framework
│ │ ├── models
│ │ │ ├── authorization.types.ts
│ │ │ ├── metadata.models.ts
│ │ │ └── form-controls.models.ts
│ │ ├── logging
│ │ │ └── severity-level.model.ts
│ │ ├── services
│ │ │ ├── url-serializer.service.ts
│ │ │ ├── system-message-data.service.ts
│ │ │ ├── authorization-data.service.ts
│ │ │ ├── metadata-data.service.ts
│ │ │ ├── web-storage.service.ts
│ │ │ ├── auth.service.ts
│ │ │ ├── auth-interceptor.service.ts
│ │ │ ├── system-message.service.ts
│ │ │ ├── can-deactivate-guard.service.ts
│ │ │ ├── global-events.service.ts
│ │ │ ├── system-message.service.spec.ts
│ │ │ ├── authorization.service.ts
│ │ │ ├── metadata.service.ts
│ │ │ ├── utilities.service.spec.ts
│ │ │ ├── authorization.service.spec.ts
│ │ │ ├── auth.service.spec.ts
│ │ │ └── auth-guard.service.ts
│ │ ├── validation
│ │ │ ├── models
│ │ │ │ └── server-error.models.ts
│ │ │ └── validation.module.ts
│ │ ├── errorhandling
│ │ │ ├── error-handler.service.ts
│ │ │ ├── error-handler.service.spec.ts
│ │ │ └── error-utilities.service.ts
│ │ ├── components
│ │ │ └── base-component.spec.ts
│ │ └── framework.module.ts
│ ├── app.component.html
│ ├── app-injector.service.spec.ts
│ ├── app-injector.service.ts
│ ├── app.component.scss
│ ├── app-routing.module.ts
│ ├── app.config.ts
│ └── app.module.ts
├── environments
│ ├── environment.ts
│ ├── environment.prod.ts
│ └── environment.deploy.ts
├── favicon.ico
├── assets
│ ├── loading.gif
│ └── config
│ │ ├── config.deploy.json
│ │ ├── config.local.json
│ │ ├── config.dev.json
│ │ └── config.prod.json
├── scss
│ ├── dimensions.scss
│ └── colors.scss
├── typings.d.ts
├── index.html
├── tsconfig.app.json
├── tsconfig.spec.json
├── main.ts
├── test.ts
├── web.config
├── polyfills.ts
└── variables.less
├── .editorconfig
├── tsconfig.json
├── README.md
├── .gitignore
├── protractor.conf.js
├── karma.conf.js
├── website.publishproj
├── package.json
└── tslint.json
/src/app/home/page-error/page-error.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/home/page-not-found/page-not-found.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/shared/directives/calendar/calendar.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/child-component1/child-component1.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/child-component3/child-component3.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/shared/directives/confirm-dialog/confirm-dialog.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/components/navigation-error/navigation-error.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/home/page-error/page-error.component.html:
--------------------------------------------------------------------------------
1 |
{{errorMessage}}
2 |
3 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | name: 'dev'
3 | };
4 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/transaction-confirm-dialog/transaction-confirm-dialog.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | name: 'prod'
3 | };
4 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laurieatkinson/ng-patterns-demo/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/environments/environment.deploy.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | name: 'deploy'
3 | };
4 |
--------------------------------------------------------------------------------
/src/app/framework/models/authorization.types.ts:
--------------------------------------------------------------------------------
1 | export type ActionCode =
2 | 'VIEW' |
3 | 'UPDATE';
4 |
--------------------------------------------------------------------------------
/src/assets/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laurieatkinson/ng-patterns-demo/HEAD/src/assets/loading.gif
--------------------------------------------------------------------------------
/src/scss/dimensions.scss:
--------------------------------------------------------------------------------
1 | $calendar-button-width:28px;
2 | $calendar-button-height:34px;
3 | $tile-fixed-height:400px;
--------------------------------------------------------------------------------
/src/app/home/page-not-found/page-not-found.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Page does not exist
3 |
--------------------------------------------------------------------------------
/src/app/shared/models/confirm-choices.enum.ts:
--------------------------------------------------------------------------------
1 | export enum ConfirmChoice {
2 | save,
3 | discard,
4 | cancel
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/shared/directives/alert/alert.component.css:
--------------------------------------------------------------------------------
1 | .fa, .glyphicon {
2 | color: inherit !important;
3 | vertical-align: middle;
4 | }
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/framework/logging/severity-level.model.ts:
--------------------------------------------------------------------------------
1 | export enum SeverityLevel {
2 | Verbose = 0,
3 | Information = 1,
4 | Warning = 2,
5 | Error = 3,
6 | Critical = 4,
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Home Page
4 | Welcome to a class inheritance demo
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | templateUrl: './home.component.html',
5 | styleUrls: ['./home.component.scss']
6 | })
7 | export class HomeComponent {
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/shared/directives/error-message/error-message.component.scss:
--------------------------------------------------------------------------------
1 | h2 {
2 | margin-top: 0px;
3 | }
4 | li {
5 | font-size: 14px;
6 | font-family: Tahoma, Helvetica, Arial, sans-serif;
7 | font-weight: normal;
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/child-component2/child-component2.component.scss:
--------------------------------------------------------------------------------
1 | pn-field.truncate-label > div > div > div.form-group > p.label {
2 | width: 80px;
3 | white-space: nowrap;
4 | overflow: hidden;
5 | text-overflow: ellipsis;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/src/app/demo-common/models/account-search-result.models.ts:
--------------------------------------------------------------------------------
1 | export interface IAccountSearchResult {
2 | accountCode: string;
3 | accountName: string;
4 | accountType: string;
5 | accountStatus: number;
6 | accountStatusDisplay: string;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/shared/directives/accordion/accordion.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{heading}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/app/framework/models/metadata.models.ts:
--------------------------------------------------------------------------------
1 | export interface ITimePeriod {
2 | period: string;
3 | frequency: string;
4 | freqPerYear: number;
5 | uniqueId: number;
6 | }
7 |
8 | export interface ILookupList {
9 | [K: string]: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/framework/services/url-serializer.service.ts:
--------------------------------------------------------------------------------
1 | import { DefaultUrlSerializer, UrlTree } from '@angular/router';
2 |
3 | export class LowerCaseUrlSerializer extends DefaultUrlSerializer {
4 | parse(url: string): UrlTree {
5 | return super.parse(url.toLowerCase());
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/models/account.models.ts:
--------------------------------------------------------------------------------
1 | import { IEntity } from '../../../../demo-common/models/transaction.models';
2 |
3 | export interface IAccount extends IEntity {
4 | accountCode: string;
5 | accountName: string;
6 | accountType: string;
7 | accountStatus: string;
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/home/log-out/log-out.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Thanks for using this demo
5 |
6 | We are logging you out.
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/src/app/shared/directives/dialog/dialog.component.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/app/home/page-not-found/page-not-found.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'la-page-not-found',
5 | templateUrl: './page-not-found.component.html',
6 | styleUrls: ['./page-not-found.component.scss']
7 | })
8 | export class PageNotFoundComponent {
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/top-nav/top-nav.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'la-top-nav',
5 | templateUrl: './top-nav.component.html',
6 | styleUrls: ['./top-nav.component.scss'],
7 | encapsulation: ViewEncapsulation.None
8 | })
9 | export class TopNavComponent {
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/home/log-out/log-out.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../scss/colors';
2 | @import '../../../scss/mixins';
3 |
4 | @include generic-button();
5 |
6 | #post-btn {
7 | @include submit-button();
8 | }
9 |
10 | .btn {
11 | margin-top: 8px;
12 | padding: 15px 18px 15px 18px;
13 | border-radius: 4px;
14 | }
15 |
16 | .well {
17 | padding: 40px 0 40px 0;
18 | }
--------------------------------------------------------------------------------
/src/app/framework/validation/models/server-error.models.ts:
--------------------------------------------------------------------------------
1 | export interface IServerError {
2 | details: Array<{ code: number, message: string, target: string }>;
3 | code: number;
4 | message: string;
5 | target: string;
6 | }
7 |
8 | export interface INavigationError {
9 | message?: string;
10 | serverError?: IServerError;
11 | navigatingTo: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/demo/search/search-results/search-results.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../scss/colors';
2 | @import '../../../../scss/mixins';
3 | @include white-background();
4 |
5 | .white-bg {
6 | padding-top: 1px !important;
7 | }
8 | .include-inactive {
9 | position: relative;
10 | float: left;
11 | }
12 | .include-inactive {
13 | position: relative;
14 | top: .5em;
15 | }
--------------------------------------------------------------------------------
/src/app/framework/models/form-controls.models.ts:
--------------------------------------------------------------------------------
1 | import { ActionCode } from './authorization.types';
2 | export interface IPartsFormControl {
3 | name: string;
4 | controls?: Array>;
5 | defaultValue?: any;
6 | disableIfNotAuthorized?: ActionCode;
7 | }
8 |
9 | export interface IPartsListChoice {
10 | value: string | number;
11 | label: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/shared/directives/error-message/error-message.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'la-error-message',
5 | templateUrl: './error-message.component.html',
6 | styleUrls: ['./error-message.component.scss']
7 | })
8 | export class ErrorMessageComponent {
9 |
10 | @Input() errorList: Array = [];
11 | @Input() header: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/framework/services/system-message-data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { DataService } from './data.service';
3 |
4 | @Injectable()
5 | export class SystemMessageDataService extends DataService {
6 |
7 | getMessage(id: number): Promise {
8 | const endpoint = `${this.apiServer.metadata}messages/${id}`;
9 | return this.get(endpoint);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/framework/services/authorization-data.service.ts:
--------------------------------------------------------------------------------
1 | import { DataService } from './data.service';
2 | import { ActionCode } from '../models/authorization.types';
3 |
4 | export class AuthorizationDataService extends DataService {
5 |
6 | getPermissions(): Promise> {
7 | const endpoint = `${this.apiServer.metadata}authorizations`;
8 | return this.get>(endpoint);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/shared/directives/error-message/error-message.component.html:
--------------------------------------------------------------------------------
1 | 0" class="alert alert-danger">
2 |
{{header}}
3 |
1">
4 | -
5 | {{error}}
6 |
7 |
8 |
9 | {{errorList}}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |

12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Angular Patterns Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Loading...
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/app/shared/directives/alert/alert.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'la-alert',
5 | templateUrl: './alert.component.html',
6 | styleUrls: ['./alert.component.css'],
7 | })
8 | export class AlertComponent {
9 |
10 | @Input() showAlert = true;
11 | @Input() alertType = 'danger';
12 |
13 | toggle() {
14 | this.showAlert = !this.showAlert;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/top-nav/top-nav.component.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/app/demo-common/models/transaction.models.ts:
--------------------------------------------------------------------------------
1 | export interface IAccountTransactionObject {
2 | accountCode: string;
3 | accountName: string;
4 | transactionId: number;
5 | description: string;
6 | }
7 |
8 | export interface INewTransaction {
9 | accountCode: string;
10 | description: string;
11 | }
12 |
13 | export interface ITransactionIdentifier {
14 | id: number;
15 | }
16 |
17 | export interface IEntity {
18 | transactionIdentifier: ITransactionIdentifier;
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/demo-common/services/search-data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { IAccountSearchResult } from '../models/account-search-result.models';
3 | import { DemoCommonDataService } from './demo-common-data.service';
4 |
5 | @Injectable()
6 | export class SearchDataService extends DemoCommonDataService {
7 |
8 | getAccountSearchResults() {
9 | const endpoint = `${this.apiServer.rules}accounts/`;
10 | return this.get>(endpoint);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/shared/directives/alert/alert.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
×
7 |
8 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016",
10 | "dom"
11 | ],
12 | "outDir": "../out-tsc/app",
13 | "target": "es5",
14 | "module": "es2015",
15 | "baseUrl": "",
16 | "types": []
17 | },
18 | "exclude": [
19 | "test.ts",
20 | "**/*.spec.ts",
21 | "**/testing/*.ts"
22 | ]
23 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "baseUrl": "src",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "moduleResolution": "node",
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "node_modules/@types"
14 | ],
15 | "lib": [
16 | "es2016",
17 | "dom"
18 | ],
19 | "types": [
20 | "jasmine"
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/demo/search/search-results/search-results.component.html:
--------------------------------------------------------------------------------
1 | Account Selection
2 |
11 |
--------------------------------------------------------------------------------
/src/app/shared/directives/calendar/calendar.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/top-nav/top-nav.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TopNavComponent } from './top-nav.component';
2 | import { TestInjector } from '../../testing/testing-helpers';
3 |
4 | describe('TopNavComponent', () => {
5 | let component: TopNavComponent;
6 | beforeAll(() => {
7 | TestInjector.setInjector();
8 | });
9 |
10 | beforeEach(() => {
11 | component = new TopNavComponent();
12 | });
13 |
14 | it('should create', () => {
15 | expect(component).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/components/navigation-error/navigation-error.component.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/models/child2-entity.models.ts:
--------------------------------------------------------------------------------
1 | import { IPartsListChoice } from '../../../../framework/models/form-controls.models';
2 | import { IEntity } from '../../../../demo-common/models/transaction.models';
3 |
4 | export interface IChild2Entity extends IEntity {
5 | accountType: string;
6 | contactName: string;
7 | contactPhoneAreaCode: string;
8 | contactPhoneNumber: string;
9 | name1: string;
10 | name2: string;
11 | name3: string;
12 | rate: number;
13 | accountTypeList: Array;
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/framework/services/metadata-data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { DataService } from './data.service';
3 | import { ILookupList, ITimePeriod } from '../models/metadata.models';
4 |
5 | // AJAX calls related to metadata
6 | @Injectable()
7 | export class MetadataDataService extends DataService {
8 |
9 | getLookupList(name: string): Promise> {
10 | const endpoint = `${this.apiServer.metadata}lookuplists/${name}`;
11 | return this.get>(endpoint);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/framework/validation/validation.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { HttpClientModule } from '@angular/common/http';
4 | import { ValidationService } from './services/validation.service';
5 |
6 | @NgModule({
7 | imports: [
8 | CommonModule,
9 | HttpClientModule
10 | ],
11 | declarations: [
12 | ],
13 | providers: [
14 | ValidationService
15 | ],
16 | exports: [
17 | ]
18 | })
19 | export class ValidationModule { }
20 |
21 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/component-header/component-header.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../scss/colors';
2 | @import '../../../../scss/mixins';
3 |
4 | @include generic-button();
5 |
6 | .save-button {
7 | margin-left:4px;
8 | }
9 |
10 | .row {
11 | margin-bottom: 1em;
12 | margin-top: 3em;
13 | }
14 |
15 | .header {
16 | @include font-h();
17 | margin-top: -20px;
18 | }
19 |
20 | h2, .h2 {
21 | font-family: Tahoma, Helvetica, Arial, sans-serif;
22 | font-weight: 520;
23 | font-size: 21px;
24 | // margin-top: 0px;
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/services/demo-account-resolver.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot } from '@angular/router';
3 | import { DemoResolver } from '../../../../demo-common/services/demo-resolver.service';
4 |
5 | @Injectable()
6 | export class DemoAccountResolver extends DemoResolver {
7 |
8 | resolve(route: ActivatedRouteSnapshot) {
9 | const accountCode = route.params['code'];
10 | this.userSessionService.accountCode = accountCode;
11 | return super.resolve(route);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/component-header/component-header.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentHeaderComponent } from './component-header.component';
2 | import { TestInjector } from '../../testing/testing-helpers';
3 |
4 | describe('ComponentHeaderComponent', () => {
5 | let component: ComponentHeaderComponent;
6 | beforeAll(() => {
7 | TestInjector.setInjector();
8 | });
9 |
10 | beforeEach(() => {
11 | component = new ComponentHeaderComponent();
12 | });
13 |
14 | it('should be created', () => {
15 | expect(component).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/models/child3-entity.models.ts:
--------------------------------------------------------------------------------
1 | import { IPartsListChoice } from '../../../../framework/models/form-controls.models';
2 | import { IEntity } from '../../../../demo-common/models/transaction.models';
3 |
4 | export interface IChild3Entity extends IEntity {
5 | accountCode: string;
6 | adminYearEndDate: Date;
7 | adminFrequencyPerYear: number;
8 | statementYearEndDate: Date;
9 | statementFrequencyPerYear: number;
10 | processingFrequencyPerYear: number;
11 | billingFrequencyPerYear: number;
12 | timePeriodList: Array;
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/shared/directives/accordion/accordion.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ViewEncapsulation } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'la-accordion',
5 | templateUrl: './accordion.component.html',
6 | styleUrls: ['./accordion.component.css'],
7 | encapsulation: ViewEncapsulation.None
8 | })
9 | export class AccordionComponent {
10 | @Input() heading: string;
11 | @Input() collapsed: boolean;
12 | @Input() disabled = false;
13 | @Input() visible = true;
14 |
15 | isCollapsed(): boolean {
16 | return this.collapsed;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/app-injector.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { AppInjector } from './app-injector.service';
2 | import { inject, TestBed } from '@angular/core/testing';
3 |
4 | describe('AppInjector', () => {
5 | beforeEach(() => {
6 |
7 | TestBed.configureTestingModule({
8 | providers: [
9 | AppInjector
10 | ]
11 | });
12 | });
13 |
14 | it('should only create one instance', () => {
15 | const service1 = AppInjector.getInstance();
16 | const service2 = AppInjector.getInstance();
17 | expect(service1).toBe(service2);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/app/home/page-not-found/page-not-found.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async } from '@angular/core/testing';
2 | import { TestInjector } from '../../demo-common/testing/testing-helpers';
3 | import { PageNotFoundComponent } from './page-not-found.component';
4 |
5 | describe('PageNotFoundComponent', () => {
6 | let component: PageNotFoundComponent;
7 | beforeAll(() => {
8 | TestInjector.setInjector();
9 | });
10 |
11 | beforeEach(() => {
12 | component = new PageNotFoundComponent();
13 | });
14 |
15 | it('should be created', async(() => {
16 | expect(component).toBeTruthy();
17 | }));
18 | });
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NgPatternsDemo
2 |
3 | ## Development server
4 | Run npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
5 |
6 | ## Build
7 |
8 | Run `npm run prod` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
9 |
10 | ## Running unit tests
11 |
12 | Run `npm run test` to execute the unit tests via [Karma](https://karma-runner.github.io).
13 |
14 | ## App Description
15 | Angular demo app with examples of form creation, validation, class inheritance, etc.
16 |
--------------------------------------------------------------------------------
/src/app/app-injector.service.ts:
--------------------------------------------------------------------------------
1 | import { Injector } from '@angular/core';
2 |
3 | export class AppInjector {
4 | private static instance: AppInjector;
5 | private injector: Injector;
6 |
7 | static getInstance() {
8 | if (!AppInjector.instance) {
9 | AppInjector.instance = new AppInjector();
10 | }
11 |
12 | return AppInjector.instance;
13 | }
14 |
15 | private constructor() {}
16 |
17 | setInjector(injector: Injector) {
18 | this.injector = injector;
19 | }
20 |
21 | getInjector(): Injector {
22 | return this.injector;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016",
10 | "dom"
11 | ],
12 | "outDir": "../out-tsc/spec",
13 | "target": "es5",
14 | "module": "es2015",
15 | "baseUrl": "",
16 | "types": [
17 | "jasmine",
18 | "node"
19 | ]
20 | },
21 | "files": [
22 | "test.ts",
23 | "polyfills.ts"
24 | ],
25 | "include": [
26 | "**/*.spec.ts",
27 | "**/testing/*.ts"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/shared/pipes/display-error.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'displayError'
5 | })
6 | export class DisplayErrorPipe implements PipeTransform {
7 |
8 | transform(value: Object): Array {
9 | if (!value) {
10 | return null;
11 | }
12 | const errorMessages: Array = [];
13 | Object.keys(value).forEach(key => {
14 | if (!errorMessages.includes(value[key])) {
15 | errorMessages.push(value[key]);
16 | }
17 | });
18 |
19 | return errorMessages;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/shared/directives/form-list/form-list.component.scss:
--------------------------------------------------------------------------------
1 | .inline-header {
2 | display: inline-block;
3 | vertical-align: baseline;
4 | }
5 | .readOnly {
6 | height: 100%;
7 | min-height: 34px;
8 | padding: 6px 12px;
9 | color: #555555;
10 | background-color: #eeeeee;
11 | border: 1px solid #ccc;
12 | border-radius: 4px;
13 | position: relative;
14 | font-size: 1em;
15 | overflow: hidden;
16 | white-space: nowrap;
17 | top: 2px;
18 | }
19 | .hide-labels p.label {
20 | position: absolute;
21 | height: 1px;
22 | width: 1px;
23 | overflow: hidden;
24 | text-indent: -9999px;
25 | }
--------------------------------------------------------------------------------
/src/app/shared/pipes/date-to-string.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { UtilitiesService } from '../../framework/services/utilities.service';
3 |
4 | @Pipe({name: 'dateToString'})
5 | export class DateToStringPipe implements PipeTransform {
6 | transform(value: Date, dateFormat: string) {
7 | if (!UtilitiesService.isDate(value)) {
8 | return '';
9 | }
10 | if (dateFormat === 'shortDate') {
11 | dateFormat = 'MM/DD/YY';
12 | }
13 | value.setMinutes(0, 0, 0);
14 | return UtilitiesService.convertDateToString(value, dateFormat);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/services/child1-data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { TransactionEntityDataService } from '../../../../demo-common/services/transaction-entity-data.service';
3 | import { IChild1Entity } from '../models/child1-entity.models';
4 |
5 | @Injectable()
6 | export class Child1DataService extends TransactionEntityDataService {
7 |
8 | getChild1(): Promise {
9 | const endpoint = this.endpointWithTransactionId();
10 | return this.get(endpoint);
11 | }
12 |
13 | endpoint() {
14 | return this.constructEndpoint('class1');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/services/child2-data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { TransactionEntityDataService } from '../../../../demo-common/services/transaction-entity-data.service';
3 | import { IChild2Entity } from '../models/child2-entity.models';
4 |
5 | @Injectable()
6 | export class Child2DataService extends TransactionEntityDataService {
7 |
8 | getChild2(): Promise {
9 | const endpoint = this.endpointWithTransactionId();
10 | return this.get(endpoint);
11 | }
12 |
13 | endpoint() {
14 | return this.constructEndpoint('class2');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/services/child3-data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { TransactionEntityDataService } from '../../../../demo-common/services/transaction-entity-data.service';
3 | import { IChild3Entity } from '../models/child3-entity.models';
4 |
5 | @Injectable()
6 | export class Child3DataService extends TransactionEntityDataService {
7 |
8 | getChild3(): Promise {
9 | const endpoint = this.endpointWithTransactionId();
10 | return this.get(endpoint);
11 | }
12 |
13 | endpoint() {
14 | return this.constructEndpoint('class3');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/demo-common/models/app-config.model.ts:
--------------------------------------------------------------------------------
1 | export interface IAppConfig {
2 | env: {
3 | name: string;
4 | };
5 | appInsights: {
6 | instrumentationKey: string;
7 | };
8 | logging: {
9 | console: boolean;
10 | appInsights: boolean;
11 | traceEnabled: boolean;
12 | };
13 | aad: {
14 | requireAuth: boolean;
15 | tenant: string;
16 | resource: string;
17 | clientId: string;
18 | endpoints: { [key: string]: string };
19 | };
20 | apiServer: {
21 | metadata: string;
22 | rules: string;
23 | transaction: string;
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/shared/directives/dialog/dialog.component.css:
--------------------------------------------------------------------------------
1 | .ui-dialog {
2 | border-radius: 0 !important;
3 | position: fixed;
4 |
5 | }
6 | .ui-dialog-title {
7 | font-weight: normal, !important;
8 | }
9 | .ui-dialog-titlebar-close .fa,
10 | .ui-dialog-titlebar-close .glyphicon {
11 | color: inherit !important;
12 | font-size: 1.5em;
13 | line-height: 1.5em;
14 | }
15 | .ui-dialog-titlebar-close .fa-close:before,
16 | .ui-dialog-titlebar-close .glyphicon-remove:before {
17 | content: 'X' !important;
18 | font-family: Helvetica, Arial, Tahoma, sans-serif;
19 | }
20 | .ui-dialog-buttonpane {
21 | text-align: left !important;
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/shared/directives/menu/menu.component.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/transaction-confirm-dialog/transaction-confirm-dialog.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TransactionConfirmDialogComponent } from './transaction-confirm-dialog.component';
2 | import { TestInjector } from '../../testing/testing-helpers';
3 |
4 | describe('TransactionConfirmDialogComponent', () => {
5 | let component: TransactionConfirmDialogComponent;
6 | beforeAll(() => {
7 | TestInjector.setInjector();
8 | });
9 |
10 | beforeEach(() => {
11 | component = new TransactionConfirmDialogComponent();
12 | });
13 |
14 | it('should create', () => {
15 | expect(component).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/directives/header-menu/header-menu.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/home/log-out/log-out.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { AuthService } from '../../framework/services/auth.service';
3 | import { ActivatedRoute } from '@angular/router';
4 |
5 | @Component({
6 | selector: 'la-log-out',
7 | templateUrl: './log-out.component.html',
8 | styleUrls: ['./log-out.component.scss']
9 | })
10 | export class LogOutComponent implements OnInit {
11 |
12 | constructor(private authService: AuthService,
13 | protected route: ActivatedRoute
14 | ) {}
15 |
16 | ngOnInit() {
17 | this.logout();
18 | }
19 |
20 | logout() {
21 | this.authService.logout();
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/scss/colors.scss:
--------------------------------------------------------------------------------
1 | $main-blue: #004d72;
2 | $main-green: #46850f;
3 | $main-gray: #999;
4 |
5 | $nav-blue: #005B80;
6 | $nav-active: #BED5E6;
7 | $nav-hover: #0093CE;
8 |
9 | $background: #efefef;
10 | $canvas: #fff;
11 |
12 | $footer: #cecece;
13 |
14 | $link-blue: #007bc2;
15 |
16 | $gray-dark: #c8c6c6;
17 | $gray-med-dark: #e9e9e9;
18 | $gray-med: #e5e5e5;
19 | $gray-light: #f5f5f5;
20 | $gray-blue: #F1F4F7;
21 |
22 | $text-black: #000;
23 | $text-dark: #333;
24 | $text-med: #666;
25 | $text-light: #999;
26 |
27 | $button-gradiant-gray: #ececec;
28 | $datatable-border: #c7c7c7;
29 |
30 | $prime-blue: #0275d8;
31 | $active-blue: #337ab7;
32 | $hover-gray: #f4f3f4;
--------------------------------------------------------------------------------
/src/assets/config/config.deploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "name": "#{envName}"
4 | },
5 | "appInsights": {
6 | "instrumentationKey": "#{appInsightsKey}"
7 | },
8 | "logging": {
9 | "console": true,
10 | "appInsights": true,
11 | "traceEnabled": false
12 | },
13 | "aad": {
14 | "requireAuth": true,
15 | "tenant": "#{aadTenant}",
16 | "clientId": "#{aadClientId}",
17 | "endpoints": {
18 | "api": "#{aadClientId}"
19 | }
20 | },
21 | "apiServer": {
22 | "metadata": "#{medtadataApi}",
23 | "rules": "#{rulesApi}",
24 | "transaction": "#{transactionApi}"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/shared/directives/accordion/accordion.component.css:
--------------------------------------------------------------------------------
1 | .ui-panel-titlebar.ui-widget-header.ui-helper-clearfix.ui-corner-all {
2 | background-color: white !important;
3 | }
4 | .ui-panel {
5 | margin-bottom: 15px;
6 | margin-top: 15px;
7 | }
8 | .ui-panel .ui-panel-titlebar .h3 {
9 | color: #000;
10 | text-transform: capitalize;
11 | font-family: Tahoma, Helvetica, Arial, sans-serif !important;
12 | font-size: 14px;
13 | font-weight: bold;
14 | }
15 | .ui-panel .ui-panel-titlebar {
16 | position: relative;
17 | font-size: 1.25em;
18 | }
19 | .edit {
20 | position: absolute;
21 | top: 10px;
22 | right: 60px;
23 | font-weight: bold;
24 | color: #333;
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/shared/directives/error-message/error-message.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ErrorMessageComponent } from './error-message.component';
2 | import { TestInjector } from '../../../demo-common/testing/testing-helpers';
3 |
4 | describe('ErrorMessageComponent', () => {
5 | let component: ErrorMessageComponent;
6 | beforeAll(() => {
7 | TestInjector.setInjector();
8 | });
9 |
10 | beforeEach(() => {
11 | component = new ErrorMessageComponent();
12 | });
13 | it('should be created', () => {
14 | expect(component).toBeTruthy();
15 | });
16 |
17 | it('can set errorList', () => {
18 | component.errorList = ['test'];
19 | expect(component.errorList.length).toBe(1);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/assets/config/config.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "name": "LOCAL"
4 | },
5 | "appInsights": {
6 | "instrumentationKey": ""
7 | },
8 | "logging": {
9 | "console": true,
10 | "appInsights": false,
11 | "traceEnabled": false
12 | },
13 | "aad": {
14 | "requireAuth": false,
15 | "tenant": "microsoft.onmicrosoft.com",
16 | "clientId": "",
17 | "endpoints": {
18 | "api": ""
19 | }
20 | },
21 | "apiServer": {
22 | "metadata": "http://localhost:56787/api/",
23 | "rules": "http://localhost:54084/api/",
24 | "transaction": "http://localhost:60101/api/"
25 | }
26 | }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 | import { enableProdMode } from '@angular/core';
3 | import { environment } from 'environments/environment';
4 | import { AppModule } from 'app/app.module';
5 | import { AppInjector } from 'app/app-injector.service';
6 |
7 | if (environment.name !== 'dev') {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule).then((moduleRef) => {
12 | AppInjector.getInstance().setInjector(moduleRef.injector);
13 | });
14 |
15 | // git remote set-url --add --push origin https://laurieatkinson.visualstudio.com/NgPatternsDemo/_git/NgPatternsDemo
16 | // git remote set-url --add --push origin https://github.com/laurieatkinson/ng-patterns-demo.git
17 |
--------------------------------------------------------------------------------
/src/app/framework/services/web-storage.service.ts:
--------------------------------------------------------------------------------
1 | export class WebStorageService {
2 |
3 | static setLocalStorage(key: string, data: string): void {
4 | localStorage.setItem(key, data);
5 | }
6 | static getLocalStorage(key: string): string | null {
7 | return localStorage.getItem(key);
8 | }
9 | static removeLocalStorage(key: string) {
10 | localStorage.removeItem(key);
11 | }
12 | static setSessionStorage(key: string, data: string): void {
13 | sessionStorage.setItem(key, data);
14 | }
15 | static getSessionStorage(key: string): string | null {
16 | return sessionStorage.getItem(key);
17 | }
18 | static removeSessionStorage(key: string) {
19 | sessionStorage.removeItem(key);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/framework/services/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { AdalService } from 'adal-angular4';
3 |
4 | @Injectable({
5 | providedIn: 'root'
6 | })
7 | export class AuthService {
8 | _isUserAuthenticated: boolean;
9 | _userName: string;
10 |
11 | constructor(private adalService: AdalService) {
12 | }
13 |
14 | get isUserAuthenticated() {
15 | return this.adalService.userInfo ? this.adalService.userInfo.authenticated : false;
16 | }
17 |
18 | get userName() {
19 | return this.adalService.userInfo ? this.adalService.userInfo.userName : '';
20 | }
21 |
22 | login() {
23 | this.adalService.login();
24 | }
25 |
26 | logout(): void {
27 | this.adalService.logOut();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /dist-server
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | /typings
37 |
38 | # e2e
39 | /e2e/*.js
40 | /e2e/*.map
41 |
42 | # System Files
43 | .DS_Store
44 | Thumbs.db
45 | TESTS-Chrome*.xml
46 | TESTS-HeadlessChrome*.xml
47 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | @import '../scss/colors';
2 | @import '../scss/mixins';
3 |
4 | .overlay.loading {
5 | position:fixed;
6 | z-index: 999;
7 | top:0;
8 | left:0;
9 | width: 100%;
10 | height: 100%;
11 | opacity: .7;
12 | background-color: $text-med;
13 | }
14 | .hide-overflow {
15 | width: 100%;
16 | overflow-x: hidden;
17 | min-height: 93vh;
18 | }
19 | .container-fluid {
20 | margin-top: 80px;
21 | padding-top: 20px;
22 | margin-bottom: 30px;
23 | }
24 | .loadingGif {
25 | position: fixed;
26 | z-index: 1000;
27 | overflow: show;
28 | margin: auto;
29 | top: 0;
30 | left: 0;
31 | bottom: 0;
32 | right: 0;
33 | height: 4em;
34 | width: 4em;
35 | }
36 | .timeoutWarning {
37 | padding-top: 50px;
38 | }
39 |
--------------------------------------------------------------------------------
/src/assets/config/config.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "name": "DEV"
4 | },
5 | "appInsights": {
6 | "instrumentationKey": ""
7 | },
8 | "logging": {
9 | "console": true,
10 | "appInsights": false,
11 | "traceEnabled": false
12 | },
13 | "aad": {
14 | "requireAuth": false,
15 | "tenant": "microsoft.onmicrosoft.com",
16 | "clientId": "",
17 | "endpoints": {
18 | "api": ""
19 | }
20 | },
21 | "apiServer": {
22 | "metadata": "https://ng-demo-metadata.azurewebsites.net/api/",
23 | "rules": "https://ng-demo-rules.azurewebsites.net/api/",
24 | "transaction": "https://ng-demo-transaction.azurewebsites.net/api/"
25 | }
26 | }
--------------------------------------------------------------------------------
/src/assets/config/config.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "name": "PROD"
4 | },
5 | "appInsights": {
6 | "instrumentationKey": ""
7 | },
8 | "logging": {
9 | "console": true,
10 | "appInsights": true,
11 | "traceEnabled": false
12 | },
13 | "aad": {
14 | "requireAuth": true,
15 | "tenant": "microsoft.onmicrosoft.com",
16 | "clientId": "",
17 | "endpoints": {
18 | "api": ""
19 | }
20 | },
21 | "apiServer": {
22 | "metadata": "https://ng-demo-metadata.azurewebsites.net/api/",
23 | "rules": "https://ng-demo-rules.azurewebsites.net/api/",
24 | "transaction": "https://ng-demo-transaction.azurewebsites.net/api/"
25 | }
26 | }
--------------------------------------------------------------------------------
/src/app/shared/directives/confirm-dialog/confirm-dialog.component.html:
--------------------------------------------------------------------------------
1 |
3 | {{message}}
4 |
5 |
6 |
7 |
10 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/shared/directives/dialog/dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'la-dialog',
5 | templateUrl: './dialog.component.html',
6 | styleUrls: ['./dialog.component.css'],
7 | encapsulation: ViewEncapsulation.None
8 | })
9 | export class DialogComponent {
10 | @Input() visible = false;
11 | @Input() title = 'Please confirm.';
12 | @Input() width: any;
13 | @Input() height: any;
14 | @Output() onHide: EventEmitter = new EventEmitter();
15 | @Output() onShow: EventEmitter = new EventEmitter();
16 |
17 | show() {
18 | this.visible = true;
19 | }
20 |
21 | showDialog() {
22 | this.onShow.emit();
23 | }
24 |
25 | hide() {
26 | this.onHide.emit();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/framework/services/auth-interceptor.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler } from '@angular/common/http';
3 | import { AdalService, AdalInterceptor } from 'adal-angular4';
4 | import { AppConfig } from '../../app.config';
5 |
6 | @Injectable({
7 | providedIn: 'root'
8 | })
9 | export class AuthInterceptorService extends AdalInterceptor {
10 | _isUserAuthenticated: boolean;
11 | _userName: string;
12 |
13 | constructor(adalService: AdalService) {
14 | super(adalService)
15 | }
16 |
17 | intercept(request: HttpRequest, next: HttpHandler) {
18 | if (request.url.startsWith('assets/config') || !AppConfig.settings.aad.requireAuth) {
19 | return next.handle(request);
20 | }
21 | return super.intercept(request, next);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/shared/directives/hide-if-unauthorized/hide-if-unauthorized.directive.ts:
--------------------------------------------------------------------------------
1 | import { OnInit } from '@angular/core';
2 | import { Directive, ElementRef, Input } from '@angular/core';
3 | import { ActionCode } from '../../../framework/models/authorization.types';
4 | import { AuthorizationService } from '../../../framework/services/authorization.service';
5 |
6 | @Directive({
7 | selector: '[laHideIfUnauthorized]'
8 | })
9 | export class HideIfUnauthorizedDirective implements OnInit {
10 | @Input('laHideIfUnauthorized') permission: ActionCode;
11 |
12 | constructor(private el: ElementRef, private authorizationService: AuthorizationService) {
13 | }
14 |
15 | ngOnInit() {
16 | if (!this.authorizationService.hasPermission(this.permission)) {
17 | this.el.nativeElement.style.display = 'none';
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/home/page-error/page-error.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestInjector } from '../../demo-common/testing/testing-helpers';
2 | import { ActivatedRoute } from '@angular/router';
3 | import { async } from '@angular/core/testing';
4 | import { PageErrorComponent } from './page-error.component';
5 | import { of } from 'rxjs';
6 |
7 | class MockActivatedRoute extends ActivatedRoute {
8 | queryParams = of({
9 | errorMessage: 'Test message'
10 | });
11 | }
12 |
13 | describe('PageErrorComponent', () => {
14 | let component: PageErrorComponent;
15 | beforeAll(() => {
16 | TestInjector.setInjector();
17 | });
18 |
19 | beforeEach(() => {
20 | component = new PageErrorComponent(new MockActivatedRoute());
21 | });
22 |
23 | it('should be created', async(() => {
24 | component.ngOnInit();
25 | expect(component).toBeTruthy();
26 | }));
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/shared/directives/disable-if-unauthorized/disable-if-unauthorized.directive.ts:
--------------------------------------------------------------------------------
1 | import { OnInit } from '@angular/core';
2 | import { Directive, ElementRef, Input } from '@angular/core';
3 | import { ActionCode } from '../../../framework/models/authorization.types';
4 | import { AuthorizationService } from '../../../framework/services/authorization.service';
5 |
6 | @Directive({
7 | selector: '[laDisableIfUnauthorized]'
8 | })
9 | export class DisableIfUnauthorizedDirective implements OnInit {
10 | @Input('laDisableIfUnauthorized') permission: ActionCode;
11 |
12 | constructor(private el: ElementRef, private authorizationService: AuthorizationService) {
13 | }
14 |
15 | ngOnInit() {
16 | if (!this.authorizationService.hasPermission(this.permission)) {
17 | this.el.nativeElement.disabled = true;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/demo/search/search-results/search-results-resolver.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
3 | import { IAccountSearchResult } from '../../../demo-common/models/account-search-result.models';
4 | import { DemoResolver } from '../../../demo-common/services/demo-resolver.service';
5 | import { SearchService } from '../../../demo-common/services/search.service';
6 |
7 | @Injectable()
8 | export class SearchResultsResolver extends DemoResolver
9 | implements Resolve> {
10 |
11 | constructor(private searchService: SearchService) {
12 | super();
13 | }
14 |
15 | resolve(route: ActivatedRouteSnapshot) {
16 | super.resolve(route);
17 | return this.searchService.getAccounts();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/models/child1-entity.models.ts:
--------------------------------------------------------------------------------
1 | import { IPartsListChoice } from '../../../../framework/models/form-controls.models';
2 | import { IEntity } from '../../../../demo-common/models/transaction.models';
3 |
4 | export interface IChild1Entity extends IEntity {
5 | accountCode: string;
6 | name1: string;
7 | name2: string;
8 | name3: string;
9 | addressLine1: string;
10 | addressLine2: string;
11 | addressLine3: string;
12 | city: string;
13 | zip: string;
14 | state: string;
15 | product: string;
16 | accountType: string;
17 | accountStatus: string;
18 | startDate: Date;
19 | lastModifiedDate: Date;
20 | statusCodeList: Array;
21 | productCodeList: Array;
22 | stateCodeList: Array;
23 | accountTypeList: Array;
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/home/log-out/log-out.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async } from '@angular/core/testing';
2 | import { TestInjector } from '../../demo-common/testing/testing-helpers';
3 | import { LogOutComponent } from './log-out.component';
4 | import { AuthService } from '../../framework/services/auth.service';
5 |
6 | describe('LogOutComponent', () => {
7 | let component: LogOutComponent;
8 | beforeAll(() => {
9 | TestInjector.setInjector();
10 | });
11 |
12 | beforeEach(() => {
13 | component = new LogOutComponent(
14 | TestInjector.getService(AuthService),
15 | null);
16 | });
17 |
18 | it('should be created', async(() => {
19 | expect(component).toBeTruthy();
20 | }));
21 |
22 | it('should log out upon creation', () => {
23 | spyOn(component, 'logout');
24 | component.ngOnInit();
25 | expect(component.logout).toHaveBeenCalled();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/framework/errorhandling/error-handler.service.ts:
--------------------------------------------------------------------------------
1 | import { ErrorHandler, Injectable } from '@angular/core';
2 | import { LoggingService } from '../logging/logging.service';
3 |
4 | @Injectable()
5 | export class ErrorHandlerService extends ErrorHandler {
6 |
7 | constructor(private loggingService: LoggingService) {
8 | super();
9 | }
10 |
11 | handleError(error: Error) {
12 | this.loggingService.logException(error); // Manually log exception
13 | const originalError = this.getOriginalError(error);
14 | if (originalError !== error) {
15 | this.loggingService.logException(originalError); // Manually log original exception
16 | }
17 | }
18 |
19 | private getOriginalError(error: any) {
20 | while (error && error.originalError) {
21 | error = error.originalError;
22 | }
23 | return (error);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/header-bar/header-bar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { Router } from '@angular/router';
2 | import { AuthService } from '../../../framework/services/auth.service';
3 | import { HeaderBarComponent } from './header-bar.component';
4 | import { TestInjector } from '../../testing/testing-helpers';
5 | import { MenuComponent } from '../../../shared/directives/menu/menu.component';
6 |
7 | describe('HeaderBarComponent', () => {
8 | let component: HeaderBarComponent;
9 | beforeAll(() => {
10 | TestInjector.setInjector();
11 | });
12 |
13 | beforeEach(() => {
14 | component = new HeaderBarComponent(
15 | TestInjector.getService(AuthService),
16 | TestInjector.getService(Router));
17 | component.userMenu = new MenuComponent(null);
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/component-header/component-header.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | /*global jasmine */
5 | const { SpecReporter } = require('jasmine-spec-reporter');
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | './e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | beforeLaunch: function() {
24 | require('ts-node').register({
25 | project: 'e2e/tsconfig.e2e.json'
26 | });
27 | },
28 | onPrepare: function() {
29 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/header-bar/header-bar.component.html:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/transaction-confirm-dialog/transaction-confirm-dialog.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{message}}
3 |
4 |
5 |
6 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/home/page-error/page-error.component.ts:
--------------------------------------------------------------------------------
1 | import { ActivatedRoute } from '@angular/router';
2 | import { Component, OnInit } from '@angular/core';
3 |
4 | @Component({
5 | selector: 'la-page-error',
6 | templateUrl: './page-error.component.html',
7 | styleUrls: ['./page-error.component.scss']
8 | })
9 | export class PageErrorComponent implements OnInit {
10 |
11 | errorMessage = 'Error occurred';
12 |
13 | constructor(private route: ActivatedRoute) { }
14 |
15 | ngOnInit() {
16 | this.route.queryParams.subscribe((params) => {
17 | let errorMessage = params['errorMessage'];
18 | if (!errorMessage) {
19 | errorMessage = params['errormessage'];
20 | }
21 | if (errorMessage && (errorMessage).toLowerCase().indexOf('auth') !== -1) {
22 | this.errorMessage = 'Azure AD token has expired. Please logout and login again.';
23 | }
24 | });
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/framework/services/system-message.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { SystemMessageDataService } from './system-message-data.service';
3 |
4 | @Injectable()
5 | export class SystemMessageService {
6 | private messages: Array<{ id: number, message: string }> = [];
7 |
8 | constructor(private systemMessageDataService: SystemMessageDataService) {
9 | }
10 |
11 | getMessage(id: number, errorParameters?: Array) {
12 | let message = `Invalid (Error: ${id})`;
13 | const match = this.messages.find(item => item.id === id);
14 | if (match) {
15 | message = match.message;
16 | if (errorParameters) {
17 | errorParameters.forEach((param, index) => {
18 | const token = `{${index.toString()}}`;
19 | message = message.replace(token, param);
20 | });
21 | }
22 | }
23 | return message;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { AuthGuardService } from './framework/services/auth-guard.service';
4 | import { HomeComponent } from './home/home.component';
5 | import { PageNotFoundComponent } from './home/page-not-found/page-not-found.component';
6 | import { PageErrorComponent } from './home/page-error/page-error.component';
7 | import { LogOutComponent } from './home/log-out/log-out.component';
8 |
9 | const routes: Routes = [
10 | { path: 'home', component: HomeComponent, canActivate: [AuthGuardService] },
11 | { path: 'error', component: PageErrorComponent },
12 | { path: 'logout', component: LogOutComponent },
13 | { path: '', redirectTo: '/home', pathMatch: 'full' },
14 | { path: '**', component: PageNotFoundComponent }
15 | ];
16 |
17 | @NgModule({
18 | imports: [RouterModule.forRoot(routes)],
19 | exports: [RouterModule]
20 | })
21 | export class AppRoutingModule {}
22 |
--------------------------------------------------------------------------------
/src/app/shared/directives/calendar/calendar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { fakeAsync } from '@angular/core/testing';
2 | import { CalendarModule } from 'primeng/components/calendar/calendar';
3 | import { CalendarComponent } from './calendar.component';
4 | import { TestInjector } from '../../../demo-common/testing/testing-helpers';
5 |
6 | describe('CalendarComponent', () => {
7 | let component: CalendarComponent;
8 | beforeAll(() => {
9 | TestInjector.setInjector();
10 | });
11 |
12 | beforeEach(() => {
13 | component = new CalendarComponent();
14 | });
15 |
16 | it('should create', () => {
17 | expect(component).toBeTruthy();
18 | });
19 |
20 | it('should emit the selected date', fakeAsync((): void => {
21 | const testDate = new Date('04/22/1989');
22 | spyOn(component.dateSelected, 'emit');
23 | component.selectedDate = testDate;
24 | component.onSelected();
25 | expect(component.dateSelected.emit).toHaveBeenCalledWith(testDate);
26 | }));
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/shared/directives/data-table/data-table-models.ts:
--------------------------------------------------------------------------------
1 | export interface IDataTableColumn {
2 | name: string;
3 | header?: string;
4 | showTotal?: boolean;
5 | sortable?: boolean;
6 | editable?: boolean;
7 | filter?: boolean;
8 | mask?: string;
9 | numberFormat?: string;
10 | dataType?: 'string' | 'number' | 'boolean' | 'date' | 'choice' | 'url' | 'mask';
11 | options?: Array<{ label: string, value: any }>;
12 | filterOptions?: Array<{ label: string, value: any }>;
13 | link?: string;
14 | linkText?: string;
15 | width?: string;
16 | hidden?: boolean;
17 | multisortOrder?: number;
18 | sortDesc?: boolean;
19 | includeEmptyChoice?: boolean;
20 | emptyChoiceLabel?: string;
21 | excludeFromGlobalFilter?: boolean;
22 | }
23 |
24 | export interface IDataTableRowExpansionField {
25 | label: string;
26 | fieldName: string;
27 | dataType?: 'string' | 'number' | 'boolean' | 'date' | 'choice' ;
28 | options?: Array<{ label: string, value: any }>;
29 | editable?: boolean;
30 | }
31 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare var __karma__: any;
17 | declare var require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | getTestBed().initTestEnvironment(
23 | BrowserDynamicTestingModule,
24 | platformBrowserDynamicTesting()
25 | );
26 | // Then we find all the tests.
27 | const context = require.context('./', true, /\.spec\.ts$/);
28 | // And load the modules.
29 | context.keys().map(context);
30 | // Finally, start Karma to run the tests.
31 | __karma__.start();
32 |
--------------------------------------------------------------------------------
/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { IAppConfig } from './demo-common/models/app-config.model';
3 | import { environment } from 'environments/environment';
4 | import { HttpClient } from '@angular/common/http';
5 | import { IAppMonitor } from './shared/models/app-monitor';
6 |
7 | @Injectable()
8 | export class AppConfig {
9 |
10 | static settings: IAppConfig;
11 | static appMonitor: IAppMonitor;
12 |
13 | constructor(private http: HttpClient) {
14 | }
15 |
16 | load() {
17 | const cacheBusterParam = (new Date()).getTime();
18 | const jsonFile = `assets/config/config.${environment.name}.json?nocache=${cacheBusterParam}`;
19 | return new Promise((resolve, reject) => {
20 | this.http.get(jsonFile).toPromise()
21 | .then((response: IAppConfig) => {
22 | AppConfig.settings = response;
23 | resolve();
24 | }).catch((response: any) => {
25 | reject(`Could not load file '${jsonFile}': ${JSON.stringify(response)}`);
26 | });
27 | });
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/shared/directives/calendar/calendar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
2 |
3 | // This calendar component is for a standalone value
4 | // If the date is a field on a form, use the form-field control instead
5 | @Component({
6 | selector: 'la-calendar',
7 | templateUrl: './calendar.component.html',
8 | styleUrls: ['./calendar.component.css'],
9 | encapsulation: ViewEncapsulation.None
10 | })
11 | export class CalendarComponent implements OnInit {
12 | yearRange: string;
13 | @Input() selectedDate: Date;
14 | @Input() placeholder: string;
15 | @Input() minDate: Date;
16 | @Input() maxDate: Date;
17 | @Input() showCalendarOnFocus = false;
18 | @Input() hideMonthNavigator = false;
19 | @Input() hideYearNavigator = false;
20 | @Input() disabled = false;
21 | @Output() dateSelected: EventEmitter = new EventEmitter();
22 |
23 | ngOnInit() {
24 | this.yearRange = '1900:' + ((new Date()).getFullYear() + 10).toString();
25 | }
26 |
27 | onSelected() {
28 | this.dateSelected.emit(this.selectedDate);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/demo-common/services/user-session.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, inject } from '@angular/core/testing';
2 | import { UserSessionService } from './user-session.service';
3 | import { UtilitiesService } from '../../framework/services/utilities.service';
4 |
5 | describe('UserSessionService', () => {
6 |
7 | let service: UserSessionService;
8 |
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({
11 | providers: [
12 | UserSessionService,
13 | UtilitiesService
14 | ]
15 | });
16 | });
17 |
18 | beforeEach(inject([UserSessionService], (s: UserSessionService) => {
19 | service = s;
20 | }));
21 |
22 | it('should create the service', () => {
23 | expect(service).toBeTruthy();
24 | });
25 |
26 | it('can get and set TransactionIdentifier', () => {
27 | service.transactionIdentifier = {
28 | id: 12345
29 | };
30 | expect(service.transactionIdentifier.id).toBe(12345);
31 | });
32 |
33 | it('can get and set account code', () => {
34 | service.accountCode = 'ABC123';
35 | expect(service.accountCode).toBe('abc123');
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/app/shared/directives/confirm-dialog/confirm-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ViewEncapsulation, Output, EventEmitter } from '@angular/core';
2 | import { ConfirmChoice } from '../../models/confirm-choices.enum';
3 |
4 | @Component({
5 | selector: 'la-confirm-dialog',
6 | templateUrl: './confirm-dialog.component.html',
7 | styleUrls: ['./confirm-dialog.component.css'],
8 | encapsulation: ViewEncapsulation.None
9 | })
10 | export class ConfirmDialogComponent {
11 | display = false;
12 | @Input() title = 'Please confirm.';
13 | @Input() message = 'Do you want to save or discard your changes?';
14 | @Input() okButtonText = 'Save';
15 | @Input() cancelButtonText = 'Cancel';
16 | @Input() allowSave = true;
17 | @Output() confirmed: EventEmitter = new EventEmitter();
18 |
19 | show() {
20 | this.display = true;
21 | }
22 | save() {
23 | this.display = false;
24 | this.confirmed.emit(ConfirmChoice.save);
25 | }
26 | cancel() {
27 | if (this.display) {
28 | this.display = false;
29 | this.confirmed.emit(ConfirmChoice.cancel);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/framework/services/can-deactivate-guard.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | export interface ICanComponentDeactivate {
6 | canDeactivate: (component: ICanComponentDeactivate,
7 | route: ActivatedRouteSnapshot,
8 | state: RouterStateSnapshot,
9 | nextState?: RouterStateSnapshot) => boolean | Observable | Promise;
10 | }
11 |
12 | // If a component should be checked before navigating away, then this CanDeactivateGuardService
13 | // can be used in the route definition for that component
14 | @Injectable()
15 | export class CanDeactivateGuardService implements CanDeactivate {
16 | canDeactivate(component: ICanComponentDeactivate,
17 | route: ActivatedRouteSnapshot,
18 | state: RouterStateSnapshot,
19 | nextState?: RouterStateSnapshot
20 | ): Observable | Promise | boolean {
21 | if (!component || !component.canDeactivate) {
22 | return true;
23 | }
24 | return component.canDeactivate(component, route, state, nextState);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/directives/account-header/account-header.component.html:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
--------------------------------------------------------------------------------
/src/app/home/home.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../scss/colors';
2 | @import '../../scss/mixins';
3 |
4 | .tile-container {
5 | flex-direction: column;
6 | }
7 | .tile {
8 | width: 100%;
9 | flex-direction: row;
10 | padding-top: 20px;
11 | }
12 |
13 | /* Small devices (tablets, 768px and up) */
14 | @media (min-width: 768px) {
15 | .overflow {
16 | overflow: hidden;
17 | margin: 15px 0;
18 | img {
19 | height: auto;
20 | width: 100%;
21 | }
22 | }
23 | }
24 |
25 | @media (min-width: 992px) {
26 | .overflow {
27 | overflow: hidden;
28 | margin: 15px 0;
29 | img {
30 | height: 100%;
31 | width: auto;
32 | }
33 | }
34 | }
35 |
36 | .header {
37 | @include font-h();
38 | margin-left: 8px;
39 | }
40 |
41 | a {
42 | &:hover {
43 | text-decoration: none;
44 | }
45 | }
46 | .well {
47 | background: #e9e9e9, !important;
48 | border-radius: 0 !important;
49 | position: relative;
50 | float: left;
51 | width: 100%;
52 | margin: 15px 0;
53 | border: 1px solid #e5e5e5;
54 | }
55 | h6, .h6{
56 | font-family: Tahoma, Helvetica, Arial, sans-serif !important;
57 | font-size: 15px;
58 | font-weight: bold;
59 | }
--------------------------------------------------------------------------------
/src/app/shared/pipes/date-to-string.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { DateToStringPipe } from './date-to-string.pipe';
2 |
3 | describe('DateToStringPipe', () => {
4 | it('can format a date to shortDate', () => {
5 | const testValue = new Date(2001, 0, 2);
6 | const pipe = new DateToStringPipe();
7 | const result = pipe.transform(testValue, 'shortDate');
8 |
9 | expect(result).toEqual('01/02/01');
10 | });
11 |
12 | it('can format a date to as MM/DD/YYYY', () => {
13 | const testValue = new Date(2001, 0, 2);
14 | const pipe = new DateToStringPipe();
15 | const result = pipe.transform(testValue, 'MM/DD/YYYY');
16 |
17 | expect(result).toEqual('01/02/2001');
18 | });
19 |
20 |
21 | it('can return empty on invalid date', () => {
22 | const testValue = 'pancakes';
23 | const pipe = new DateToStringPipe();
24 | const result = pipe.transform(testValue, 'shortDate');
25 |
26 | expect(result).toEqual('');
27 | });
28 |
29 | it('can format MM-DD', () => {
30 | const testValue = new Date(2001, 0, 2);
31 | const pipe = new DateToStringPipe();
32 | const result = pipe.transform(testValue, 'MM-DD');
33 |
34 | expect(result).toEqual('01-02');
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/src/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/app/framework/services/global-events.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Subject } from 'rxjs';
3 |
4 | @Injectable()
5 | export class GlobalEventsService {
6 | private routingStartedSource = new Subject();
7 | routingStarted = this.routingStartedSource.asObservable();
8 | private routingCompleteSource = new Subject();
9 | routingComplete = this.routingCompleteSource.asObservable();
10 | private loadingDataStartedSource = new Subject();
11 | loadingDataStarted = this.loadingDataStartedSource.asObservable();
12 | private loadingDataCompleteSource = new Subject();
13 | loadingDataComplete = this.loadingDataCompleteSource.asObservable();
14 | private _isBusyRouting = false;
15 |
16 | get isBusyRouting() {
17 | return this._isBusyRouting;
18 | }
19 |
20 | startRouting() {
21 | this._isBusyRouting = true;
22 | this.routingStartedSource.next();
23 | }
24 |
25 | completeRouting() {
26 | this._isBusyRouting = false;
27 | this.routingCompleteSource.next();
28 | }
29 |
30 | startLoadingData() {
31 | this.loadingDataStartedSource.next();
32 | }
33 |
34 | completeLoadingData() {
35 | this.loadingDataCompleteSource.next();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/demo/accounts/shared/services/account-data.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Subject } from 'rxjs';
3 | import { TransactionEntityDataService } from '../../../../demo-common/services/transaction-entity-data.service';
4 | import { IAccount } from '../models/account.models';
5 |
6 | @Injectable()
7 | export class AccountDataService extends TransactionEntityDataService {
8 |
9 | private _currentAccount: IAccount;
10 | private accountChangedSource = new Subject();
11 | accountChanged = this.accountChangedSource.asObservable();
12 |
13 | get currentAccount() {
14 | return this._currentAccount;
15 | }
16 |
17 | userSelectedAnotherAccount() {
18 | return this.currentAccount.accountCode !== this.userSessionService.accountCode;
19 | }
20 |
21 | userUpdatedAccountName(name) {
22 | this.currentAccount.accountName = name;
23 | }
24 |
25 | getAccount(): Promise {
26 | const endpoint = this.endpoint();
27 | return this.get(endpoint).then((account: IAccount) => {
28 | this._currentAccount = account;
29 | this.accountChangedSource.next();
30 | return account;
31 | });
32 | }
33 |
34 | endpoint() {
35 | return this.constructEndpoint('');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/framework/services/system-message.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, inject } from '@angular/core/testing';
2 | import { SystemMessageDataService } from './system-message-data.service';
3 | import { SystemMessageService } from './system-message.service';
4 |
5 | const testErrorMessage = 'Test error message';
6 | const testErrorMessage2 = '{0} error {1}';
7 |
8 | class MockSystemMessageDataService {
9 | getMessage(id: number, errorParameters?: Array): Promise {
10 | return new Promise((resolve) => {
11 | if (id === 2) {
12 | resolve(testErrorMessage2);
13 | } else {
14 | resolve(testErrorMessage);
15 | }
16 | });
17 | }
18 | }
19 |
20 | describe('SystemMessageService', () => {
21 | beforeEach(() => {
22 |
23 | TestBed.configureTestingModule({
24 | providers: [
25 | SystemMessageService,
26 | {
27 | provide: SystemMessageDataService,
28 | useClass: MockSystemMessageDataService
29 | }
30 | ]
31 | });
32 | });
33 |
34 | it('should create the service',
35 | inject([SystemMessageService], async(service: SystemMessageService) => {
36 | expect(service).toBeTruthy();
37 | }));
38 | });
39 |
--------------------------------------------------------------------------------
/src/app/framework/services/authorization.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActionCode } from '../models/authorization.types';
3 | import { AuthorizationDataService } from './authorization-data.service';
4 | import { AppConfig } from '../../app.config';
5 |
6 | @Injectable()
7 | export class AuthorizationService {
8 |
9 | permissions: Array; // The actions for which this user has permissions
10 |
11 | constructor(private authorizationDataService: AuthorizationDataService) {
12 | }
13 |
14 | hasPermission(action: ActionCode) {
15 | if (!AppConfig.settings.aad.requireAuth || !action) {
16 | return true;
17 | }
18 | if (this.permissions && this.permissions.find(permission => {
19 | return permission === action;
20 | })) {
21 | return true;
22 | }
23 | return false;
24 | }
25 |
26 | initializePermissions() {
27 | return new Promise((resolve, reject) => {
28 | this.authorizationDataService.getPermissions()
29 | .then(permissions => {
30 | this.permissions = permissions;
31 | resolve();
32 | })
33 | .catch((e) => {
34 | reject(e);
35 | });
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/transaction-confirm-dialog/transaction-confirm-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation, Input, EventEmitter, Output } from '@angular/core';
2 | import { ConfirmDialogComponent } from '../../../shared/directives/confirm-dialog/confirm-dialog.component';
3 | import { ConfirmChoice } from '../../../shared/models/confirm-choices.enum';
4 |
5 | @Component({
6 | selector: 'la-transaction-confirm-dialog',
7 | templateUrl: './transaction-confirm-dialog.component.html',
8 | styleUrls: ['./transaction-confirm-dialog.component.css'],
9 | encapsulation: ViewEncapsulation.None
10 | })
11 | export class TransactionConfirmDialogComponent extends ConfirmDialogComponent {
12 | description = '';
13 | @Input() confirm = false;
14 | @Input() allowDiscard = true;
15 | @Output() confirmed: EventEmitter = new EventEmitter();
16 |
17 | saveButtonText() {
18 | if (this.allowSave && this.confirm) {
19 | return 'Save and commit';
20 | } else if (this.allowSave) {
21 | return 'Save';
22 | } else if (this.confirm) {
23 | return 'Commit';
24 | }
25 | return '';
26 | }
27 |
28 | discard() {
29 | this.display = false;
30 | this.confirmed.emit(ConfirmChoice.discard);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/demo-common/directives/header-bar/header-bar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewChild } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { MenuItem } from 'primeng/primeng';
4 | import { AuthService } from '../../../framework/services/auth.service';
5 | import { MenuComponent } from '../../../shared/directives/menu/menu.component';
6 |
7 | @Component({
8 | selector: 'la-header-bar',
9 | templateUrl: './header-bar.component.html',
10 | styleUrls: ['./header-bar.component.scss']
11 | })
12 | export class HeaderBarComponent {
13 | showSearch = false;
14 | userMenuItems: Array