diff --git a/.editorconfig b/.editorconfig index f1cc3ad3..90bafbc1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,15 +1,196 @@ -# http://editorconfig.org - +############################### +# Core EditorConfig Options # +############################### root = true +# All files [*] -charset = utf-8 indent_style = space -indent_size = 2 -end_of_line = lf + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 insert_final_newline = true -trim_trailing_whitespace = true +charset = utf-8-bom [*.md] insert_final_newline = false trim_trailing_whitespace = false + +############################### +# .NET Coding Conventions # +############################### + +# Solution Files +[*.sln] +indent_style = tab + +# XML Project Files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Configuration Files +[*.{json,xml,yml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# Dotnet Code Style Settings +# See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +# See http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers +[*.{cs,csx,cake,vb}] +dotnet_sort_system_directives_first = true:warning +dotnet_style_coalesce_expression = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_object_initializer = true:warning +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +dotnet_style_qualification_for_event = true:warning +dotnet_style_qualification_for_field = true:warning +dotnet_style_qualification_for_method = true:warning +dotnet_style_qualification_for_property = true:warning + +# Naming Symbols +# constant_fields - Define constant fields +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +# non_private_readonly_fields - Define public, internal and protected readonly fields +dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected +dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly +# static_readonly_fields - Define static and readonly fields +dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly +# private_readonly_fields - Define private readonly fields +dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly +# public_internal_fields - Define public and internal fields +dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_internal_fields.applicable_kinds = field +# private_protected_fields - Define private and protected fields +dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected +dotnet_naming_symbols.private_protected_fields.applicable_kinds = field +# public_symbols - Define any public symbol +dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal +dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate +# parameters - Defines any parameter +dotnet_naming_symbols.parameters.applicable_kinds = parameter +# non_interface_types - Defines class, struct, enum and delegate types +dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate +# interface_types - Defines interfaces +dotnet_naming_symbols.interface_types.applicable_kinds = interface + +# Naming Styles +# camel_case - Define the camelCase style +dotnet_naming_style.camel_case.capitalization = camel_case +# pascal_case - Define the Pascal_case style +dotnet_naming_style.pascal_case.capitalization = pascal_case +# first_upper - The first character must start with an upper-case character +dotnet_naming_style.first_upper.capitalization = first_word_upper +# prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I' +dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case +dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I + +# Naming Rules +# Constant fields must be PascalCase +dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning +dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case +# Public, internal and protected readonly fields must be PascalCase +dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning +dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields +dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case +# Static readonly fields must be PascalCase +dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning +dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields +dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case +# Private readonly fields must be camelCase +dotnet_naming_rule.private_readonly_fields_must_be_camel_case.severity = warning +dotnet_naming_rule.private_readonly_fields_must_be_camel_case.symbols = private_readonly_fields +dotnet_naming_rule.private_readonly_fields_must_be_camel_case.style = camel_case +# Public and internal fields must be PascalCase +dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning +dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields +dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case +# Private and protected fields must be camelCase +dotnet_naming_rule.private_protected_fields_must_be_camel_case.severity = warning +dotnet_naming_rule.private_protected_fields_must_be_camel_case.symbols = private_protected_fields +dotnet_naming_rule.private_protected_fields_must_be_camel_case.style = camel_case +# Public members must be capitalized +dotnet_naming_rule.public_members_must_be_capitalized.severity = warning +dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols +dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper +# Parameters must be camelCase +dotnet_naming_rule.parameters_must_be_camel_case.severity = warning +dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters +dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case +# Class, struct, enum and delegates must be PascalCase +dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning +dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types +dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case +# Interfaces must be PascalCase and start with an 'I' +dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning +dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types +dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i + +# C# Code Style Settings +# See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +# See http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers +[*.{cs,csx,cake}] +# Indentation Options +csharp_indent_block_contents = true:warning +csharp_indent_braces = false:warning +csharp_indent_case_contents = true:warning +csharp_indent_labels = no_change:warning +csharp_indent_switch_labels = true:warning +# Style Options +csharp_style_conditional_delegate_call = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_constructors = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_methods = true:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_inlined_variable_declaration = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_throw_expression = true:warning +csharp_style_var_elsewhere = true:warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +# New Line Options +csharp_new_line_before_catch = true:warning +csharp_new_line_before_else = true:warning +csharp_new_line_before_finally = true:warning +csharp_new_line_before_members_in_anonymous_types = true:warning +csharp_new_line_before_members_in_object_initializers = true:warning +# BUG: Warning level cannot be set https://github.com/dotnet/roslyn/issues/18010 +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true:warning +# Spacing Options +csharp_space_after_cast = false:warning +csharp_space_after_colon_in_inheritance_clause = true:warning +csharp_space_after_comma = true:warning +csharp_space_after_dot = false:warning +csharp_space_after_keywords_in_control_flow_statements = true:warning +csharp_space_after_semicolon_in_for_statement = true:warning +csharp_space_around_binary_operators = before_and_after:warning +csharp_space_around_declaration_statements = do_not_ignore:warning +csharp_space_before_colon_in_inheritance_clause = true:warning +csharp_space_before_comma = false:warning +csharp_space_before_dot = false:warning +csharp_space_before_semicolon_in_for_statement = false:warning +csharp_space_before_open_square_brackets = false:warning +csharp_space_between_empty_square_brackets = false:warning +csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning +csharp_space_between_method_declaration_parameter_list_parentheses = false:warning +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning +csharp_space_between_method_call_name_and_opening_parenthesis = false:warning +csharp_space_between_method_call_parameter_list_parentheses = false:warning +csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning +csharp_space_between_parentheses = expressions:warning +csharp_space_between_square_brackets = false:warning +# Wrapping Options +csharp_preserve_single_line_blocks = true:warning +csharp_preserve_single_line_statements = false:warning diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 33256db7..da1cb069 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,7 +8,7 @@ "tasks": [ { "taskName": "build", - "args": [ ], + "args": ["Asp2017.sln"], "isBuildCommand": true, "showOutput": "silent", "problemMatcher": "$msCompile" diff --git a/Asp2017.csproj b/Asp2017.csproj index 7db490d3..b307acba 100644 --- a/Asp2017.csproj +++ b/Asp2017.csproj @@ -9,11 +9,11 @@ - + - - + + diff --git a/ClientApp/app/_styles.scss b/ClientApp/app/_styles.scss index 42cf15fe..87ea278f 100644 --- a/ClientApp/app/_styles.scss +++ b/ClientApp/app/_styles.scss @@ -1,22 +1,26 @@ $body-bg: #f1f1f1; $body-color: #111; -$theme-colors: ( "primary": #216DAD); -$theme-colors: ( "accent": #669ECD); +$theme-colors: ( + 'primary': #216dad +); +$theme-colors: ( + 'accent': #669ecd +); - -@import "http://github.com/~bootstrap/scss/bootstrap"; +@import 'http://github.com/~bootstrap/scss/bootstrap'; .panel { - box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2), 0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12); + box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), + 0 1px 5px 0 rgba(0, 0, 0, 0.12); height: 100%; flex: 1; - background-color: rgba(255, 255, 255, .30); - border-radius: .25rem; + background-color: rgba(255, 255, 255, 0.3); + border-radius: 0.25rem; .title { background-color: #86afd0; - color: #FFFFFF; + color: #ffffff; text-align: center; - border-top-left-radius: .25rem; - border-top-right-radius: .25rem; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } .body { display: flex; diff --git a/ClientApp/app/_variables.scss b/ClientApp/app/_variables.scss index 2d34745a..313015eb 100644 --- a/ClientApp/app/_variables.scss +++ b/ClientApp/app/_variables.scss @@ -1,6 +1,6 @@ -@import "http://github.com/styles"; -$header-height:50px; -$menu-max-width:25%; +@import 'http://github.com/styles'; +$header-height: 50px; +$menu-max-width: 25%; $navbar-default-bg: #312312; $light-orange: #ff8c00; $navbar-default-color: $light-orange; diff --git a/ClientApp/app/app.component.scss b/ClientApp/app/app.component.scss index 8705be92..1e7eea64 100644 --- a/ClientApp/app/app.component.scss +++ b/ClientApp/app/app.component.scss @@ -1,4 +1,4 @@ -@import "http://github.com/variables"; +@import 'http://github.com/variables'; /* *** Overall APP Styling can go here *** -------------------------------------------- Note: This Component has ViewEncapsulation.None so the styles will bleed out @@ -15,7 +15,7 @@ body { } h1 { - border-bottom: 3px theme-color("accent") solid; + border-bottom: 3px theme-color('accent') solid; font-size: 24px; } @@ -40,7 +40,7 @@ ul li { blockquote { margin: 25px 10px; padding: 10px 35px 10px 10px; - border-left: 10px color("green") solid; + border-left: 10px color('green') solid; background: $gray-100; } @@ -57,7 +57,7 @@ blockquote a:hover { margin-left: $menu-max-width; } h1 { - border-bottom: 5px #4189C7 solid; + border-bottom: 5px #4189c7 solid; font-size: 36px; } h2 { diff --git a/ClientApp/app/app.component.ts b/ClientApp/app/app.component.ts index 614cba3e..415c196a 100644 --- a/ClientApp/app/app.component.ts +++ b/ClientApp/app/app.component.ts @@ -1,101 +1,103 @@ - -import {mergeMap, map, filter} from 'rxjs/operators'; -import { Component, OnInit, OnDestroy, Inject, ViewEncapsulation, RendererFactory2, PLATFORM_ID, Injector } from '@angular/core'; -import { Router, NavigationEnd, ActivatedRoute, PRIMARY_OUTLET } from '@angular/router'; -import { Meta, Title, DOCUMENT, MetaDefinition } from '@angular/platform-browser'; -import { Subscription } from 'rxjs'; -import { isPlatformServer } from '@angular/common'; -import { LinkService } from './shared/link.service'; - +import { + Component, + Injector, + OnDestroy, + OnInit, + ViewEncapsulation +} from '@angular/core'; +import { Meta, Title } from '@angular/platform-browser'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { REQUEST } from '@nguniversal/aspnetcore-engine/tokens'; // i18n support import { TranslateService } from '@ngx-translate/core'; -import { REQUEST } from '@nguniversal/aspnetcore-engine/tokens'; +import { Subscription } from 'rxjs'; +import { filter, map, mergeMap } from 'rxjs/operators'; +import { LinkService } from './shared/link.service'; @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], - encapsulation: ViewEncapsulation.None + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + encapsulation: ViewEncapsulation.None }) export class AppComponent implements OnInit, OnDestroy { - - // This will go at the END of your title for example "Home - Angular Universal..." <-- after the dash (-) - private endPageTitle: string = 'Angular Universal and ASP.NET Core Starter'; - // If no Title is provided, we'll use a default one before the dash(-) - private defaultPageTitle: string = 'My App'; - - private routerSub$: Subscription; - private request; - - constructor( - private router: Router, - private activatedRoute: ActivatedRoute, - private title: Title, - private meta: Meta, - private linkService: LinkService, - public translate: TranslateService, - private injector: Injector - ) { - // this language will be used as a fallback when a translation isn't found in the current language - translate.setDefaultLang('en'); - - // the lang to use, if the lang isn't available, it will use the current loader to get them - translate.use('en'); - - this.request = this.injector.get(REQUEST); - - console.log(`What's our REQUEST Object look like?`); - console.log(`The Request object only really exists on the Server, but on the Browser we can at least see Cookies`); - console.log(this.request); + // This will go at the END of your title for example "Home - Angular Universal..." <-- after the dash (-) + private endPageTitle: string = 'Angular Universal and ASP.NET Core Starter'; + // If no Title is provided, we'll use a default one before the dash(-) + private defaultPageTitle: string = 'My App'; + + private routerSub$: Subscription; + private request; + + constructor( + private router: Router, + private activatedRoute: ActivatedRoute, + private title: Title, + private meta: Meta, + private linkService: LinkService, + public translate: TranslateService, + private injector: Injector + ) { + // this language will be used as a fallback when a translation isn't found in the current language + translate.setDefaultLang('en'); + + // the lang to use, if the lang isn't available, it will use the current loader to get them + translate.use('en'); + + this.request = this.injector.get(REQUEST); + + console.log(`What's our REQUEST Object look like?`); + console.log( + `The Request object only really exists on the Server, but on the Browser we can at least see Cookies` + ); + console.log(this.request); + } + + ngOnInit() { + // Change "Title" on every navigationEnd event + // Titles come from the data.title property on all Routes (see app.routes.ts) + this._changeTitleOnNavigation(); + } + + ngOnDestroy() { + // Subscription clean-up + this.routerSub$.unsubscribe(); + } + + private _changeTitleOnNavigation() { + this.routerSub$ = this.router.events + .pipe( + filter(event => event instanceof NavigationEnd), + map(() => this.activatedRoute), + map(route => { + while (route.firstChild) route = route.firstChild; + return route; + }), + filter(route => route.outlet === 'primary'), + mergeMap(route => route.data) + ) + .subscribe(event => { + this._setMetaAndLinks(event); + }); + } + + private _setMetaAndLinks(event) { + // Set Title if available, otherwise leave the default Title + const title = event['title'] + ? `${event['title']} - ${this.endPageTitle}` + : `${this.defaultPageTitle} - ${this.endPageTitle}`; + + this.title.setTitle(title); + + const metaData = event['meta'] || []; + const linksData = event['links'] || []; + + for (let i = 0; i < metaData.length; i++) { + this.meta.updateTag(metaData[i]); } - ngOnInit() { - // Change "Title" on every navigationEnd event - // Titles come from the data.title property on all Routes (see app.routes.ts) - this._changeTitleOnNavigation(); + for (let i = 0; i < linksData.length; i++) { + this.linkService.addTag(linksData[i]); } - - ngOnDestroy() { - // Subscription clean-up - this.routerSub$.unsubscribe(); - } - - private _changeTitleOnNavigation() { - - this.routerSub$ = this.router.events.pipe( - filter(event => event instanceof NavigationEnd), - map(() => this.activatedRoute), - map(route => { - while (route.firstChild) route = route.firstChild; - return route; - }), - filter(route => route.outlet === 'primary'), - mergeMap(route => route.data),) - .subscribe((event) => { - this._setMetaAndLinks(event); - }); - } - - private _setMetaAndLinks(event) { - - // Set Title if available, otherwise leave the default Title - const title = event['title'] - ? `${event['title']} - ${this.endPageTitle}` - : `${this.defaultPageTitle} - ${this.endPageTitle}`; - - this.title.setTitle(title); - - const metaData = event['meta'] || []; - const linksData = event['links'] || []; - - for (let i = 0; i < metaData.length; i++) { - this.meta.updateTag(metaData[i]); - } - - for (let i = 0; i < linksData.length; i++) { - this.linkService.addTag(linksData[i]); - } - } - + } } - diff --git a/ClientApp/app/app.module.browser.ts b/ClientApp/app/app.module.browser.ts index f7371e80..5162c3ca 100644 --- a/ClientApp/app/app.module.browser.ts +++ b/ClientApp/app/app.module.browser.ts @@ -1,13 +1,9 @@ import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { APP_BASE_HREF } from '@angular/common'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - import { ORIGIN_URL, REQUEST } from '@nguniversal/aspnetcore-engine/tokens'; -import { AppModuleShared } from './app.module'; -import { AppComponent } from './app.component'; -import { BrowserTransferStateModule } from '@angular/platform-browser'; import { PrebootModule } from 'preboot'; +import { AppComponent } from './app.component'; +import { AppModuleShared } from './app.module'; export function getOriginUrl() { return window.location.origin; @@ -19,26 +15,26 @@ export function getRequest() { } @NgModule({ - bootstrap: [AppComponent], - imports: [ - PrebootModule.withConfig({ appRoot: 'app-root' }), - BrowserAnimationsModule, - - // Our Common AppModule - AppModuleShared + bootstrap: [AppComponent], + imports: [ + PrebootModule.withConfig({ appRoot: 'app-root' }), + BrowserAnimationsModule, - ], - providers: [ - { - // We need this for our Http calls since they'll be using an ORIGIN_URL provided in main.server - // (Also remember the Server requires Absolute URLs) - provide: ORIGIN_URL, - useFactory: (getOriginUrl) - }, { - // The server provides these in main.server - provide: REQUEST, - useFactory: (getRequest) - } - ] + // Our Common AppModule + AppModuleShared + ], + providers: [ + { + // We need this for our Http calls since they'll be using an ORIGIN_URL provided in main.server + // (Also remember the Server requires Absolute URLs) + provide: ORIGIN_URL, + useFactory: getOriginUrl + }, + { + // The server provides these in main.server + provide: REQUEST, + useFactory: getRequest + } + ] }) -export class AppModule { } +export class AppModule {} diff --git a/ClientApp/app/app.module.server.ts b/ClientApp/app/app.module.server.ts index 642a774c..06af629a 100644 --- a/ClientApp/app/app.module.server.ts +++ b/ClientApp/app/app.module.server.ts @@ -1,13 +1,11 @@ import { NgModule } from '@angular/core'; -import { ServerModule } from '@angular/platform-server'; -import { BrowserModule } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; - -import { AppModuleShared } from './app.module'; +import { ServerModule } from '@angular/platform-server'; +import { PrebootModule } from 'preboot'; import { AppComponent } from './app.component'; -import { ServerTransferStateModule } from '@angular/platform-server'; +import { AppModuleShared } from './app.module'; -import { PrebootModule } from 'preboot'; +import { TransferHttpCacheModule, StateTransferInitializerModule } from '@nguniversal/common'; @NgModule({ bootstrap: [AppComponent], @@ -19,14 +17,12 @@ import { PrebootModule } from 'preboot'; PrebootModule.withConfig({ appRoot: 'app-root' }), NoopAnimationsModule, - // HttpTransferCacheModule still needs fixes for 5.0 + TransferHttpCacheModule, // still needs fixes for 5.0 // Leave this commented out for now, as it breaks Server-renders // Looking into fixes for this! - @MarkPieszak - // ServerTransferStateModule // <-- broken for the time-being with ASP.NET + // StateTransferInitializerModule // <-- broken for the time-being with ASP.NET ] }) export class AppModule { - - constructor() { } - + constructor() {} } diff --git a/ClientApp/app/app.module.ts b/ClientApp/app/app.module.ts index 7cc9c4c0..a30f46d8 100644 --- a/ClientApp/app/app.module.ts +++ b/ClientApp/app/app.module.ts @@ -1,156 +1,212 @@ -import { NgModule, Inject } from '@angular/core'; -import { RouterModule, PreloadAllModules } from '@angular/router'; -import { CommonModule, APP_BASE_HREF } from '@angular/common'; -import { HttpModule, Http } from '@angular/http'; -import { HttpClientModule, HttpClient } from '@angular/common/http'; +import { CommonModule } from '@angular/common'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser'; +import { + BrowserModule, + BrowserTransferStateModule +} from '@angular/platform-browser'; +import { PreloadAllModules, RouterModule } from '@angular/router'; +import { ORIGIN_URL } from '@nguniversal/aspnetcore-engine/tokens'; import { TransferHttpCacheModule } from '@nguniversal/common'; - -import { AccordionModule } from 'ngx-bootstrap'; - // i18n support -import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; - +import { AccordionModule } from 'ngx-bootstrap'; import { AppComponent } from './app.component'; import { NavMenuComponent } from './components/navmenu/navmenu.component'; -import { HomeComponent } from './containers/home/home.component'; -import { UsersComponent } from './containers/users/users.component'; import { UserDetailComponent } from './components/user-detail/user-detail.component'; import { CounterComponent } from './containers/counter/counter.component'; -import { NotFoundComponent } from './containers/not-found/not-found.component'; +import { HomeComponent } from './containers/home/home.component'; import { NgxBootstrapComponent } from './containers/ngx-bootstrap-demo/ngx-bootstrap.component'; - +import { NotFoundComponent } from './containers/not-found/not-found.component'; +import { UsersComponent } from './containers/users/users.component'; import { LinkService } from './shared/link.service'; import { UserService } from './shared/user.service'; -import { ORIGIN_URL } from '@nguniversal/aspnetcore-engine/tokens'; export function createTranslateLoader(http: HttpClient, baseHref) { - // Temporary Azure hack - if (baseHref === null && typeof window !== 'undefined') { - baseHref = window.location.origin; - } - // i18n files are in `wwwroot/assets/` - return new TranslateHttpLoader(http, `${baseHref}/assets/i18n/`, '.json'); + // Temporary Azure hack + if (baseHref === null && typeof window !== 'undefined') { + baseHref = window.location.origin; + } + // i18n files are in `wwwroot/assets/` + return new TranslateHttpLoader(http, `${baseHref}/assets/i18n/`, '.json'); } @NgModule({ - declarations: [ - AppComponent, - NavMenuComponent, - CounterComponent, - UsersComponent, - UserDetailComponent, - HomeComponent, - NotFoundComponent, - NgxBootstrapComponent - ], - imports: [ - CommonModule, - BrowserModule.withServerTransition({ - appId: 'my-app-id' // make sure this matches with your Server NgModule - }), - HttpClientModule, - TransferHttpCacheModule, - BrowserTransferStateModule, - FormsModule, - ReactiveFormsModule, - AccordionModule.forRoot(), // You could also split this up if you don't want the Entire Module imported + declarations: [ + AppComponent, + NavMenuComponent, + CounterComponent, + UsersComponent, + UserDetailComponent, + HomeComponent, + NotFoundComponent, + NgxBootstrapComponent + ], + imports: [ + CommonModule, + BrowserModule.withServerTransition({ + appId: 'my-app-id' // make sure this matches with your Server NgModule + }), + HttpClientModule, + TransferHttpCacheModule, + BrowserTransferStateModule, + FormsModule, + ReactiveFormsModule, + AccordionModule.forRoot(), // You could also split this up if you don't want the Entire Module imported - // i18n support - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: (createTranslateLoader), - deps: [HttpClient, [ORIGIN_URL]] - } - }), + // i18n support + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: createTranslateLoader, + deps: [HttpClient, [ORIGIN_URL]] + } + }), - // App Routing - RouterModule.forRoot([ - { - path: '', - redirectTo: 'home', - pathMatch: 'full' - }, - { - path: 'home', component: HomeComponent, + // App Routing + RouterModule.forRoot( + [ + { + path: '', + redirectTo: 'home', + pathMatch: 'full' + }, + { + path: 'home', + component: HomeComponent, - // *** SEO Magic *** - // We're using "data" in our Routes to pass in our <meta> <link> tag information - // Note: This is only happening for ROOT level Routes, you'd have to add some additional logic if you wanted this for Child level routing - // When you change Routes it will automatically append these to your document for you on the Server-side - // - check out app.component.ts to see how it's doing this - data: { - title: 'Homepage', - meta: [{ name: 'description', content: 'This is an example Description Meta tag!' }], - links: [ - { rel: 'canonical', href: 'http://blogs.example.com/blah/nice' }, - { rel: 'alternate', hreflang: 'es', href: 'http://es.example.com/' } - ] - } - }, - { - path: 'counter', component: CounterComponent, - data: { - title: 'Counter', - meta: [{ name: 'description', content: 'This is an Counter page Description!' }], - links: [ - { rel: 'canonical', href: 'http://blogs.example.com/counter/something' }, - { rel: 'alternate', hreflang: 'es', href: 'http://es.example.com/counter' } - ] - } - }, - { - path: 'users', component: UsersComponent, - data: { - title: 'Users REST example', - meta: [{ name: 'description', content: 'This is User REST API example page Description!' }], - links: [ - { rel: 'canonical', href: 'http://blogs.example.com/chat/something' }, - { rel: 'alternate', hreflang: 'es', href: 'http://es.example.com/users' } - ] - } - }, - { - path: 'ngx-bootstrap', component: NgxBootstrapComponent, - data: { - title: 'Ngx-bootstrap demo!!', - meta: [{ name: 'description', content: 'This is an Demo Bootstrap page Description!' }], - links: [ - { rel: 'canonical', href: 'http://blogs.example.com/bootstrap/something' }, - { rel: 'alternate', hreflang: 'es', href: 'http://es.example.com/bootstrap-demo' } - ] - } - }, + // *** SEO Magic *** + // We're using "data" in our Routes to pass in our <title> <meta> <link> tag information + // Note: This is only happening for ROOT level Routes, you'd have to add some additional logic if you wanted this for Child level routing + // When you change Routes it will automatically append these to your document for you on the Server-side + // - check out app.component.ts to see how it's doing this + data: { + title: 'Homepage', + meta: [ + { + name: 'description', + content: 'This is an example Description Meta tag!' + } + ], + links: [ + { rel: 'canonical', href: 'http://blogs.example.com/blah/nice' }, + { + rel: 'alternate', + hreflang: 'es', + href: 'http://es.example.com/' + } + ] + } + }, + { + path: 'counter', + component: CounterComponent, + data: { + title: 'Counter', + meta: [ + { + name: 'description', + content: 'This is an Counter page Description!' + } + ], + links: [ + { + rel: 'canonical', + href: 'http://blogs.example.com/counter/something' + }, + { + rel: 'alternate', + hreflang: 'es', + href: 'http://es.example.com/counter' + } + ] + } + }, + { + path: 'users', + component: UsersComponent, + data: { + title: 'Users REST example', + meta: [ + { + name: 'description', + content: 'This is User REST API example page Description!' + } + ], + links: [ + { + rel: 'canonical', + href: 'http://blogs.example.com/chat/something' + }, + { + rel: 'alternate', + hreflang: 'es', + href: 'http://es.example.com/users' + } + ] + } + }, + { + path: 'ngx-bootstrap', + component: NgxBootstrapComponent, + data: { + title: 'Ngx-bootstrap demo!!', + meta: [ + { + name: 'description', + content: 'This is an Demo Bootstrap page Description!' + } + ], + links: [ + { + rel: 'canonical', + href: 'http://blogs.example.com/bootstrap/something' + }, + { + rel: 'alternate', + hreflang: 'es', + href: 'http://es.example.com/bootstrap-demo' + } + ] + } + }, - { path: 'lazy', loadChildren: './containers/lazy/lazy.module#LazyModule'}, + { + path: 'lazy', + loadChildren: './containers/lazy/lazy.module#LazyModule' + }, - { - path: '**', component: NotFoundComponent, - data: { - title: '404 - Not found', - meta: [{ name: 'description', content: '404 - Error' }], - links: [ - { rel: 'canonical', href: 'http://blogs.example.com/bootstrap/something' }, - { rel: 'alternate', hreflang: 'es', href: 'http://es.example.com/bootstrap-demo' } - ] - } - } - ], { - // Router options - useHash: false, - preloadingStrategy: PreloadAllModules, - initialNavigation: 'enabled' - }) - ], - providers: [ - LinkService, - UserService, - TranslateModule - ], - bootstrap: [AppComponent] + { + path: '**', + component: NotFoundComponent, + data: { + title: '404 - Not found', + meta: [{ name: 'description', content: '404 - Error' }], + links: [ + { + rel: 'canonical', + href: 'http://blogs.example.com/bootstrap/something' + }, + { + rel: 'alternate', + hreflang: 'es', + href: 'http://es.example.com/bootstrap-demo' + } + ] + } + } + ], + { + // Router options + useHash: false, + preloadingStrategy: PreloadAllModules, + initialNavigation: 'enabled' + } + ) + ], + providers: [LinkService, UserService, TranslateModule], + bootstrap: [AppComponent] }) -export class AppModuleShared { -} +export class AppModuleShared {} diff --git a/ClientApp/app/components/navmenu/navmenu.component.html b/ClientApp/app/components/navmenu/navmenu.component.html index fdef2c65..ba9ff74e 100644 --- a/ClientApp/app/components/navmenu/navmenu.component.html +++ b/ClientApp/app/components/navmenu/navmenu.component.html @@ -1,5 +1,5 @@ <nav class="navbar navbar-expand-lg"> - <a [routerLink]="['/home']" class='navbar-brand'>Angular 6 Universal & ASP.NET Core</a> + <a [routerLink]="['/home']" class='navbar-brand'>Angular 7 Universal & ASP.NET Core</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation" (click)="collapseNavbar()"> <i class="fas fa-bars"></i> diff --git a/ClientApp/app/components/navmenu/navmenu.component.scss b/ClientApp/app/components/navmenu/navmenu.component.scss index 462e9461..66115049 100644 --- a/ClientApp/app/components/navmenu/navmenu.component.scss +++ b/ClientApp/app/components/navmenu/navmenu.component.scss @@ -1,10 +1,10 @@ -@import "http://github.com/variables"; -// Mobile first styling. +@import 'http://github.com/variables'; +// Mobile first styling. /* Apply for small displays */ .navbar { position: fixed; - background-color: theme-color("primary"); + background-color: theme-color('primary'); top: 0; right: 0; left: 0; @@ -53,7 +53,7 @@ li a { li.link-active a, li.link-active a:hover, li.link-active a:focus { - background-color: theme-color("accent"); + background-color: theme-color('accent'); color: $body-bg; } @@ -81,7 +81,7 @@ li.link-active a:focus { .navbar, a, button { - color: theme-color("primary"); + color: theme-color('primary'); } .navbar-brand { padding: 15px 15px; diff --git a/ClientApp/app/components/navmenu/navmenu.component.ts b/ClientApp/app/components/navmenu/navmenu.component.ts index c5eb3e8c..d3dadb4d 100644 --- a/ClientApp/app/components/navmenu/navmenu.component.ts +++ b/ClientApp/app/components/navmenu/navmenu.component.ts @@ -1,23 +1,22 @@ import { Component } from '@angular/core'; @Component({ - selector: 'app-nav-menu', - templateUrl: './navmenu.component.html', - styleUrls: ['./navmenu.component.scss'] + selector: 'app-nav-menu', + templateUrl: './navmenu.component.html', + styleUrls: ['./navmenu.component.scss'] }) - export class NavMenuComponent { - collapse: string = 'collapse'; + collapse: string = 'collapse'; - collapseNavbar(): void { - if (this.collapse.length > 1) { - this.collapse = ''; - } else { - this.collapse = 'collapse'; - } + collapseNavbar(): void { + if (this.collapse.length > 1) { + this.collapse = ''; + } else { + this.collapse = 'collapse'; } + } - collapseMenu() { - this.collapse = 'collapse'; - } + collapseMenu() { + this.collapse = 'collapse'; + } } diff --git a/ClientApp/app/components/user-detail/user-detail.component.html b/ClientApp/app/components/user-detail/user-detail.component.html index 0b9ed7b1..f43b4bbb 100644 --- a/ClientApp/app/components/user-detail/user-detail.component.html +++ b/ClientApp/app/components/user-detail/user-detail.component.html @@ -1,6 +1,6 @@ <div class="panel"> <h2 class="title">{{user.name}} details:</h2> - <form class="body" [formGroup]="userForm"(ngSubmit)="updateUser()"> + <form class="body" [formGroup]="userForm" (ngSubmit)="updateUser()"> <div class="form-group"> <label for="userId">Id</label> <input type="input" formControlName="id" class="form-control" id="userId" placeholder="Id" readonly> diff --git a/ClientApp/app/components/user-detail/user-detail.component.scss b/ClientApp/app/components/user-detail/user-detail.component.scss index 15a7f257..a7809e89 100644 --- a/ClientApp/app/components/user-detail/user-detail.component.scss +++ b/ClientApp/app/components/user-detail/user-detail.component.scss @@ -1,4 +1,4 @@ -@import "http://github.com/variables"; +@import 'http://github.com/variables'; :host-context() { flex: 1; } diff --git a/ClientApp/app/components/user-detail/user-detail.component.ts b/ClientApp/app/components/user-detail/user-detail.component.ts index 2f2f5ffd..d14dbdc1 100644 --- a/ClientApp/app/components/user-detail/user-detail.component.ts +++ b/ClientApp/app/components/user-detail/user-detail.component.ts @@ -1,40 +1,52 @@ -import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges +} from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { IUser } from '../../models/User'; import { UserService } from '../../shared/user.service'; -import { FormControl, FormGroup } from '@angular/forms'; -import { debounce, debounceTime, distinctUntilChanged } from 'rxjs/operators'; @Component({ - selector: 'app-user-detail', - styleUrls: ['./user-detail.component.scss'], - templateUrl: './user-detail.component.html' + selector: 'app-user-detail', + styleUrls: ['./user-detail.component.scss'], + templateUrl: './user-detail.component.html' }) export class UserDetailComponent implements OnInit, OnChanges { + @Input() user: IUser; + @Output() userUpdate: EventEmitter<any> = new EventEmitter(); + userForm = new FormGroup({ + id: new FormControl(), + name: new FormControl() + }); + constructor(private userService: UserService) {} - @Input() user: IUser; - @Output() userUpdate: EventEmitter<any> = new EventEmitter(); - userForm = new FormGroup({ - id: new FormControl(), - name: new FormControl() - }); - constructor(private userService: UserService) { } - - ngOnInit() { - this.userForm.valueChanges.pipe( - debounceTime(400), - distinctUntilChanged() - ).subscribe(user => this.user = user); - } - ngOnChanges(changes: SimpleChanges) { - this.userForm.patchValue(this.user); - } + ngOnInit() { + this.userForm.valueChanges + .pipe( + debounceTime(400), + distinctUntilChanged() + ) + .subscribe(user => (this.user = user)); + } + ngOnChanges(changes: SimpleChanges) { + this.userForm.patchValue(this.user); + } - updateUser() { - this.userService.updateUser(this.userForm.value).subscribe(result => { - console.log('Put user result: ', result); - }, error => { - console.log(`There was an issue. ${error._body}.`); - }); - this.userUpdate.emit(this.user); - } + updateUser() { + this.userService.updateUser(this.userForm.value).subscribe( + result => { + console.log('Put user result: ', result); + }, + error => { + console.log(`There was an issue. ${error._body}.`); + } + ); + this.userUpdate.emit(this.user); + } } diff --git a/ClientApp/app/containers/counter/counter.component.html b/ClientApp/app/containers/counter/counter.component.html index cc2bbd59..d4dcafe4 100644 --- a/ClientApp/app/containers/counter/counter.component.html +++ b/ClientApp/app/containers/counter/counter.component.html @@ -2,6 +2,8 @@ <h1>Counter</h1> <p>This is a simple example of an Angular 2 component.</p> -<p>Current count: <strong>{{ currentCount }}</strong></p> +<p>Current count: + <strong>{{ currentCount }}</strong> +</p> <button (click)="incrementCounter()">Increment</button> diff --git a/ClientApp/app/containers/counter/counter.component.spec.ts b/ClientApp/app/containers/counter/counter.component.spec.ts index c1e54ed4..960f08d3 100644 --- a/ClientApp/app/containers/counter/counter.component.spec.ts +++ b/ClientApp/app/containers/counter/counter.component.spec.ts @@ -1,29 +1,29 @@ -/// <reference path="../../../../node_modules/@types/jasmine/index.d.ts" /> -import { assert } from 'chai'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CounterComponent } from './counter.component'; -import { TestBed, async, ComponentFixture } from '@angular/core/testing'; + +import {} from 'jasmine'; let fixture: ComponentFixture<CounterComponent>; describe('Counter component', () => { - beforeEach(() => { - TestBed.configureTestingModule({ declarations: [CounterComponent] }); - fixture = TestBed.createComponent(CounterComponent); - fixture.detectChanges(); - }); + beforeEach(() => { + TestBed.configureTestingModule({ declarations: [CounterComponent] }); + fixture = TestBed.createComponent(CounterComponent); + fixture.detectChanges(); + }); - it('should display a title', async(() => { - const titleText = fixture.nativeElement.querySelector('h1').textContent; - expect(titleText).toEqual('Counter'); - })); + it('should display a title', async(() => { + const titleText = fixture.nativeElement.querySelector('h1').textContent; + expect(titleText).toEqual('Counter'); + })); - it('should start with count 0, then increments by 1 when clicked', async(() => { - const countElement = fixture.nativeElement.querySelector('strong'); - expect(countElement.textContent).toEqual('0'); + it('should start with count 0, then increments by 1 when clicked', async(() => { + const countElement = fixture.nativeElement.querySelector('strong'); + expect(countElement.textContent).toEqual('0'); - const incrementButton = fixture.nativeElement.querySelector('button'); - incrementButton.click(); - fixture.detectChanges(); - expect(countElement.textContent).toEqual('1'); - })); + const incrementButton = fixture.nativeElement.querySelector('button'); + incrementButton.click(); + fixture.detectChanges(); + expect(countElement.textContent).toEqual('1'); + })); }); diff --git a/ClientApp/app/containers/counter/counter.component.ts b/ClientApp/app/containers/counter/counter.component.ts index 5adb5195..42123687 100644 --- a/ClientApp/app/containers/counter/counter.component.ts +++ b/ClientApp/app/containers/counter/counter.component.ts @@ -1,13 +1,13 @@ import { Component } from '@angular/core'; @Component({ - selector: 'app-counter', - templateUrl: './counter.component.html' + selector: 'app-counter', + templateUrl: './counter.component.html' }) export class CounterComponent { - public currentCount = 0; + public currentCount = 0; - public incrementCounter() { - this.currentCount++; - } + public incrementCounter() { + this.currentCount++; + } } diff --git a/ClientApp/app/containers/home/home.component.html b/ClientApp/app/containers/home/home.component.html index 8f12abfc..c3a92f5c 100644 --- a/ClientApp/app/containers/home/home.component.html +++ b/ClientApp/app/containers/home/home.component.html @@ -1,9 +1,11 @@ <h1>{{ title }}</h1> <blockquote> - <strong>Enjoy the latest features from .NET Core & Angular 6.x!</strong> - <br> For more info check the repo here: <a href="https://github.com/MarkPieszak/aspnetcore-angular2-universal">AspNetCore-Angular-Universal repo</a> - <br><br> + <strong>Enjoy the latest features from .NET Core & Angular 7.x!</strong> + <br> For more info check the repo here: + <a href="https://github.com/MarkPieszak/aspnetcore-angular-universal" target="_blank">AspNetCore-Angular-Universal repo</a> + <br> + <br> </blockquote> <div class="row"> @@ -12,10 +14,9 @@ <h2>{{ 'HOME_FEATURE_LIST_TITLE' | translate }} </h2> <ul> <li>ASP.NET Core 2.1 :: ( Visual Studio 2017 )</li> <li> - Angular 6.* front-end UI framework + Angular 7.* front-end UI framework <ul> - <li>Angular **platform-server** (aka: Universal) - server-side rendering for SEO, deep-linking, and - incredible performance.</li> + <li>Angular **platform-server** (aka: Universal) - server-side rendering for SEO, deep-linking, and incredible performance.</li> <!--<li>HMR State Management - Don't lose your applications state during HMR!</li>--> <li>AoT (Ahead-of-time) production compilation for even faster Prod builds.</li> </ul> @@ -41,24 +42,44 @@ <h2>{{ 'HOME_FEATURE_LIST_TITLE' | translate }} </h2> <h2>{{ 'HOME_ISSUES_TITLE' | translate }}</h2> <ul> - <li><strong>Issues with this Starter?</strong> <br>Please post an issue here: <a href="https://github.com/MarkPieszak/aspnetcore-angular2-universal">AspNetCore-Angular2-Universal repo</a><br><br></li> + <li> + <strong>Issues with this Starter?</strong> + <br>Please post an issue here: + <a href="https://github.com/MarkPieszak/aspnetcore-angular2-universal" target="_blank">AspNetCore-Angular2-Universal repo</a> + <br> + <br> + </li> <!--<li><strong>Issues with <u>Universal</u> itself?</strong> <br>Please post an issue here: <a href="https://github.com/angular/universal">Angular Universal repo</a></li>--> </ul> + + <h2><a href="https://trilon.io/" target="_blank">Trilon Consulting - Trilon.io</a></h2> + + <strong>Consulting | Development | Training | Workshops</strong> + <br><br> + Get your Team or Application up to speed by working with some of the leading industry experts in JavaScript, Node / NestJS, & ASP.NET!<br> + <br> + <strong>Follow us on Twitter!</strong><br><br> + <a href="http://www.twitter.com/trilon_io" target="_blank">@trilon_io</a> | + <a href="http://www.twitter.com/MarkPieszak" target="_blank">@MarkPieszak</a> + <br> + <br> + </div> </div> + <div class="row"> <div class="col-lg-12"> <h2> {{ 'SWITCH_LANGUAGE' | translate }}</h2> <button class="btn btn-outline-secondary mr-2" (click)="setLanguage('en')"> - <span class="flag-icon flag-icon-us"></span> {{ 'ENGLISH' | translate }} + <span class="flag-icon flag-icon-us"></span> {{ 'ENGLISH' | translate }} </button> <button class="btn btn-outline-secondary" (click)="setLanguage('no')"> - <span class="flag-icon flag-icon-no"></span> {{ 'NORWEGIAN' | translate }} + <span class="flag-icon flag-icon-no"></span> {{ 'NORWEGIAN' | translate }} </button> </div> diff --git a/ClientApp/app/containers/home/home.component.ts b/ClientApp/app/containers/home/home.component.ts index abc2db05..850f7b8f 100644 --- a/ClientApp/app/containers/home/home.component.ts +++ b/ClientApp/app/containers/home/home.component.ts @@ -1,25 +1,22 @@ -import { Component, OnInit, Inject } from '@angular/core'; - +import { Component, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; @Component({ - selector: 'app-home', - templateUrl: './home.component.html' + selector: 'app-home', + templateUrl: './home.component.html' }) export class HomeComponent implements OnInit { + title: string = + 'Angular 7.x Universal & ASP.NET Core 2.1 advanced starter-kit'; - title: string = 'Angular 6.x Universal & ASP.NET Core 2.1 advanced starter-kit'; - - // Use "constructor"s only for dependency injection - constructor( - public translate: TranslateService - ) { } + // Use "constructor"s only for dependency injection + constructor(public translate: TranslateService) {} - // Here you want to handle anything with @Input()'s @Output()'s - // Data retrieval / etc - this is when the Component is "ready" and wired up - ngOnInit() { } + // Here you want to handle anything with @Input()'s @Output()'s + // Data retrieval / etc - this is when the Component is "ready" and wired up + ngOnInit() {} - public setLanguage(lang) { - this.translate.use(lang); - } + public setLanguage(lang) { + this.translate.use(lang); + } } diff --git a/ClientApp/app/containers/lazy/lazy.component.ts b/ClientApp/app/containers/lazy/lazy.component.ts index 97d260fd..53327733 100644 --- a/ClientApp/app/containers/lazy/lazy.component.ts +++ b/ClientApp/app/containers/lazy/lazy.component.ts @@ -10,4 +10,4 @@ import { Component } from '@angular/core'; </blockquote> ` }) -export class LazyComponent { } +export class LazyComponent {} diff --git a/ClientApp/app/containers/lazy/lazy.module.ts b/ClientApp/app/containers/lazy/lazy.module.ts index 8d468754..aa33a605 100644 --- a/ClientApp/app/containers/lazy/lazy.module.ts +++ b/ClientApp/app/containers/lazy/lazy.module.ts @@ -1,4 +1,4 @@ -import { NgModule, Component } from '@angular/core'; +import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { LazyComponent } from './lazy.component'; @@ -10,6 +10,4 @@ import { LazyComponent } from './lazy.component'; ]) ] }) -export class LazyModule { - -} +export class LazyModule {} diff --git a/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.html b/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.html index c57f2260..2b95c774 100644 --- a/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.html +++ b/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.html @@ -1,38 +1,35 @@ <h1>Ngx-bootstrap Demo:</h1> <blockquote> - <strong>Here we're using Bootstrap via <a href="https://github.com/valor-software/ngx-bootstrap">ngx-bootstrap</a>, which can even be rendered on the server!</strong> + <strong>Here we're using Bootstrap via + <a href="https://github.com/valor-software/ngx-bootstrap">ngx-bootstrap</a>, which can even be rendered on the server!</strong> <br> </blockquote> <hr> -<br><br> +<br> +<br> <h3>Bootstrap Accordion demo:</h3> <p> - <button type="button" class="btn btn-primary btn-sm" - (click)="group.isOpen = !group.isOpen"> + <button type="button" class="btn btn-primary btn-sm" (click)="group.isOpen = !group.isOpen"> Toggle last panel </button> - <button type="button" class="btn btn-primary btn-sm ml-2" - (click)="status.isFirstDisabled = ! status.isFirstDisabled"> + <button type="button" class="btn btn-primary btn-sm ml-2" (click)="status.isFirstDisabled = ! status.isFirstDisabled"> Enable / Disable first panel </button> </p> <div class="checkbox"> <label> - <input type="checkbox" [(ngModel)]="oneAtATime"> - Open only one at a time + <input type="checkbox" [(ngModel)]="oneAtATime"> Open only one at a time </label> </div> <accordion [closeOthers]="oneAtATime"> - <accordion-group heading="Static Header, initially expanded" - [isOpen]="status.isFirstOpen" - [isDisabled]="status.isFirstDisabled"> + <accordion-group heading="Static Header, initially expanded" [isOpen]="status.isFirstOpen" [isDisabled]="status.isFirstDisabled"> This content is straight in the template. </accordion-group> <accordion-group *ngFor="let group of groups" [heading]="group.title"> @@ -46,9 +43,8 @@ <h3>Bootstrap Accordion demo:</h3> <accordion-group #group [isOpen]="status.open"> <div accordion-heading> I can have markup, too! - <i class="pull-right glyphicon" - [ngClass]="{'glyphicon-chevron-down': group?.isOpen, 'glyphicon-chevron-right': !group?.isOpen}"></i> + <i class="pull-right glyphicon" [ngClass]="{'glyphicon-chevron-down': group?.isOpen, 'glyphicon-chevron-right': !group?.isOpen}"></i> </div> This is just some content to illustrate fancy headings. </accordion-group> -</accordion> \ No newline at end of file +</accordion> diff --git a/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.ts b/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.ts index a8ba7dec..d1122dad 100644 --- a/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.ts +++ b/ClientApp/app/containers/ngx-bootstrap-demo/ngx-bootstrap.component.ts @@ -1,36 +1,34 @@ import { Component } from '@angular/core'; @Component({ - selector: 'app-bootstrap', - templateUrl: './ngx-bootstrap.component.html' + selector: 'app-bootstrap', + templateUrl: './ngx-bootstrap.component.html' }) export class NgxBootstrapComponent { + public oneAtATime: boolean = true; + public items = ['Item 1', 'Item 2', 'Item 3']; - public oneAtATime: boolean = true; - public items = ['Item 1', 'Item 2', 'Item 3']; + public status = { + isFirstOpen: true, + isFirstDisabled: false, + open: false + }; - public status = { - isFirstOpen: true, - isFirstDisabled: false, - open: false - }; - - public groups = [ - { - title: 'Angular is neato gang!', - content: 'ASP.NET Core is too :)' - }, - { - title: 'Another One!', - content: 'Some content going here' - } - ]; - - // Use "constructor"s only for dependency injection - constructor() { } - - addItem(): void { - this.items.push(`Items ${this.items.length + 1}`); + public groups = [ + { + title: 'Angular is neato gang!', + content: 'ASP.NET Core is too :)' + }, + { + title: 'Another One!', + content: 'Some content going here' } + ]; + + // Use "constructor"s only for dependency injection + constructor() {} -} \ No newline at end of file + addItem(): void { + this.items.push(`Items ${this.items.length + 1}`); + } +} diff --git a/ClientApp/app/containers/not-found/not-found.component.html b/ClientApp/app/containers/not-found/not-found.component.html index 347dcb93..b0b7072d 100644 --- a/ClientApp/app/containers/not-found/not-found.component.html +++ b/ClientApp/app/containers/not-found/not-found.component.html @@ -1,8 +1,8 @@ <div class="wrapper"> <header class="header header--large"> <h1 class="title">Ahhhhhhhhhhh! This page doesn't exist</h1> - <h2 class="strapline">Not to worry. You can either head back to <a href="http://github.com/">our homepage</a>, or sit there and listen to a goat scream like - a human.</h2> + <h2 class="strapline">Not to worry. You can either head back to + <a href="http://github.com/">our homepage</a>, or sit there and listen to a goat scream like a human.</h2> </header> <div class="content fit-vid vid"> <iframe src="http://www.youtube.com/embed/SIaFtAKnqBU?vq=hd720&rel=0&showinfo=0&controls=0&iv_load_policy=3&loop=1&playlist=SIaFtAKnqBU&modestbranding=1&autoplay=1" diff --git a/ClientApp/app/containers/not-found/not-found.component.ts b/ClientApp/app/containers/not-found/not-found.component.ts index c59a5f8f..6bd9147a 100644 --- a/ClientApp/app/containers/not-found/not-found.component.ts +++ b/ClientApp/app/containers/not-found/not-found.component.ts @@ -1,11 +1,11 @@ import { Component, OnInit } from '@angular/core'; @Component({ - selector: 'app-not-found', - templateUrl: './not-found.component.html' + selector: 'app-not-found', + templateUrl: './not-found.component.html' }) export class NotFoundComponent implements OnInit { - constructor() { } + constructor() {} - ngOnInit() { } + ngOnInit() {} } diff --git a/ClientApp/app/containers/users/users.component.scss b/ClientApp/app/containers/users/users.component.scss index 85a47c26..67acfed0 100644 --- a/ClientApp/app/containers/users/users.component.scss +++ b/ClientApp/app/containers/users/users.component.scss @@ -1,4 +1,4 @@ -@import "http://github.com/variables"; +@import 'http://github.com/variables'; .users-wrapper { max-height: 65vh; overflow-y: auto; @@ -35,9 +35,9 @@ h1 { align-items: center; padding: 0px; &:hover { - color: #607D8B; + color: #607d8b; background-color: #e6e6e6; - left: .1em; + left: 0.1em; } &.selected { background-color: #7eaacd !important; @@ -45,7 +45,7 @@ h1 { } } -.users li>* { +.users li > * { display: flex; flex-direction: column; align-items: center; @@ -58,13 +58,13 @@ h1 { } .users li.selected:hover { - background-color: #BBD8DC !important; + background-color: #bbd8dc !important; color: white; } .user-id { @extend .input-group-text; - background-color: #607D8B; + background-color: #607d8b; color: white; width: 2.5rem; border: 0px; @@ -73,9 +73,7 @@ h1 { } .delete-user { - @extend .btn, - .badge, - .badge-secondary; + @extend .btn, .badge, .badge-secondary; height: 25px; width: 25px; margin-right: 10px; diff --git a/ClientApp/app/containers/users/users.component.ts b/ClientApp/app/containers/users/users.component.ts index 27a9bbff..b64ea0ee 100644 --- a/ClientApp/app/containers/users/users.component.ts +++ b/ClientApp/app/containers/users/users.component.ts @@ -1,13 +1,11 @@ import { - Component, OnInit, Inject -} from '@angular/core'; -import { - trigger, + animate, state, style, - animate, - transition + transition, + trigger } from '@angular/animations'; +import { Component, OnInit } from '@angular/core'; import { IUser } from '../../models/User'; import { UserService } from '../../shared/user.service'; @@ -31,14 +29,11 @@ import { UserService } from '../../shared/user.service'; ] }) export class UsersComponent implements OnInit { - users: IUser[]; selectedUser: IUser; // Use "constructor"s only for dependency injection - constructor( - private userService: UserService - ) { } + constructor(private userService: UserService) {} // Here you want to handle anything with @Input()'s @Output()'s // Data retrieval / etc - this is when the Component is "ready" and wired up @@ -55,27 +50,33 @@ export class UsersComponent implements OnInit { deleteUser(user) { this.clearUser(); - this.userService.deleteUser(user).subscribe(result => { - console.log('Delete user result: ', result); - let position = this.users.indexOf(user); - this.users.splice(position, 1); - }, error => { - console.log(`There was an issue. ${error._body}.`); - }); + this.userService.deleteUser(user).subscribe( + result => { + console.log('Delete user result: ', result); + let position = this.users.indexOf(user); + this.users.splice(position, 1); + }, + error => { + console.log(`There was an issue. ${error._body}.`); + } + ); } onUserUpdate(user: IUser) { - this.users[this.users.findIndex((u => u.id == user.id))] = user; + this.users[this.users.findIndex(u => u.id == user.id)] = user; } addUser(newUserName) { - this.userService.addUser(newUserName).subscribe(result => { - console.log('Post user result: ', result); - this.users.push(result); - this.selectedUser = result; - }, error => { - console.log(`There was an issue. ${error._body}.`); - }); + this.userService.addUser(newUserName).subscribe( + result => { + console.log('Post user result: ', result); + this.users.push(result); + this.selectedUser = result; + }, + error => { + console.log(`There was an issue. ${error._body}.`); + } + ); } clearUser() { diff --git a/ClientApp/app/models/User.ts b/ClientApp/app/models/User.ts index 53f9df3a..f140ecb1 100644 --- a/ClientApp/app/models/User.ts +++ b/ClientApp/app/models/User.ts @@ -1,4 +1,4 @@ export interface IUser { - id: number; - name: string; -} \ No newline at end of file + id: number; + name: string; +} diff --git a/ClientApp/app/shared/link.service.ts b/ClientApp/app/shared/link.service.ts index c5a2f16d..2e6754a4 100644 --- a/ClientApp/app/shared/link.service.ts +++ b/ClientApp/app/shared/link.service.ts @@ -6,90 +6,90 @@ * Soon there will be an overall HeadService within Angular that handles Meta/Link everything */ -import { Injectable, PLATFORM_ID, Optional, RendererFactory2, ViewEncapsulation, Inject } from '@angular/core'; -import { DOCUMENT } from '@angular/platform-browser'; import { isPlatformServer } from '@angular/common'; +import { + Inject, + Injectable, + PLATFORM_ID, + RendererFactory2, + ViewEncapsulation +} from '@angular/core'; +import { DOCUMENT } from '@angular/platform-browser'; @Injectable() export class LinkService { - - private isServer: boolean = isPlatformServer(this.platform_id); - - constructor( - private rendererFactory: RendererFactory2, - @Inject(DOCUMENT) private document, - @Inject(PLATFORM_ID) private platform_id - ) { - } - - /** - * Inject the State into the bottom of the <head> - */ - addTag(tag: LinkDefinition, forceCreation?: boolean) { - - try { - const renderer = this.rendererFactory.createRenderer(this.document, { - id: '-1', - encapsulation: ViewEncapsulation.None, - styles: [], - data: {} - }); - - const link = renderer.createElement('link'); - const head = this.document.head; - const selector = this._parseSelector(tag); - - if (head === null) { - throw new Error('<head> not found within DOCUMENT.'); - } - - Object.keys(tag).forEach((prop: string) => { - return renderer.setAttribute(link, prop, tag[prop]); - }); - - // [TODO]: get them to update the existing one (if it exists) ? - renderer.appendChild(head, link); - - } catch (e) { - console.error('Error within linkService : ', e); - } + private isServer: boolean = isPlatformServer(this.platform_id); + + constructor( + private rendererFactory: RendererFactory2, + @Inject(DOCUMENT) private document, + @Inject(PLATFORM_ID) private platform_id + ) {} + + /** + * Inject the State into the bottom of the <head> + */ + addTag(tag: LinkDefinition, forceCreation?: boolean) { + try { + const renderer = this.rendererFactory.createRenderer(this.document, { + id: '-1', + encapsulation: ViewEncapsulation.None, + styles: [], + data: {} + }); + + const link = renderer.createElement('link'); + const head = this.document.head; + const selector = this._parseSelector(tag); + + if (head === null) { + throw new Error('<head> not found within DOCUMENT.'); + } + + Object.keys(tag).forEach((prop: string) => { + return renderer.setAttribute(link, prop, tag[prop]); + }); + + // [TODO]: get them to update the existing one (if it exists) ? + renderer.appendChild(head, link); + } catch (e) { + console.error('Error within linkService : ', e); } - - // updateTag(tag: LinkDefinition, selector?: string) { - // if (!tag) return null; - // selector = selector || this._parseSelector(tag); - // const meta = this.getTag(selector); - // if (meta) { - // return this._setMetaElementAttributes(tag, meta); - // } - // return this._getOrCreateElement(tag, true); - // } - - // getTag(attrSelector: string): HTMLMetaElement { - // if (!attrSelector) return null; - // return this._dom.querySelector(this._doc, `meta[${attrSelector}]`); - // } - - private _parseSelector(tag: LinkDefinition): string { - // Possibly re-work this - const attr: string = tag.rel ? 'rel' : 'hreflang'; - return `${attr}="${tag[attr]}"`; - } - + } + + // updateTag(tag: LinkDefinition, selector?: string) { + // if (!tag) return null; + // selector = selector || this._parseSelector(tag); + // const meta = this.getTag(selector); + // if (meta) { + // return this._setMetaElementAttributes(tag, meta); + // } + // return this._getOrCreateElement(tag, true); + // } + + // getTag(attrSelector: string): HTMLMetaElement { + // if (!attrSelector) return null; + // return this._dom.querySelector(this._doc, `meta[${attrSelector}]`); + // } + + private _parseSelector(tag: LinkDefinition): string { + // Possibly re-work this + const attr: string = tag.rel ? 'rel' : 'hreflang'; + return `${attr}="${tag[attr]}"`; + } } - export declare type LinkDefinition = { - charset?: string; - crossorigin?: string; - href?: string; - hreflang?: string; - media?: string; - rel?: string; - rev?: string; - sizes?: string; - target?: string; - type?: string; + charset?: string; + crossorigin?: string; + href?: string; + hreflang?: string; + media?: string; + rel?: string; + rev?: string; + sizes?: string; + target?: string; + type?: string; } & { - [prop: string]: string; - }; \ No newline at end of file + [prop: string]: string; +}; diff --git a/ClientApp/app/shared/user.service.ts b/ClientApp/app/shared/user.service.ts index 31110e7e..85249249 100644 --- a/ClientApp/app/shared/user.service.ts +++ b/ClientApp/app/shared/user.service.ts @@ -1,21 +1,13 @@ -import { Injectable, Inject, Injector } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { Http, URLSearchParams } from '@angular/http'; -import { APP_BASE_HREF } from '@angular/common'; +import { Injectable, Injector } from '@angular/core'; import { ORIGIN_URL } from '@nguniversal/aspnetcore-engine/tokens'; import { IUser } from '../models/User'; -import { Observable } from 'rxjs'; - @Injectable() export class UserService { - private baseUrl: string; - constructor( - private http: HttpClient, - private injector: Injector - ) { + constructor(private http: HttpClient, private injector: Injector) { this.baseUrl = this.injector.get(ORIGIN_URL); } @@ -31,11 +23,13 @@ export class UserService { return this.http.delete<IUser>(`${this.baseUrl}/api/users/` + user.id); } - updateUser(user: IUser){ + updateUser(user: IUser) { return this.http.put<IUser>(`${this.baseUrl}/api/users/` + user.id, user); } addUser(newUserName: string) { - return this.http.post<IUser>(`${this.baseUrl}/api/users`, { name: newUserName }); + return this.http.post<IUser>(`${this.baseUrl}/api/users`, { + name: newUserName + }); } } diff --git a/ClientApp/boot.browser.ts b/ClientApp/boot.browser.ts index 329344f7..68de6e5b 100644 --- a/ClientApp/boot.browser.ts +++ b/ClientApp/boot.browser.ts @@ -5,12 +5,12 @@ import { AppModule } from './app/app.module.browser'; // // Enable either Hot Module Reloading or production mode if (module['hot']) { - module['hot'].accept(); - module['hot'].dispose(() => { - modulePromise.then(appModule => appModule.destroy()); - }); + module['hot'].accept(); + module['hot'].dispose(() => { + modulePromise.then(appModule => appModule.destroy()); + }); } else { - enableProdMode(); + enableProdMode(); } const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/ClientApp/boot.server.PRODUCTION.ts b/ClientApp/boot.server.PRODUCTION.ts index 30f8deb0..e3c33ff0 100644 --- a/ClientApp/boot.server.PRODUCTION.ts +++ b/ClientApp/boot.server.PRODUCTION.ts @@ -9,8 +9,7 @@ import { ngAspnetCoreEngine, IEngineOptions, createTransferScript } from '@nguni enableProdMode(); -export default createServerRenderer((params) => { - +export default createServerRenderer(params => { // Platform-server provider configuration const setupOptions: IEngineOptions = { appSelector: '<app-root></app-root>', @@ -23,16 +22,16 @@ export default createServerRenderer((params) => { }; return ngAspnetCoreEngine(setupOptions).then(response => { - // Apply your transferData to response.globals response.globals.transferData = createTransferScript({ - someData: 'Transfer this to the client on the window.TRANSFER_CACHE {} object', + someData: + 'Transfer this to the client on the window.TRANSFER_CACHE {} object', fromDotnet: params.data.thisCameFromDotNET // example of data coming from dotnet, in HomeController }); - return ({ + return { html: response.html, // our <app-root> serialized globals: response.globals // all of our styles/scripts/meta-tags/link-tags for aspnet to serve up - }); + }; }); }); diff --git a/ClientApp/boot.server.ts b/ClientApp/boot.server.ts index c2c0fb47..b6510018 100644 --- a/ClientApp/boot.server.ts +++ b/ClientApp/boot.server.ts @@ -1,11 +1,12 @@ import 'zone.js/dist/zone-node'; import './polyfills/server.polyfills'; + import { enableProdMode } from '@angular/core'; +import { createTransferScript, IEngineOptions, ngAspnetCoreEngine } from '@nguniversal/aspnetcore-engine'; import { createServerRenderer } from 'aspnet-prerendering'; // Grab the (Node) server-specific NgModule import { AppModule } from './app/app.module.server'; -import { ngAspnetCoreEngine, IEngineOptions, createTransferScript } from '@nguniversal/aspnetcore-engine'; enableProdMode(); diff --git a/ClientApp/polyfills/browser.polyfills.ts b/ClientApp/polyfills/browser.polyfills.ts index 1db98ac8..9e6fe1d4 100644 --- a/ClientApp/polyfills/browser.polyfills.ts +++ b/ClientApp/polyfills/browser.polyfills.ts @@ -1,5 +1,8 @@ +// Note: * The order is IMPORTANT! * + import './polyfills.ts'; -import 'zone.js/dist/zone'; import 'reflect-metadata'; +import 'zone.js/dist/zone'; + // import 'web-animations-js'; // Run `npm install --save web-animations-js`. diff --git a/ClientApp/polyfills/polyfills.ts b/ClientApp/polyfills/polyfills.ts index fd43e294..45b62d73 100644 --- a/ClientApp/polyfills/polyfills.ts +++ b/ClientApp/polyfills/polyfills.ts @@ -1,30 +1,29 @@ +// Note: * The order is IMPORTANT! * /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE9, IE10 and IE11 requires all of the following polyfills. **/ - import 'core-js/es6/symbol'; - import 'core-js/es6/object'; - import 'core-js/es6/function'; - import 'core-js/es6/parse-int'; - import 'core-js/es6/parse-float'; - import 'core-js/es6/number'; - import 'core-js/es6/math'; - import 'core-js/es6/string'; - import 'core-js/es6/date'; - import 'core-js/es6/array'; - import 'core-js/es6/regexp'; - import 'core-js/es6/map'; - import 'core-js/es6/set'; +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/set'; - /** */ - import 'reflect-metadata'; +/** */ +import 'reflect-metadata'; /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. - /** Evergreen browsers require these. **/ import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; - +import 'core-js/es7/reflect'; \ No newline at end of file diff --git a/ClientApp/polyfills/server.polyfills.ts b/ClientApp/polyfills/server.polyfills.ts index 7044895f..68867b14 100644 --- a/ClientApp/polyfills/server.polyfills.ts +++ b/ClientApp/polyfills/server.polyfills.ts @@ -1,3 +1,4 @@ -import './polyfills.ts'; +// Note: * The order is IMPORTANT! * +import './polyfills.ts'; import 'zone.js'; diff --git a/ClientApp/test/boot-tests.js b/ClientApp/test/boot-tests.js index 647b8c02..51eeb6d3 100644 --- a/ClientApp/test/boot-tests.js +++ b/ClientApp/test/boot-tests.js @@ -12,13 +12,15 @@ const testing = require('@angular/core/testing'); const testingBrowser = require('@angular/platform-browser-dynamic/testing'); // Prevent Karma from running prematurely -__karma__.loaded = function () {}; +__karma__.loaded = function() {}; // First, initialize the Angular testing environment -testing.getTestBed().initTestEnvironment( +testing + .getTestBed() + .initTestEnvironment( testingBrowser.BrowserDynamicTestingModule, testingBrowser.platformBrowserDynamicTesting() -); + ); // Then we find all the tests const context = require.context('../', true, /\.spec\.ts$/); diff --git a/ClientApp/test/karma.conf.js b/ClientApp/test/karma.conf.js index b54ce8f9..6ab0db4b 100644 --- a/ClientApp/test/karma.conf.js +++ b/ClientApp/test/karma.conf.js @@ -1,15 +1,12 @@ // Karma configuration file, see link for more information // https://karma-runner.github.io/0.13/config/configuration-file.html -module.exports = function (config) { +module.exports = function(config) { config.set({ basePath: '.', frameworks: ['jasmine'], exclude: [], - files: [ - '../../wwwroot/dist/vendor.js', - './boot-tests.js' - ], + files: ['../../wwwroot/dist/vendor.js', './boot-tests.js'], preprocessors: { './boot-tests.js': ['coverage', 'webpack', 'sourcemap'] }, @@ -45,7 +42,7 @@ module.exports = function (config) { }, // you can define custom flags customLaunchers: { - 'PhantomJS_custom': { + PhantomJS_custom: { base: 'PhantomJS', options: { windowName: 'test-window', @@ -53,7 +50,7 @@ module.exports = function (config) { webSecurityEnabled: false } }, - flags: ['--load-images=true'], + flags: ['--load-images=true'] // debug: true } }, diff --git a/ClientApp/test/webpack.config.test.js b/ClientApp/test/webpack.config.test.js index 3a14d96d..5cbcecc5 100644 --- a/ClientApp/test/webpack.config.test.js +++ b/ClientApp/test/webpack.config.test.js @@ -5,7 +5,7 @@ const webpack = require('webpack'); var path = require('path'); var rootPath = path.join.bind(path, path.resolve(__dirname, '../../')); -module.exports = function (options) { +module.exports = function(options) { return { devtool: 'inline-source-map', resolve: { @@ -13,7 +13,8 @@ module.exports = function (options) { modules: [rootPath('ClientApp'), 'node_modules'] }, module: { - rules: [{ + rules: [ + { enforce: 'pre', test: /\.js$/, loader: 'source-map-loader', @@ -24,7 +25,8 @@ module.exports = function (options) { }, { test: /\.ts$/, - use: [{ + use: [ + { loader: 'awesome-typescript-loader', query: { sourceMap: false, @@ -32,7 +34,7 @@ module.exports = function (options) { compilerOptions: { removeComments: true } - }, + } }, 'angular2-template-loader' ], @@ -58,13 +60,8 @@ module.exports = function (options) { esModules: true }, include: rootPath('ClientApp'), - exclude: [ - /ClientApp\\test/, - /\.(e2e|spec)\.ts$/, - /node_modules/ - ] + exclude: [/ClientApp\\test/, /\.(e2e|spec)\.ts$/, /node_modules/] } - ] }, plugins: [ @@ -91,8 +88,7 @@ module.exports = function (options) { * legacy options go here */ } - }), - + }) ], performance: { hints: false @@ -112,6 +108,5 @@ module.exports = function (options) { clearImmediate: false, setImmediate: false } - }; -} +}; diff --git a/ClientApp/tsconfig.app.json b/ClientApp/tsconfig.app.json index 3f094c64..a082833e 100644 --- a/ClientApp/tsconfig.app.json +++ b/ClientApp/tsconfig.app.json @@ -4,11 +4,11 @@ "outDir": "../out-tsc/app", "module": "es2015", "baseUrl": "", - "types": [] + "sourceMap": true, + "types": ["node"] }, "exclude": [ - "test.ts", - "**/*.spec.ts", - "boot.server.PRODUCTION.ts" + "test.ts", + "**/*.spec.ts" ] } diff --git a/ClientApp/tsconfig.spec.json b/ClientApp/tsconfig.spec.json index de4e2a75..c8fc1d84 100644 --- a/ClientApp/tsconfig.spec.json +++ b/ClientApp/tsconfig.spec.json @@ -5,16 +5,8 @@ "module": "commonjs", "target": "es5", "baseUrl": "", - "types": [ - "jasmine", - "node" - ] + "types": ["jasmine", "node"] }, - "files": [ - "polyfills.ts" - ], - "include": [ - "**/*.spec.ts", - "**/*.d.ts" - ] + "files": ["polyfills.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] } diff --git a/Program.cs b/Program.cs new file mode 100644 index 00000000..fdf46454 --- /dev/null +++ b/Program.cs @@ -0,0 +1,47 @@ +using AspCoreServer; +using AspCoreServer.Data; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.IO; +using System.Threading.Tasks; + +public class Program +{ + public static async Task Main(string[] args) + { + var host = BuildWebHost(args); + using (var scope = host.Services.CreateScope()) + { + var services = scope.ServiceProvider; + + try + { + await EnsureDataStorageIsReady(services); + + } catch (Exception ex) + { + var logger = services.GetRequiredService<ILogger<Program>>(); + logger.LogError(ex, "An error occurred while seeding the database."); + } + } + + host.Run(); + } + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup<Startup>() + .Build(); + + private static async Task EnsureDataStorageIsReady(IServiceProvider services) + { + await CoreEFStartup.InitializeDatabaseAsync(services); + await SimpleContentEFStartup.InitializeDatabaseAsync(services); + await LoggingEFStartup.InitializeDatabaseAsync(services); + } +} diff --git a/README.md b/README.md index 8ed5a749..f52bdf4d 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,37 @@ -# ASP.NET Core 2.1 & Angular 6(+) Advanced Starter - PWA & Server-side prerendering (for Angular SEO)! +# ASP.NET Core 2.1 & Angular 7(+) Advanced Starter - PWA & Server-side prerendering (for Angular SEO)! -## By [DevHelp.Online](http://www.DevHelp.Online) - -> Updated to the latest Angular 6.x +--- +<br> <p align="center"> - <img src="http://github.com/docs/architecture.png" alt="ASP.NET Core 2.0 Angular 6+ Starter" title="ASP.NET Core 2.0 Angular 6+ Starter"> + <a href="https://trilon.io/" target="_blank"> + <img width="500" height="auto" src="https://trilon.io/trilon-logo-clear.png" alt="Trilon.io - Angular Universal, NestJS, JavaScript Application Consulting Development and Training"> + </a> </p> -### Harness the power of Angular 6+, ASP.NET Core 2.0, now with SEO ! + +<h3 align="center"> Made with :heart: by <a href="https://trilon.io/">Trilon.io</a></h3> + +--- + +### Harness the power of Angular 7+, ASP.NET Core 2.1, now with SEO ! Angular SEO in action: <p align="center"> - <img src="http://github.com/docs/angular2-seo.png" alt="ASP.NET Core Angular6 SEO" title="ASP.NET Core Angular6 SEO"> + <img src="http://github.com/docs/angular2-seo.png" alt="ASP.NET Core Angular7 SEO" title="ASP.NET Core Angular7 SEO"> +</p> + +### Angular Universal Application Architecture + +<p align="center"> + <img src="http://github.com/docs/architecture.png" alt="ASP.NET Core 2.1 Angular 7+ Starter" title="ASP.NET Core 2.1 Angular 7+ Starter"> </p> ### What is this repo? Live Demo here: http://aspnetcore-angular2-universal.azurewebsites.net -This repository is maintained by [Angular](https://github.com/angular/angular) and is meant to be an advanced starter -for both ASP.NET Core 2.1 using Angular 6.0+, not only for the client-side, but to be rendered on the server for instant +This repository is maintained by [Trilon.io](https://Trilon.io) and the [Angular](https://github.com/angular/angular) Universal team and is meant to be an advanced starter +for both ASP.NET Core 2.1 using Angular 7.0+, not only for the client-side, but to be rendered on the server for instant application paints (Note: If you don't need SSR [read here](#faq) on how to disable it). This is meant to be a Feature-Rich Starter application containing all of the latest technologies, best build systems available, and include many real-world examples and libraries needed in todays Single Page Applications (SPAs). @@ -38,10 +50,10 @@ This utilizes all the latest standards, no gulp, no bower, no typings, no manual * [Upcoming Features](#upcoming-features) * [Application Structure](#application-structure) * [Gotchas](#gotchas) -* [FAQ](#faq---also-check-out-the-faq-issues-label) +* [FAQ](#faq---also-check-out-the-faq-issues-label-and-the-how-to-issues-label) * [Special Thanks](#special-thanks) * [License](#license) -* [Consulting & Training](#looking-for-angular--aspnet-consulting--training--support) +* [Trilon - Consulting & Training](#trilon---angular--aspnet---consulting--training--development) --- @@ -57,7 +69,7 @@ This utilizes all the latest standards, no gulp, no bower, no typings, no manual - Swagger WebAPI documentation when running in development mode - SignalR Chat demo! (Thanks to [@hakonamatata](https://github.com/hakonamatata)) -- **Angular 6.0.0** : +- **Angular 7.0.0** : - PWA (Progressive Web App) - (Minimal) Angular-CLI integration - This is to be used mainly for Generating Components/Services/etc. @@ -304,7 +316,7 @@ Take a look at the `_Layout.cshtml` file for example, notice how we let .NET han <head> <base href="http://github.com/" /> <!-- Title will be the one you set in your Angular application --> - <title>@ViewData["Title"] - AspNET.Core Angular 6.0.0 (+) starter + @ViewData["Title"] - AspNET.Core Angular 7.0.0 (+) starter @@ -345,7 +357,7 @@ Well now, your Client-side Angular will take over, and you'll have a fully funct - This repository uses ASP.Net Core 2.1, which has a hard requirement on .NET Core Runtime 2.1 and .NET Core SDK 2.1. Please install these items from [here](https://blogs.msdn.microsoft.com/dotnet/2018/05/30/announcing-net-core-2-1/?WT.mc_id=blog-twitter-timheuer) -> When building components in Angular 6 there are a few things to keep in mind. +> When building components in Angular 7 there are a few things to keep in mind. - Make sure you provide Absolute URLs when calling any APIs. (The server can't understand relative paths, so `/api/whatever` will fail). @@ -427,7 +439,7 @@ import * as $ from 'jquery'; ### How can I support IE9 through IE11? -To support IE9 through IE11 open the `polyfills.ts` file in the `polyfills` folder and uncomment out the 'import polyfills' as needed. +To support IE9 through IE11 open the `polyfills.ts` file in the `polyfills` folder and uncomment out the 'import polyfills' as needed. ALSO - make sure that your `webpack.config` and `webpack.config.vendor` change option of `TerserPlugin` from `ecma: 6` to **`ecma: 5`**. ---- @@ -458,22 +470,24 @@ Nothing's ever perfect, but please let me know by creating an issue (make sure t [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](/LICENSE) -Copyright (c) 2016-2018 [Mark Pieszak](https://github.com/MarkPieszak) +Copyright (c) 2016-2019 [Mark Pieszak](https://github.com/MarkPieszak) + +[![Twitter Follow](https://img.shields.io/twitter/follow/MarkPieszak.svg?style=social)](https://twitter.com/MarkPieszak) ---- -# DevHelp.Online - Angular & ASP.NET - Consulting | Training | Development +# Trilon - Angular & ASP.NET - Consulting | Training | Development -Check out **[www.DevHelp.Online](http://DevHelp.Online)** for more info! Twitter [@DevHelpOnline](http://www.twitter.com/DevHelpOnline) +Check out **[Trilon.io](https://Trilon.io)** for more info! Twitter [@Trilon_io](http://www.twitter.com/Trilon_io) -Contact us at , and let's talk about your projects needs. +Contact us at , and let's talk about your projects needs.

- DevHelp.Online - Angular ASPNET JavaScript Consulting Development and Training + + Trilon.io - Angular Universal, NestJS, JavaScript Application Consulting Development and Training +

----- - -## Follow me online: +## Follow Trilon online: -Twitter: [@MarkPieszak](http://twitter.com/MarkPieszak) | Medium: [@MarkPieszak](https://medium.com/@MarkPieszak) +Twitter: [@Trilon_io](http://twitter.com/Trilon_io) diff --git a/Server/Controllers/HomeController.cs b/Server/Controllers/HomeController.cs index 399174ef..18c423c6 100644 --- a/Server/Controllers/HomeController.cs +++ b/Server/Controllers/HomeController.cs @@ -1,63 +1,47 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using Asp2017.Server.Helpers; -using Asp2017.Server.Models; +using Asp2017.Server.Helpers; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.NodeServices; -using Microsoft.AspNetCore.SpaServices.Prerendering; -using Microsoft.Extensions.DependencyInjection; - -namespace AspCoreServer.Controllers { - public class HomeController : Controller { - protected readonly IHostingEnvironment HostingEnvironment; - public HomeController (IHostingEnvironment hostingEnv) { - this.HostingEnvironment = hostingEnv; - } - - [HttpGet] - public async Task Index () { - var prerenderResult = await Request.BuildPrerender (); - - ViewData["SpaHtml"] = prerenderResult.Html; // our from Angular - ViewData["Title"] = prerenderResult.Globals["title"]; // set our from Angular - ViewData["Styles"] = prerenderResult.Globals["styles"]; // put styles in the correct place - ViewData["Scripts"] = prerenderResult.Globals["scripts"]; // scripts (that were in our header) - ViewData["Meta"] = prerenderResult.Globals["meta"]; // set our <meta> SEO tags - ViewData["Links"] = prerenderResult.Globals["links"]; // set our <link rel="canonical"> etc SEO tags - ViewData["TransferData"] = prerenderResult.Globals["transferData"]; // our transfer data set to window.TRANSFER_CACHE = {}; - if (!HostingEnvironment.IsDevelopment ()) { - ViewData["ServiceWorker"] = "<script>'serviceWorker'in navigator&&navigator.serviceWorker.register('/serviceworker')</script>"; - } - - return View (); - } - - [HttpGet] - [Route ("sitemap.xml")] - public IActionResult SitemapXml () { - String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; - - xml += "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9/">"; - xml += "<url>"; - xml += "<loc>http://localhost:4251/home</loc>"; - xml += "<lastmod>" + DateTime.Now.ToString ("yyyy-MM-dd") + "</lastmod>"; - xml += "</url>"; - xml += "<url>"; - xml += "<loc>http://localhost:4251/counter</loc>"; - xml += "<lastmod>" + DateTime.Now.ToString ("yyyy-MM-dd") + "</lastmod>"; - xml += "</url>"; - xml += "</urlset>"; - - return Content (xml, "text/xml"); - - } +using System; +using System.Threading.Tasks; - public IActionResult Error () { - return View (); +namespace AspCoreServer.Controllers +{ + public class HomeController : Controller { + protected readonly IHostingEnvironment HostingEnvironment; + public HomeController(IHostingEnvironment hostingEnv) => this.HostingEnvironment = hostingEnv; + + [HttpGet] + public async Task<IActionResult> Index () { + var prerenderResult = await this.Request.BuildPrerender (); + + this.ViewData["SpaHtml"] = prerenderResult.Html; // our <app-root /> from Angular + this.ViewData["Title"] = prerenderResult.Globals["title"]; // set our <title> from Angular + this.ViewData["Styles"] = prerenderResult.Globals["styles"]; // put styles in the correct place + this.ViewData["Scripts"] = prerenderResult.Globals["scripts"]; // scripts (that were in our header) + this.ViewData["Meta"] = prerenderResult.Globals["meta"]; // set our <meta> SEO tags + this.ViewData["Links"] = prerenderResult.Globals["links"]; // set our <link rel="canonical"> etc SEO tags + this.ViewData["TransferData"] = prerenderResult.Globals["transferData"]; // our transfer data set to window.TRANSFER_CACHE = {}; + if (!this.HostingEnvironment.IsDevelopment ()) { + this.ViewData["ServiceWorker"] = "<script>'serviceWorker'in navigator&&navigator.serviceWorker.register('/serviceworker')</script>"; + } + + return View (); + } + + [HttpGet] + [Route("sitemap.xml")] + public IActionResult SitemapXml() => Content($@"<?xml version=""1.0"" encoding=""utf-8""?> + <urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9""> + <url> + <loc>http://localhost:4251/home</loc> + <lastmod>{ DateTime.Now.ToString("yyyy-MM-dd")}</lastmod> + </url> + <url> + <loc>http://localhost:4251/counter</loc> + <lastmod>{DateTime.Now.ToString("yyyy-MM-dd")}</lastmod> + </url> + </urlset>", "text/xml"); + + public IActionResult Error() => View(); } - } -} \ No newline at end of file +} diff --git a/Server/Data/CoreEFStartup.cs b/Server/Data/CoreEFStartup.cs new file mode 100644 index 00000000..d3947ef1 --- /dev/null +++ b/Server/Data/CoreEFStartup.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace AspCoreServer.Data +{ + public static class CoreEFStartup + { + public static async Task InitializeDatabaseAsync(IServiceProvider services) + { + var context = services.GetRequiredService<SpaDbContext>(); + + await context.Database.EnsureCreatedAsync(); + } + + } +} diff --git a/Server/Data/DbInitializer.cs b/Server/Data/DbInitializer.cs deleted file mode 100644 index 45b7498e..00000000 --- a/Server/Data/DbInitializer.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Linq; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using AspCoreServer.Models; -using AspCoreServer; - -namespace AspCoreServer.Data -{ - public static class DbInitializer - { - public static void Initialize(SpaDbContext context) - { - context.Database.EnsureCreated(); - - if (context.User.Any()) - { - return; // DB has been seeded - } - var users = new User[] - { - new User(){Name = "Mark Pieszak"}, - new User(){Name = "Abrar Jahin"}, - new User(){Name = "hakonamatata"}, - new User(){Name = "LiverpoolOwen"}, - new User(){Name = "Ketrex"}, - new User(){Name = "markwhitfeld"}, - new User(){Name = "daveo1001"}, - new User(){Name = "paonath"}, - new User(){Name = "nalex095"}, - new User(){Name = "ORuban"}, - new User(){Name = "Gaulomatic"}, - new User(){Name = "GRIMMR3AP3R"} - }; - - foreach (User s in users) - { - context.User.Add(s); - } - context.SaveChanges(); - } - } -} diff --git a/Server/Data/LoggingEFStartup.cs b/Server/Data/LoggingEFStartup.cs new file mode 100644 index 00000000..edf33498 --- /dev/null +++ b/Server/Data/LoggingEFStartup.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; + +namespace AspCoreServer.Data +{ + public static class LoggingEFStartup + { + public static async Task InitializeDatabaseAsync(IServiceProvider services) + { + //Implent to your hearts' content + } + } +} diff --git a/Server/Data/SimpleContentEFStartup.cs b/Server/Data/SimpleContentEFStartup.cs new file mode 100644 index 00000000..db160dee --- /dev/null +++ b/Server/Data/SimpleContentEFStartup.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using AspCoreServer.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace AspCoreServer.Data +{ + public static class SimpleContentEFStartup + { + public static async Task InitializeDatabaseAsync(IServiceProvider services) + { + var context = services.GetRequiredService<SpaDbContext>(); + + + if (await context.User.AnyAsync()) + { + return; // DB has been seeded + } + var users = new User[] { + new User () { Name = "Mark Pieszak" }, + new User () { Name = "Abrar Jahin" }, + new User () { Name = "hakonamatata" }, + new User () { Name = "LiverpoolOwen" }, + new User () { Name = "Ketrex" }, + new User () { Name = "markwhitfeld" }, + new User () { Name = "daveo1001" }, + new User () { Name = "paonath" }, + new User () { Name = "nalex095" }, + new User () { Name = "ORuban" }, + new User () { Name = "Gaulomatic" }, + new User () { Name = "GRIMMR3AP3R" } + }; + await context.User.AddRangeAsync(users); + + await context.SaveChangesAsync(); + } + } +} diff --git a/Server/Data/SpaDbContext.cs b/Server/Data/SpaDbContext.cs index e6fee5f7..0f90c230 100644 --- a/Server/Data/SpaDbContext.cs +++ b/Server/Data/SpaDbContext.cs @@ -1,14 +1,10 @@ using AspCoreServer.Models; using Microsoft.EntityFrameworkCore; -namespace AspCoreServer.Data -{ - public class SpaDbContext : DbContext - { - public SpaDbContext(DbContextOptions<SpaDbContext> options) - : base(options) - { - Database.EnsureCreated(); +namespace AspCoreServer.Data { + public class SpaDbContext : DbContext { + public SpaDbContext (DbContextOptions<SpaDbContext> options) : base (options) { + Database.EnsureCreated (); } //List of DB Models - Add your DB models here diff --git a/Server/Helpers/HttpRequestExtensions.cs b/Server/Helpers/HttpRequestExtensions.cs index 12648b60..81c613b9 100644 --- a/Server/Helpers/HttpRequestExtensions.cs +++ b/Server/Helpers/HttpRequestExtensions.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Asp2017.Server.Models; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -5,61 +9,35 @@ using Microsoft.AspNetCore.NodeServices; using Microsoft.AspNetCore.SpaServices.Prerendering; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Asp2017.Server.Helpers -{ - public static class HttpRequestExtensions - { - public static IRequest AbstractRequestInfo(this HttpRequest request) - { - - IRequest requestSimplified = new IRequest(); - requestSimplified.cookies = request.Cookies; - requestSimplified.headers = request.Headers; - requestSimplified.host = request.Host; - - return requestSimplified; - } - - public static async Task<RenderToStringResult> BuildPrerender(this HttpRequest Request) - { - var nodeServices = Request.HttpContext.RequestServices.GetRequiredService<INodeServices>(); - var hostEnv = Request.HttpContext.RequestServices.GetRequiredService<IHostingEnvironment>(); - - var applicationBasePath = hostEnv.ContentRootPath; - var requestFeature = Request.HttpContext.Features.Get<IHttpRequestFeature>(); - var unencodedPathAndQuery = requestFeature.RawTarget; - var unencodedAbsoluteUrl = $"{Request.Scheme}://{Request.Host}{unencodedPathAndQuery}"; - - // ** TransferData concept ** - // Here we can pass any Custom Data we want ! - - // By default we're passing down Cookies, Headers, Host from the Request object here - TransferData transferData = new TransferData(); - transferData.request = Request.AbstractRequestInfo(); - transferData.thisCameFromDotNET = "Hi Angular it's asp.net :)"; - // Add more customData here, add it to the TransferData class - - //Prerender now needs CancellationToken - System.Threading.CancellationTokenSource cancelSource = new System.Threading.CancellationTokenSource(); - System.Threading.CancellationToken cancelToken = cancelSource.Token; - // Prerender / Serialize application (with Universal) - return await Prerenderer.RenderToString( +namespace Asp2017.Server.Helpers { + public static class HttpRequestExtensions { + public static IRequest AbstractRequestInfo(this HttpRequest request) => new IRequest() + { + cookies = request.Cookies, + headers = request.Headers, + host = request.Host + }; + + public static async Task<RenderToStringResult> BuildPrerender(this HttpRequest request) => + // Prerender / Serialize application (with Universal) + await Prerenderer.RenderToString( "/", - nodeServices, - cancelToken, - new JavaScriptModuleExport(applicationBasePath + "/ClientApp/dist/main-server"), - unencodedAbsoluteUrl, - unencodedPathAndQuery, - transferData, // Our simplified Request object & any other CustommData you want to send! + request.HttpContext.RequestServices.GetRequiredService<INodeServices>(), + new System.Threading.CancellationTokenSource().Token, + new JavaScriptModuleExport(request.HttpContext.RequestServices.GetRequiredService<IHostingEnvironment>().ContentRootPath + "/ClientApp/dist/main-server"), + $"{request.Scheme}://{request.Host}{request.HttpContext.Features.Get<IHttpRequestFeature>().RawTarget}", + request.HttpContext.Features.Get<IHttpRequestFeature>().RawTarget, + // ** TransferData concept ** + // Here we can pass any Custom Data we want ! + // By default we're passing down Cookies, Headers, Host from the Request object here + new TransferData + { + request = request.AbstractRequestInfo(), + thisCameFromDotNET = "Hi Angular it's asp.net :)" + }, // Our simplified Request object & any other CustommData you want to send! 30000, - Request.PathBase.ToString() + request.PathBase.ToString() ); } - } } diff --git a/Server/Models/IRequest.cs b/Server/Models/IRequest.cs index 407cf571..295c88fb 100644 --- a/Server/Models/IRequest.cs +++ b/Server/Models/IRequest.cs @@ -3,12 +3,10 @@ using System.Linq; using System.Threading.Tasks; -namespace Asp2017.Server.Models -{ - public class IRequest - { - public object cookies { get; set; } - public object headers { get; set; } - public object host { get; set; } - } +namespace Asp2017.Server.Models { + public class IRequest { + public object cookies { get; set; } + public object headers { get; set; } + public object host { get; set; } + } } diff --git a/Server/Models/TransferData.cs b/Server/Models/TransferData.cs index a89ab38d..c144d2df 100644 --- a/Server/Models/TransferData.cs +++ b/Server/Models/TransferData.cs @@ -3,13 +3,11 @@ using System.Linq; using System.Threading.Tasks; -namespace Asp2017.Server.Models -{ - public class TransferData - { - public dynamic request { get; set; } +namespace Asp2017.Server.Models { + public class TransferData { + public dynamic request { get; set; } - // Your data here ? - public object thisCameFromDotNET { get; set; } - } + // Your data here ? + public object thisCameFromDotNET { get; set; } + } } diff --git a/Server/Models/User.cs b/Server/Models/User.cs index fb3d74da..56e388bf 100644 --- a/Server/Models/User.cs +++ b/Server/Models/User.cs @@ -1,20 +1,17 @@ using System; using System.ComponentModel.DataAnnotations; -namespace AspCoreServer.Models -{ - public class User - { +namespace AspCoreServer.Models { + public class User { public int ID { get; set; } public string Name { get; set; } - [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] - [DataType(DataType.Date)] + [DisplayFormat (DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] + [DataType (DataType.Date)] public DateTime EntryTime { get; set; } //Setting Default value - public User() - { + public User () { EntryTime = DateTime.Now; } } diff --git a/Server/RestAPI/UsersController.cs b/Server/RestAPI/UsersController.cs index 3a7fe3d2..a23301e0 100644 --- a/Server/RestAPI/UsersController.cs +++ b/Server/RestAPI/UsersController.cs @@ -1,123 +1,95 @@ +using System; +using System.Linq; +using System.Threading.Tasks; using AspCoreServer.Data; using AspCoreServer.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using System; -using System.Linq; -using System.Threading.Tasks; -namespace AspCoreServer.Controllers -{ - [Route("api/[controller]")] - public class UsersController : Controller - { - private readonly SpaDbContext _context; +namespace AspCoreServer.Controllers { + [Route ("api/[controller]")] + public class UsersController : Controller { + private readonly SpaDbContext context; - public UsersController(SpaDbContext context) - { - _context = context; - } + public UsersController(SpaDbContext context) => this.context = context; - [HttpGet] - public async Task<IActionResult> Get(int currentPageNo = 1, int pageSize = 20) - { - var users = await _context.User - .OrderByDescending(u => u.EntryTime) - .Skip((currentPageNo - 1) * pageSize) - .Take(pageSize) - .ToArrayAsync(); + [HttpGet] + public async Task<IActionResult> Get (int currentPageNo = 1, int pageSize = 20) { + var users = await this.context.User + .OrderByDescending (u => u.EntryTime) + .Skip ((currentPageNo - 1) * pageSize) + .Take (pageSize) + .ToArrayAsync (); - if (!users.Any()) - { - return NotFound("Users not Found"); - } - else - { - return Ok(users); - } - } + if (!users.Any ()) { + return NotFound ("Users not Found"); + } else { + return Ok (users); + } + } - [HttpGet("{id}")] - public async Task<IActionResult> Get(int id) - { - var user = await _context.User - .Where(u => u.ID == id) - .AsNoTracking() - .SingleOrDefaultAsync(m => m.ID == id); + [HttpGet ("{id}")] + public async Task<IActionResult> Get (int id) { + var user = await this.context.User + .Where (u => u.ID == id) + .AsNoTracking () + .SingleOrDefaultAsync (m => m.ID == id); - if (user == null) - { - return NotFound("User not Found"); - } - else - { - return Ok(user); - } - } + if (user == null) { + return NotFound ("User not Found"); + } else { + return Ok (user); + } + } - [HttpPost] - public async Task<IActionResult> Post([FromBody]User user) - { - if (!string.IsNullOrEmpty(user.Name)) - { - _context.Add(user); - await _context.SaveChangesAsync(); - return CreatedAtAction("Post", user); - } - else - { - return BadRequest("User's name was not given"); - } - } + [HttpPost] + public async Task<IActionResult> Post ([FromBody] User user) { + if (!string.IsNullOrEmpty (user.Name)) { + this.context.Add (user); + await this.context.SaveChangesAsync (); + return CreatedAtAction ("Post", user); + } else { + return BadRequest ("User's name was not given"); + } + } - [HttpPut("{id}")] - public async Task<IActionResult> Put(int id, [FromBody]User userUpdateValue) - { - try - { - userUpdateValue.EntryTime = DateTime.Now; + [HttpPut ("{id}")] + public async Task<IActionResult> Put (int id, [FromBody] User userUpdateValue) { + try { + userUpdateValue.EntryTime = DateTime.Now; - var userToEdit = await _context.User - .AsNoTracking() - .SingleOrDefaultAsync(m => m.ID == id); + var userToEdit = await context.User + .AsNoTracking () + .SingleOrDefaultAsync (m => m.ID == id); - if (userToEdit == null) - { - return NotFound("Could not update user as it was not Found"); + if (userToEdit == null) { + return NotFound ("Could not update user as it was not Found"); + } else { + this.context.Update (userUpdateValue); + await this.context.SaveChangesAsync (); + return Json ("Updated user - " + userUpdateValue.Name); + } + } catch (DbUpdateException) { + //Log the error (uncomment ex variable name and write a log.) + this.ModelState.AddModelError ("", "Unable to save changes. " + + "Try again, and if the problem persists, " + + "see your system administrator."); + return NotFound ("User not Found"); + } } - else - { - _context.Update(userUpdateValue); - await _context.SaveChangesAsync(); - return Json("Updated user - " + userUpdateValue.Name); - } - } - catch (DbUpdateException) - { - //Log the error (uncomment ex variable name and write a log.) - ModelState.AddModelError("", "Unable to save changes. " + - "Try again, and if the problem persists, " + - "see your system administrator."); - return NotFound("User not Found"); - } - } - [HttpDelete("{id}")] - public async Task<IActionResult> Delete(int id) - { - var userToRemove = await _context.User - .AsNoTracking() - .SingleOrDefaultAsync(m => m.ID == id); - if (userToRemove == null) - { - return NotFound("Could not delete user as it was not Found"); - } - else - { - _context.User.Remove(userToRemove); - await _context.SaveChangesAsync(); - return Json("Deleted user - " + userToRemove.Name); - } + [HttpDelete ("{id}")] + public async Task<IActionResult> Delete (int id) { + var userToRemove = await this.context.User + .AsNoTracking () + .SingleOrDefaultAsync (m => m.ID == id); + if (userToRemove == null) { + return NotFound ("Could not delete user as it was not Found"); + } else { + this.context.User.Remove (userToRemove); + await this.context.SaveChangesAsync (); + return Json ("Deleted user - " + userToRemove.Name); + } + } } - } } diff --git a/Startup.cs b/Startup.cs index bc77beb6..8d978fd4 100644 --- a/Startup.cs +++ b/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using AspCoreServer.Data; using Microsoft.AspNetCore.Builder; @@ -14,112 +14,96 @@ using WebEssentials.AspNetCore.Pwa; namespace AspCoreServer { - public class Startup { - - public static void Main (string[] args) { - var host = new WebHostBuilder () - .UseKestrel () - .UseContentRoot (Directory.GetCurrentDirectory ()) - .UseIISIntegration () - .UseStartup<Startup> () - .Build (); - - host.Run (); - } - public Startup (IHostingEnvironment env) { - var builder = new ConfigurationBuilder () - .SetBasePath (env.ContentRootPath) - .AddJsonFile ("appsettings.json", optional : true, reloadOnChange : true) - .AddJsonFile ($"appsettings.{env.EnvironmentName}.json", optional : true) - .AddEnvironmentVariables (); - Configuration = builder.Build (); - } - - public IConfigurationRoot Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices (IServiceCollection services) { - // Add framework services. - services.AddMvc (); - services.AddNodeServices (); - services.AddHttpContextAccessor (); - services.AddProgressiveWebApp (new PwaOptions { Strategy = ServiceWorkerStrategy.CacheFirst, RegisterServiceWorker = true, RegisterWebmanifest = true }, "manifest.json"); - - var connectionStringBuilder = new Microsoft.Data.Sqlite.SqliteConnectionStringBuilder { DataSource = "spa.db" }; - var connectionString = connectionStringBuilder.ToString (); - - services.AddDbContext<SpaDbContext> (options => - options.UseSqlite (connectionString)); - - // Register the Swagger generator, defining one or more Swagger documents - services.AddSwaggerGen (c => { - c.SwaggerDoc ("v1", new Info { Title = "Angular 6.0 Universal & ASP.NET Core advanced starter-kit web API", Version = "v1" }); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, SpaDbContext context) { - loggerFactory.AddConsole (Configuration.GetSection ("Logging")); - loggerFactory.AddDebug (); - - // app.UseStaticFiles(); - - app.UseStaticFiles (new StaticFileOptions () { - OnPrepareResponse = c => { - //Do not add cache to json files. We need to have new versions when we add new translations. - - if (!c.Context.Request.Path.Value.Contains (".json")) { - c.Context.Response.GetTypedHeaders ().CacheControl = new CacheControlHeaderValue () { - MaxAge = TimeSpan.FromDays (30) // Cache everything except json for 30 days - }; - } else { - c.Context.Response.GetTypedHeaders ().CacheControl = new CacheControlHeaderValue () { - MaxAge = TimeSpan.FromMinutes (15) // Cache json for 15 minutes - }; - } + public class Startup { + public Startup (IHostingEnvironment env) { + var builder = new ConfigurationBuilder () + .SetBasePath (env.ContentRootPath) + .AddJsonFile ("appsettings.json", optional : true, reloadOnChange : true) + .AddJsonFile ($"appsettings.{env.EnvironmentName}.json", optional : true) + .AddEnvironmentVariables (); + this.Configuration = builder.Build (); } - }); - - DbInitializer.Initialize (context); - if (env.IsDevelopment ()) { - app.UseDeveloperExceptionPage (); - app.UseWebpackDevMiddleware (new WebpackDevMiddlewareOptions { - HotModuleReplacement = true, - HotModuleReplacementEndpoint = "/dist/" - }); - app.UseSwagger (); - app.UseSwaggerUI (c => { - c.SwaggerEndpoint ("/swagger/v1/swagger.json", "My API V1"); - }); + public IConfigurationRoot Configuration { get; } - // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint. + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices (IServiceCollection services) { + // Add framework services. + services.AddMvc (); + services.AddNodeServices (); + services.AddHttpContextAccessor (); + services.AddProgressiveWebApp (new PwaOptions { Strategy = ServiceWorkerStrategy.CacheFirst, RegisterServiceWorker = true, RegisterWebmanifest = true }, "manifest.json"); - app.MapWhen (x => !x.Request.Path.Value.StartsWith ("/swagger", StringComparison.OrdinalIgnoreCase), builder => { - builder.UseMvc (routes => { - routes.MapSpaFallbackRoute ( - name: "spa-fallback", - defaults : new { controller = "Home", action = "Index" }); - }); - }); - } else { - app.UseMvc (routes => { - routes.MapRoute ( - name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); + var connectionStringBuilder = new Microsoft.Data.Sqlite.SqliteConnectionStringBuilder { DataSource = "spa.db" }; + var connectionString = connectionStringBuilder.ToString(); - routes.MapRoute ( - "Sitemap", - "sitemap.xml", - new { controller = "Home", action = "SitemapXml" }); + services.AddDbContext<SpaDbContext>(options => + options.UseSqlite(connectionString)); - routes.MapSpaFallbackRoute ( - name: "spa-fallback", - defaults : new { controller = "Home", action = "Index" }); + // Register the Swagger generator, defining one or more Swagger documents + services.AddSwaggerGen (c => { + c.SwaggerDoc ("v1", new Info { Title = "Angular 7.0 Universal & ASP.NET Core advanced starter-kit web API", Version = "v1" }); + }); + } - }); - app.UseExceptionHandler ("/Home/Error"); - } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, SpaDbContext context) { + loggerFactory.AddConsole (this.Configuration.GetSection ("Logging")); + loggerFactory.AddDebug (); + + // app.UseStaticFiles(); + + app.UseStaticFiles (new StaticFileOptions () { + OnPrepareResponse = c => { + //Do not add cache to json files. We need to have new versions when we add new translations. + c.Context.Response.GetTypedHeaders ().CacheControl = !c.Context.Request.Path.Value.Contains (".json") + ? new CacheControlHeaderValue () { + MaxAge = TimeSpan.FromDays (30) // Cache everything except json for 30 days + } + : new CacheControlHeaderValue () { + MaxAge = TimeSpan.FromMinutes (15) // Cache json for 15 minutes + }; + } + }); + + if (env.IsDevelopment ()) { + app.UseDeveloperExceptionPage (); + app.UseWebpackDevMiddleware (new WebpackDevMiddlewareOptions { + HotModuleReplacement = true, + HotModuleReplacementEndpoint = "/dist/" + }); + app.UseSwagger (); + app.UseSwaggerUI (c => { + c.SwaggerEndpoint ("/swagger/v1/swagger.json", "My API V1"); + }); + + // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint. + + app.MapWhen (x => !x.Request.Path.Value.StartsWith ("/swagger", StringComparison.OrdinalIgnoreCase), builder => { + builder.UseMvc (routes => { + routes.MapSpaFallbackRoute ( + name: "spa-fallback", + defaults : new { controller = "Home", action = "Index" }); + }); + }); + } else { + app.UseMvc (routes => { + routes.MapRoute ( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + + routes.MapRoute ( + "Sitemap", + "sitemap.xml", + new { controller = "Home", action = "SitemapXml" }); + + routes.MapSpaFallbackRoute ( + name: "spa-fallback", + defaults : new { controller = "Home", action = "Index" }); + + }); + app.UseExceptionHandler ("/Home/Error"); + } + } } - } -} \ No newline at end of file +} diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index b8f7a97f..d8882a29 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -9,4 +9,5 @@ <!-- Our webpack bundle --> <script src="http://github.com/~/dist/main-client.js" asp-append-version="true"></script> } + @Html.Raw(ViewData["ServiceWorker"]) diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index eeebe090..e749bc3b 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -1,10 +1,10 @@ <!DOCTYPE html> <html> <head> + <meta charset="utf-8" /> <base href="http://github.com/@(Url.Content("~/"))" /> <title>@ViewData["Title"] - - + @Html.Raw(ViewData["Meta"]) @Html.Raw(ViewData["Links"]) diff --git a/angular.json b/angular.json index 42179045..b6c5377c 100644 --- a/angular.json +++ b/angular.json @@ -5,28 +5,52 @@ "projects": { "AspnetCore-Angular-Universal": { "root": "", - "sourceRoot": "src", + "sourceRoot": "ClientApp", "projectType": "application", + "prefix": "app", + "schematics": {}, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/", - "index": "ClientApp/index.html", - "main": "ClientApp/main.ts", + "outputPath": "wwwroot/dist", + "main": "ClientApp/boot-browser.ts", + "polyfills": "ClientApp/polyfills/polyfills.ts", "tsConfig": "ClientApp/tsconfig.app.json", "assets": [], "styles": [], "scripts": [] }, - "configurations": {} + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "ClientApp/environments/environment.ts", + "with": "ClientApp/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "AspnetCore-Angular-Universal:build" }, - "configurations": {} + "configurations": { + "production": { + "browserTarget": "AspnetCore-Angular-Universal:build:production" + } + } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", @@ -34,6 +58,18 @@ "browserTarget": "AspnetCore-Angular-Universal:build" } }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "ClientApp/test.ts", + "polyfills": "ClientApp/polyfills.ts", + "tsConfig": "ClientApp/tsconfig.spec.json", + "karmaConfig": "ClientApp/karma.conf.js", + "styles": ["ClientApp/styles.css"], + "scripts": [], + "assets": ["ClientApp/favicon.ico", "ClientApp/assets"] + } + }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { @@ -41,23 +77,31 @@ "ClientApp/tsconfig.app.json", "ClientApp/tsconfig.spec.json" ], - "exclude": [] + "exclude": ["**/node_modules/**"] } } } }, "AspnetCore-Angular-Universal-e2e": { - "root": "", - "sourceRoot": "", - "projectType": "application" + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "AspnetCore-Angular-Universal:serve" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": ["**/node_modules/**"] + } + } + } } }, - "defaultProject": "AspnetCore-Angular-Universal", - "schematics": { - "@schematics/angular:component": { - "spec": false, - "styleext": "scss" - }, - "@schematics/angular:directive": {} - } -} \ No newline at end of file + "defaultProject": "AspnetCore-Angular-Universal" +} diff --git a/package.json b/package.json index a7fcc1ca..01bc52bc 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,13 @@ { - "name": "angular4-aspnetcore-universal", - "version": "1.0.0-rc3", + "name": "angular7-aspnetcore-universal", + "author": { + "name": "Mark Pieszak | Trilon Consulting", + "email": "hello@trilon.io", + "url": "https://trilon.io/" + }, + "version": "1.0.0-rc4", "scripts": { - "clean:install": "npm run clean && rimraf ./node_modules ./bin ./obj ./package-lock.json && dotnet restore && npm i", + "clean:install": "npm run clean && rimraf ./node_modules ./bin ./obj ./package-lock.json && dotnet restore Asp2017.csproj && npm i", "lint": "tslint -p tsconfig.json", "test": "npm run build:vendor && karma start ClientApp/test/karma.conf.js", "test:watch": "npm run test -- --auto-watch --no-single-run", @@ -17,80 +22,84 @@ "clean": "rimraf wwwroot/dist clientapp/dist" }, "dependencies": { - "@angular/animations": "^6.0.3", - "@angular/common": "^6.0.3", - "@angular/compiler": "^6.0.3", - "@angular/core": "^6.0.3", - "@angular/forms": "^6.0.3", - "@angular/http": "^6.0.3", - "@angular/platform-browser": "^6.0.3", - "@angular/platform-browser-dynamic": "^6.0.3", - "@angular/platform-server": "^6.0.3", - "@angular/router": "^6.0.3", - "@nguniversal/aspnetcore-engine": "^6.0.0", - "@nguniversal/common": "^6.0.0", - "@ngx-translate/core": "^10.0.2", - "@ngx-translate/http-loader": "^3.0.1", - "@types/node": "^10.1.2", + "@angular/animations": "~7.2.0", + "@angular/common": "~7.2.0", + "@angular/compiler": "~7.2.0", + "@angular/core": "~7.2.0", + "@angular/forms": "~7.2.0", + "@angular/http": "~7.2.0", + "@angular/platform-browser": "~7.2.0", + "@angular/platform-browser-dynamic": "~7.2.0", + "@angular/platform-server": "~7.2.0", + "@angular/router": "~7.2.0", + "@nguniversal/aspnetcore-engine": "^7.1.0", + "@nguniversal/common": "^7.1.0", + "@ngx-translate/core": "^11.0.1", + "@ngx-translate/http-loader": "^4.0.0", + "@types/node": "^11.9.5", "angular2-router-loader": "^0.3.5", "angular2-template-loader": "^0.6.2", "aspnet-prerendering": "^3.0.1", - "aspnet-webpack": "^2.0.3", - "awesome-typescript-loader": "^5.0.0", - "bootstrap": "^4.1.1", - "core-js": "^2.5.6", - "css": "^2.2.3", - "css-loader": "^0.28.11", - "event-source-polyfill": "^0.0.12", + "aspnet-webpack": "^3.0.0", + "awesome-typescript-loader": "^5.2.1", + "bootstrap": "^4.3.1", + "core-js": "^2.6.5", + "css": "^2.2.4", + "css-loader": "^2.1.0", + "event-source-polyfill": "^1.0.5", "expose-loader": "^0.7.5", - "file-loader": "^1.1.11", + "file-loader": "^3.0.1", "html-loader": "^0.5.5", "isomorphic-fetch": "^2.2.1", "jquery": "^3.3.1", "json-loader": "^0.5.7", - "moment": "2.22.1", - "ngx-bootstrap": "^3.0.0", - "node-sass": "^4.9.0", - "npm": "^6.1.0", - "preboot": "^6.0.0-beta.4", - "raw-loader": "^0.5.1", - "rimraf": "^2.6.2", - "rxjs": "^6.2.0", - "sass-loader": "^7.0.1", - "style-loader": "^0.21.0", + "moment": "^2.24.0", + "ngx-bootstrap": "^3.2.0", + "node-sass": "^4.11.0", + "preboot": "^7.0.0", + "raw-loader": "^1.0.0", + "rimraf": "^2.6.3", + "rxjs": "6.2.2", + "sass-loader": "^7.1.0", + "style-loader": "^0.23.1", "to-string-loader": "^1.1.5", - "typescript": "~2.7.2", - "url-loader": "^1.0.1", - "webpack": "^4.9.1", - "webpack-hot-middleware": "^2.22.2", - "webpack-merge": "^4.1.2", - "zone.js": "^0.8.26" + "url-loader": "^1.1.2", + "webpack": "^4.29.5", + "webpack-hot-middleware": "^2.24.3", + "webpack-merge": "^4.2.1", + "zone.js": "^0.8.29" }, "devDependencies": { - "@angular-devkit/build-angular": "~0.6.6", - "@angular/cli": "^6.0.5", - "@angular/compiler-cli": "6.0.3", - "@ngtools/webpack": "^6.0.5", - "@types/chai": "^4.1.3", - "@types/jasmine": "^2.8.7", - "chai": "^4.1.2", - "codelyzer": "^3.1.2", + "@angular-devkit/build-angular": "~0.13.3", + "@angular/cli": "~7.3.3", + "@angular/compiler-cli": "~7.2.0", + "@ngtools/webpack": "~7.3.3", + "@types/jasmine": "~2.8.8", + "codelyzer": "~4.5.0", "istanbul-instrumenter-loader": "^3.0.1", - "jasmine-core": "^2.5.2", - "karma": "^1.7.1", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-coverage": "^1.1.1", - "karma-jasmine": "^1.1.2", + "jasmine-core": "^3.3.0", + "jasmine-spec-reporter": "^4.2.1", + "karma": "~4.0.0", + "karma-chrome-launcher": "~2.2.0", + "karma-coverage": "~1.1.2", + "karma-jasmine": "~2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-phantomjs-launcher": "^1.0.4", "karma-remap-coverage": "^0.1.5", "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^2.0.4", - "mini-css-extract-plugin": "^0.4.0", - "tslint": "^5.10.0", - "uglifyjs-webpack-plugin": "^1.2.5", - "webpack-bundle-analyzer": "^2.13.1", - "webpack-cli": "^2.1.4" - } + "karma-webpack": "^3.0.5", + "mini-css-extract-plugin": "^0.5.0", + "terser-webpack-plugin": "^1.2.3", + "tslint": "~5.11.0", + "typescript": "~3.2.2", + "uglifyjs-webpack-plugin": "^2.1.2", + "webpack-bundle-analyzer": "^3.0.4", + "webpack-cli": "^3.2.3" + }, + "license": "MIT", + "repository": { + "type": "github", + "url": "https://github.com/MarkPieszak/aspnetcore-angular2-universal" + }, + "readme": "https://github.com/MarkPieszak/aspnetcore-angular2-universal/blob/master/README.md" } diff --git a/webpack.additions.js b/webpack.additions.js index 62bf4bb3..9c63a2f8 100644 --- a/webpack.additions.js +++ b/webpack.additions.js @@ -7,12 +7,14 @@ // Shared rules[] we need to add const sharedModuleRules = [ // sass - { test: /\.scss$/, loaders: ['to-string-loader', 'css-loader', 'sass-loader'] }, + { + test: /\.scss$/, + loaders: ['to-string-loader', 'css-loader', 'sass-loader'] + }, // font-awesome { test: /\.(woff2?|ttf|eot|svg)$/, loader: 'url-loader?limit=10000' } ]; - module.exports = { sharedModuleRules }; diff --git a/webpack.config.js b/webpack.config.js index a7984c89..249c4f59 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,18 +13,17 @@ const webpack = require('webpack'); const merge = require('webpack-merge'); const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin; const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin; -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') + .BundleAnalyzerPlugin; +const TerserPlugin = require('terser-webpack-plugin'); -const { - sharedModuleRules -} = require('./webpack.additions'); +const { sharedModuleRules } = require('./webpack.additions'); -module.exports = (env) => { +module.exports = env => { // Configuration in common to both client-side and server-side bundles const isDevBuild = !(env && env.prod); const sharedConfig = { - mode: isDevBuild ? "development" : "production", + mode: isDevBuild ? 'development' : 'production', stats: { modules: false }, @@ -37,9 +36,16 @@ module.exports = (env) => { publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix }, module: { - rules: [{ - test: /\.ts$/, - use: isDevBuild ? ['awesome-typescript-loader?silent=true', 'angular2-template-loader', 'angular2-router-loader'] : '@ngtools/webpack' + rules: [ + { + test: /^(?!.*\.spec\.ts$).*\.ts$/, + use: isDevBuild + ? [ + 'awesome-typescript-loader?silent=true', + 'angular2-template-loader', + 'angular2-router-loader' + ] + : '@ngtools/webpack' }, { test: /\.html$/, @@ -47,7 +53,10 @@ module.exports = (env) => { }, { test: /\.css$/, - use: ['to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize'] + use: [ + 'to-string-loader', + isDevBuild ? 'css-loader' : 'css-loader?minimize' + ] }, { test: /\.(png|jpg|jpeg|gif|svg)$/, @@ -73,42 +82,55 @@ module.exports = (env) => { context: __dirname, manifest: require('./wwwroot/dist/vendor-manifest.json') }) - ].concat(isDevBuild ? [ - // Plugins that apply in development builds only - new webpack.SourceMapDevToolPlugin({ - filename: '[file].map', // Remove this line if you prefer inline source maps - moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk - }) - ] : [ - // new BundleAnalyzerPlugin(), - // Plugins that apply in production builds only - new AngularCompilerPlugin({ - mainPath: path.join(__dirname, 'ClientApp/boot.browser.ts'), - tsConfigPath: './tsconfig.json', - entryModule: path.join(__dirname, 'ClientApp/app/app.module.browser#AppModule'), - exclude: ['./**/*.server.ts'] - }) - ]), + ].concat( + isDevBuild + ? [ + // Plugins that apply in development builds only + new webpack.SourceMapDevToolPlugin({ + filename: '[file].map', // Remove this line if you prefer inline source maps + moduleFilenameTemplate: path.relative( + clientBundleOutputDir, + '[resourcePath]' + ) // Point sourcemap entries to the original file locations on disk + }) + ] + : [ + // new BundleAnalyzerPlugin(), + // Plugins that apply in production builds only + new AngularCompilerPlugin({ + mainPath: path.join(__dirname, 'ClientApp/boot.browser.ts'), + tsConfigPath: './ClientApp/tsconfig.app.json', + entryModule: path.join( + __dirname, + 'ClientApp/app/app.module.browser#AppModule' + ), + exclude: ['./**/*.server.ts'], + sourceMap: isDevBuild + }) + ] + ), devtool: isDevBuild ? 'cheap-eval-source-map' : false, node: { - fs: "empty" + fs: 'empty' }, optimization: { - minimizer: [].concat(isDevBuild ? [] : [ - // we specify a custom UglifyJsPlugin here to get source maps in production - new UglifyJsPlugin({ - cache: true, - parallel: true, - uglifyOptions: { - compress: false, - ecma: 6, - mangle: true, - keep_classnames: true, - keep_fnames: true - }, - sourceMap: true - }) - ]) + minimizer: [].concat( + isDevBuild + ? [] + : [ + // we specify a custom TerserPlugin here to get source maps in production + new TerserPlugin({ + sourceMap: true, + terserOptions: { + compress: true, + ecma: 6, + mangle: true, + keep_classnames: true, + keep_fnames: true, + }, + }), + ] + ) } }); @@ -116,7 +138,9 @@ module.exports = (env) => { const serverBundleConfig = merge(sharedConfig, { // resolve: { mainFields: ['main'] }, entry: { - 'main-server': isDevBuild ? './ClientApp/boot.server.ts' : './ClientApp/boot.server.PRODUCTION.ts' + 'main-server': isDevBuild + ? './ClientApp/boot.server.ts' + : './ClientApp/boot.server.PRODUCTION.ts' }, plugins: [ new webpack.DllReferencePlugin({ @@ -125,27 +149,39 @@ module.exports = (env) => { sourceType: 'commonjs2', name: './vendor' }) - ].concat(isDevBuild ? [ - new webpack.ContextReplacementPlugin( - // fixes WARNING Critical dependency: the request of a dependency is an expression - /(.+)?angular(\\|\/)core(.+)?/, - path.join(__dirname, 'src'), // location of your src - {} // a map of your routes - ), - new webpack.ContextReplacementPlugin( - // fixes WARNING Critical dependency: the request of a dependency is an expression - /(.+)?express(\\|\/)(.+)?/, - path.join(__dirname, 'src'), {} - ) - ] : [ - // Plugins that apply in production builds only - new AngularCompilerPlugin({ - mainPath: path.join(__dirname, 'ClientApp/boot.server.PRODUCTION.ts'), - tsConfigPath: './tsconfig.json', - entryModule: path.join(__dirname, 'ClientApp/app/app.module.server#AppModule'), - exclude: ['./**/*.browser.ts'] - }) - ]), + ].concat( + isDevBuild + ? [ + new webpack.ContextReplacementPlugin( + // fixes WARNING Critical dependency: the request of a dependency is an expression + /(.+)?angular(\\|\/)core(.+)?/, + path.join(__dirname, 'src'), // location of your src + {} // a map of your routes + ), + new webpack.ContextReplacementPlugin( + // fixes WARNING Critical dependency: the request of a dependency is an expression + /(.+)?express(\\|\/)(.+)?/, + path.join(__dirname, 'src'), + {} + ) + ] + : [ + // Plugins that apply in production builds only + new AngularCompilerPlugin({ + mainPath: path.join( + __dirname, + 'ClientApp/boot.server.PRODUCTION.ts' + ), + tsConfigPath: './ClientApp/tsconfig.app.json', + entryModule: path.join( + __dirname, + 'ClientApp/app/app.module.server#AppModule' + ), + exclude: ['./**/*.browser.ts'], + sourceMap: isDevBuild + }) + ] + ), output: { libraryTarget: 'commonjs', path: path.join(__dirname, './ClientApp/dist') @@ -154,21 +190,25 @@ module.exports = (env) => { // switch to "inline-source-map" if you want to debug the TS during SSR devtool: isDevBuild ? 'cheap-eval-source-map' : false, optimization: { - minimizer: [].concat(isDevBuild ? [] : [ - // we specify a custom UglifyJsPlugin here to get source maps in production - new UglifyJsPlugin({ - cache: true, - parallel: true, - uglifyOptions: { - compress: false, - ecma: 6, - mangle: true, - keep_classnames: true, - keep_fnames: true - }, - sourceMap: true - }) - ]) + minimizer: [].concat( + isDevBuild + ? [] + : [ + // we specify a custom TerserPlugin here to get source maps in production + new TerserPlugin({ + cache: true, + parallel: true, + sourceMap: true, + terserOptions: { + compress: false, + ecma: 6, + mangle: true, + keep_classnames: true, + keep_fnames: true, + }, + }) + ] + ) } }); diff --git a/webpack.config.vendor.js b/webpack.config.vendor.js index 6b67e525..775910fb 100644 --- a/webpack.config.vendor.js +++ b/webpack.config.vendor.js @@ -2,7 +2,7 @@ const path = require('path'); const webpack = require('webpack'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const merge = require('webpack-merge'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); const treeShakableModules = [ '@angular/animations', '@angular/common', @@ -95,18 +95,18 @@ module.exports = (env) => { ]), optimization: { minimizer: [].concat(isDevBuild ? [] : [ - // we specify a custom UglifyJsPlugin here to get source maps in production - new UglifyJsPlugin({ + // we specify a custom TerserPlugin here to get source maps in production + new TerserPlugin({ cache: true, parallel: true, - uglifyOptions: { + sourceMap: true, + terserOptions: { compress: false, ecma: 6, mangle: true, keep_classnames: true, - keep_fnames: true + keep_fnames: true, }, - sourceMap: true }) ]) } @@ -141,18 +141,18 @@ module.exports = (env) => { ].concat(isDevBuild ? [] : []), optimization: { minimizer: [].concat(isDevBuild ? [] : [ - // we specify a custom UglifyJsPlugin here to get source maps in production - new UglifyJsPlugin({ + // we specify a custom TerserPlugin here to get source maps in production + new TerserPlugin({ cache: true, parallel: true, - uglifyOptions: { + sourceMap: true, + terserOptions: { compress: false, ecma: 6, mangle: true, keep_classnames: true, - keep_fnames: true + keep_fnames: true, }, - sourceMap: true }) ]) } diff --git a/wwwroot/favicon.ico b/wwwroot/favicon.ico index 6884543f..23015bba 100644 Binary files a/wwwroot/favicon.ico and b/wwwroot/favicon.ico differ diff --git a/wwwroot/images/icon144x144.png b/wwwroot/images/icon144x144.png new file mode 100644 index 00000000..b53f3791 Binary files /dev/null and b/wwwroot/images/icon144x144.png differ diff --git a/wwwroot/images/icon192x192.png b/wwwroot/images/icon192x192.png index 7f020c3f..6f4b36a1 100644 Binary files a/wwwroot/images/icon192x192.png and b/wwwroot/images/icon192x192.png differ diff --git a/wwwroot/images/icon256x256.png b/wwwroot/images/icon256x256.png new file mode 100644 index 00000000..66c420db Binary files /dev/null and b/wwwroot/images/icon256x256.png differ diff --git a/wwwroot/images/icon36x36.png b/wwwroot/images/icon36x36.png new file mode 100644 index 00000000..974921f1 Binary files /dev/null and b/wwwroot/images/icon36x36.png differ diff --git a/wwwroot/images/icon384x384.png b/wwwroot/images/icon384x384.png new file mode 100644 index 00000000..56c8c739 Binary files /dev/null and b/wwwroot/images/icon384x384.png differ diff --git a/wwwroot/images/icon48x48.png b/wwwroot/images/icon48x48.png new file mode 100644 index 00000000..734be054 Binary files /dev/null and b/wwwroot/images/icon48x48.png differ diff --git a/wwwroot/images/icon512x512.png b/wwwroot/images/icon512x512.png index 8f7e89c7..ed0b48d3 100644 Binary files a/wwwroot/images/icon512x512.png and b/wwwroot/images/icon512x512.png differ diff --git a/wwwroot/images/icon72x72.png b/wwwroot/images/icon72x72.png new file mode 100644 index 00000000..77fde9eb Binary files /dev/null and b/wwwroot/images/icon72x72.png differ diff --git a/wwwroot/images/icon96x96.png b/wwwroot/images/icon96x96.png new file mode 100644 index 00000000..c5a86651 Binary files /dev/null and b/wwwroot/images/icon96x96.png differ diff --git a/wwwroot/manifest.json b/wwwroot/manifest.json index fa6cd755..b386fa18 100644 --- a/wwwroot/manifest.json +++ b/wwwroot/manifest.json @@ -1,14 +1,45 @@ { - "name": "ASP.NET Core 2.1 & Angular 6(+) Advanced Starter", + "name": "ASP.NET Core 2.1 & Angular 7(+) Advanced Starter", "short_name": "aspnetcore-angular2-universal", - "description": "ASP.NET Core 2.1 & Angular 6(+) Advanced Starter - with Server-side prerendering (for Angular SEO)!", - "icons": [{ - "src": "/images/icon192x192.png", - "sizes": "192x192" - }, + "description": + "ASP.NET Core 2.1 & Angular 7(+) Advanced Starter - with Server-side prerendering (for Angular SEO)!", + "icons": [ { + "src": "/images/icon36x36.png", + "sizes": "36x36", + "type": "image/png" + }, { + "src": "/images/icon48x48.png", + "sizes": "48x48", + "type": "image/png" + }, { + "src": "/images/icon72x72.png", + "sizes": "72x72", + "type": "image/png" + }, { + "src": "/images/icon96x96.png", + "sizes": "96x96", + "type": "image/png" + }, { + "src": "/images/icon144x144.png", + "sizes": "144x144", + "type": "image/png" + }, { + "src": "/images/icon192x192.png", + "sizes": "192x192", + "type": "image/png" + }, { + "src": "/images/icon256x256.png", + "sizes": "256x256", + "type": "image/png" + }, { + "src": "/images/icon384x384.png", + "sizes": "384x384", + "type": "image/png" + }, { "src": "/images/icon512x512.png", - "sizes": "512x512" + "sizes": "512x512", + "type": "image/png" } ], "background_color": "#3E4EB8",