├── .github
└── screen.gif
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── README.md
├── backend
└── ASPNETCore
│ ├── ASPNETCore.Web
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── ASPNETCore.Web.csproj
│ ├── ASPNETCore.Web.csproj.user
│ ├── Controllers
│ │ └── FoodItemsController.cs
│ ├── Dtos
│ │ └── FoodItemDto.cs
│ ├── Extensions
│ │ └── MappingExtensions.cs
│ ├── Hubs
│ │ └── ChatHub.cs
│ ├── MappingProfiles
│ │ └── FoodMappings.cs
│ ├── Models
│ │ ├── ChatMessage.cs
│ │ └── FoodItem.cs
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Repositories
│ │ ├── FoodDbContext.cs
│ │ ├── FoodRepository.cs
│ │ └── IFoodRepository.cs
│ ├── Services
│ │ ├── HostedServiceBase.cs
│ │ ├── SchedulerHostedService.cs
│ │ └── TimerServiceConfiguration.cs
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ └── wwwroot
│ │ └── index.html
│ └── ASPNETCore.sln
└── frontend
├── .editorconfig
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
├── app
│ ├── about
│ │ ├── container
│ │ │ └── about
│ │ │ │ ├── about.component.css
│ │ │ │ ├── about.component.html
│ │ │ │ ├── about.component.spec.ts
│ │ │ │ └── about.component.ts
│ │ └── index.ts
│ ├── app-routes.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.config.ts
│ ├── dashboard
│ │ ├── container
│ │ │ └── dashboard
│ │ │ │ ├── dashboard.component.css
│ │ │ │ ├── dashboard.component.html
│ │ │ │ ├── dashboard.component.spec.ts
│ │ │ │ └── dashboard.component.ts
│ │ ├── index.ts
│ │ └── presentational
│ │ │ ├── chat
│ │ │ ├── chat.component.css
│ │ │ ├── chat.component.html
│ │ │ ├── chat.component.spec.ts
│ │ │ └── chat.component.ts
│ │ │ ├── cpu
│ │ │ ├── cpu.component.css
│ │ │ ├── cpu.component.html
│ │ │ ├── cpu.component.spec.ts
│ │ │ └── cpu.component.ts
│ │ │ └── food
│ │ │ ├── food.component.css
│ │ │ ├── food.component.html
│ │ │ ├── food.component.spec.ts
│ │ │ └── food.component.ts
│ ├── models
│ │ ├── chat-message.model.ts
│ │ └── foodItem.model.ts
│ ├── services
│ │ ├── food-data.service.ts
│ │ └── signalR.service.ts
│ └── shared
│ │ └── navigation
│ │ ├── navigation.component.css
│ │ ├── navigation.component.html
│ │ ├── navigation.component.spec.ts
│ │ └── navigation.component.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.development.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
└── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/.github/screen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/.github/screen.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /backend/.vs
2 | /backend/ASPNETCore/obj/
3 | /backend/ASPNETCore/bin/
4 | /frontend/angularsignalrclient/.temp
5 | backend/ASPNETCore/Properties/PublishProfiles/
6 | /backend/ASPNETCore/.vs
7 | /backend/ASPNETCore/ASPNETCore.Web/bin
8 | /backend/ASPNETCore/ASPNETCore.Web/obj
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | // Use IntelliSense to find out which attributes exist for C# debugging
6 | // Use hover for the description of the existing attributes
7 | // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md.
8 | "name": ".NET Core Launch (web)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/backend/ASPNETCore/ASPNETCore.Web/bin/Debug/net7.0/ASPNETCore.Web.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/backend/ASPNETCore/ASPNETCore.Web",
16 | "stopAtEntry": false,
17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
18 | "serverReadyAction": {
19 | "action": "openExternally",
20 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
21 | },
22 | "env": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | },
25 | "sourceFileMap": {
26 | "/Views": "${workspaceFolder}/Views"
27 | }
28 | },
29 | {
30 | "name": ".NET Core Attach",
31 | "type": "coreclr",
32 | "request": "attach"
33 | }
34 | ]
35 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.organizeImports": true,
4 | "source.fixAll.eslint": true
5 | },
6 | "editor.formatOnSave": true,
7 | "editor.formatOnPaste": true,
8 | "editor.defaultFormatter": "esbenp.prettier-vscode",
9 | "[typescript]": {
10 | "editor.defaultFormatter": "esbenp.prettier-vscode"
11 | },
12 | "[json]": {
13 | "editor.defaultFormatter": "esbenp.prettier-vscode"
14 | },
15 | "[jsonc]": {
16 | "editor.defaultFormatter": "esbenp.prettier-vscode"
17 | },
18 | "[javascript]": {
19 | "editor.defaultFormatter": "esbenp.prettier-vscode"
20 | },
21 | "[html]": {
22 | "editor.defaultFormatter": "esbenp.prettier-vscode"
23 | },
24 | "[less]": {
25 | "editor.defaultFormatter": "esbenp.prettier-vscode"
26 | },
27 | "typescript.preferences.importModuleSpecifier": "relative"
28 | }
29 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/backend/ASPNETCore/ASPNETCore.sln",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/backend/ASPNETCore/ASPNETCore.sln",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "--project",
36 | "${workspaceFolder}/backend/ASPNETCore/ASPNETCore.sln"
37 | ],
38 | "problemMatcher": "$msCompile"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Welcome to ASPNET Core Angular SignalR Typescript 👋
2 |
3 |
4 |
5 |
6 |
7 |
8 | With this project we are running an Angular project which is getting updated by an ASP.NET Core backend with SignalR. It receives data over a REST interface and gets datapushed with SignalR. Enjoy!
9 |
10 | 
11 |
12 | ## Install
13 |
14 | Just clone this repo and run
15 |
16 | ```javascript
17 | npm install
18 | ```
19 |
20 | in the `\frontend\angularsignalrclient` folder.
21 |
22 | ## Usage
23 |
24 | ```javascript
25 | npm start
26 | ```
27 |
28 | in the `\frontend\angularsignalrclient` folder.
29 |
30 | To run the backend make sure you have installed the [dotnet cli](https://dotnet.microsoft.com/) and run
31 |
32 | ```bash
33 | dotnet run
34 | ```
35 |
36 | in the `backend\ASPNETCore` folder.
37 |
38 | ```javascript
39 | http://localhost:4200
40 | ```
41 |
42 | to see the page then.
43 |
44 | Have fun!
45 |
46 | ## Author
47 |
48 | 👤 **Fabian Gosebrink**
49 |
50 | - Twitter: [@FabianGosebrink](https://twitter.com/FabianGosebrink)
51 | - Github: [@FabianGosebrink](https://github.com/FabianGosebrink)
52 |
53 | ## Show your support
54 |
55 | Give a ⭐️ if this project helped you!
56 |
57 | ---
58 |
59 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_
60 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (web)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/ASPNETCore.Web.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}",
16 | "stopAtEntry": false,
17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
18 | "serverReadyAction": {
19 | "action": "openExternally",
20 | "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"
21 | },
22 | "env": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | },
25 | "sourceFileMap": {
26 | "/Views": "${workspaceFolder}/Views"
27 | }
28 | },
29 | {
30 | "name": ".NET Core Attach",
31 | "type": "coreclr",
32 | "request": "attach",
33 | "processId": "${command:pickProcess}"
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/ASPNETCore.Web.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/ASPNETCore.Web.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/ASPNETCore.Web.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/ASPNETCore.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/ASPNETCore.Web.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ProjectDebugger
5 |
6 |
7 | ASPNETCore.Web
8 |
9 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Controllers/FoodItemsController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using AspNetCoreAngularSignalR.Dtos;
5 | using AspNetCoreAngularSignalR.Hubs;
6 | using AspNetCoreAngularSignalR.Models;
7 | using AspNetCoreAngularSignalR.Repositories;
8 | using AutoMapper;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.AspNetCore.SignalR;
11 |
12 | namespace AspNetCoreAngularSignalR.Controllers
13 | {
14 | [Route("api/[controller]")]
15 | public class FoodItemsController : Controller
16 | {
17 | private readonly IFoodRepository _foodRepository;
18 | private readonly IHubContext _chatHubContext;
19 | private readonly IMapper _mapper;
20 |
21 | public FoodItemsController(
22 | IFoodRepository foodRepository,
23 | IHubContext hubContext,
24 | IMapper mapper)
25 | {
26 | _foodRepository = foodRepository;
27 | _chatHubContext = hubContext;
28 | _mapper = mapper;
29 | }
30 |
31 | [HttpGet]
32 | public ActionResult> GetAllFoods()
33 | {
34 | var foods = _foodRepository.GetAll();
35 | return Ok(foods.Select(x => _mapper.Map(x)));
36 | }
37 |
38 | [HttpGet]
39 | [Route("{id:int}", Name = nameof(GetSingleFood))]
40 | public ActionResult GetSingleFood(int id)
41 | {
42 | FoodItem foodItem = _foodRepository.GetSingle(id);
43 |
44 | if (foodItem == null)
45 | {
46 | return NotFound();
47 | }
48 |
49 | return Ok(_mapper.Map(foodItem));
50 | }
51 |
52 | [HttpPost]
53 | public ActionResult AddFoodToList([FromBody] FoodItemDto viewModel)
54 | {
55 | if (viewModel == null)
56 | {
57 | return BadRequest();
58 | }
59 |
60 | if (!ModelState.IsValid)
61 | {
62 | return BadRequest(ModelState);
63 | }
64 |
65 | FoodItem item = _mapper.Map(viewModel);
66 | item.Created = DateTime.Now;
67 | FoodItem newFoodItem = _foodRepository.Add(item);
68 |
69 | _chatHubContext.Clients.All.SendAsync("FoodAdded", newFoodItem);
70 |
71 | return CreatedAtRoute(
72 | nameof(GetSingleFood),
73 | new { id = newFoodItem.Id },
74 | _mapper.Map(newFoodItem));
75 | }
76 |
77 | [HttpPut]
78 | [Route("{foodItemId:int}")]
79 | public ActionResult UpdateFoodInList(int foodItemId, [FromBody] FoodItemDto viewModel)
80 | {
81 | if (viewModel == null)
82 | {
83 | return BadRequest();
84 | }
85 |
86 | if (!ModelState.IsValid)
87 | {
88 | return BadRequest(ModelState);
89 | }
90 |
91 |
92 | FoodItem singleById = _foodRepository.GetSingle(foodItemId);
93 |
94 | if (singleById == null)
95 | {
96 | return NotFound();
97 | }
98 |
99 | singleById.ItemName = viewModel.ItemName;
100 |
101 | FoodItem newFoodItem = _foodRepository.Update(singleById);
102 | _chatHubContext.Clients.All.SendAsync("FoodUpdated", newFoodItem);
103 | return Ok(_mapper.Map(newFoodItem));
104 | }
105 |
106 | [HttpDelete]
107 | [Route("{foodItemId:int}")]
108 | public ActionResult DeleteFoodFromList(int foodItemId)
109 | {
110 |
111 | FoodItem singleById = _foodRepository.GetSingle(foodItemId);
112 |
113 | if (singleById == null)
114 | {
115 | return NotFound();
116 | }
117 |
118 | _foodRepository.Delete(foodItemId);
119 |
120 | _chatHubContext.Clients.All.SendAsync("FoodDeleted");
121 | return NoContent();
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Dtos/FoodItemDto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AspNetCoreAngularSignalR.Dtos
4 | {
5 | public class FoodItemDto
6 | {
7 | public int Id { get; set; }
8 | public string ItemName { get; set; }
9 | public DateTime Created { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Extensions/MappingExtensions.cs:
--------------------------------------------------------------------------------
1 | using ASPNETCore.Web.MappingProfiles;
2 | using AutoMapper;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace ASPNETCore.Extensions
6 | {
7 | public static class MappingExtensions
8 | {
9 | public static void AddMappingProfiles(this IServiceCollection services)
10 | {
11 | services.AddAutoMapper(typeof(FoodMappings));
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Hubs/ChatHub.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using AspNetCoreAngularSignalR.Models;
3 | using Microsoft.AspNetCore.SignalR;
4 |
5 | namespace AspNetCoreAngularSignalR.Hubs
6 | {
7 | public class ChatHub : Hub
8 | {
9 | public async Task SendMessage(ChatMessage chatMessage)
10 | {
11 | await Clients.All.SendAsync("Send", chatMessage);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/MappingProfiles/FoodMappings.cs:
--------------------------------------------------------------------------------
1 | using AspNetCoreAngularSignalR.Dtos;
2 | using AspNetCoreAngularSignalR.Models;
3 | using AutoMapper;
4 |
5 | namespace ASPNETCore.Web.MappingProfiles
6 | {
7 | public class FoodMappings : Profile
8 | {
9 | public FoodMappings()
10 | {
11 | CreateMap().ReverseMap();
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Models/ChatMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AspNetCoreAngularSignalR.Models
4 | {
5 | public class ChatMessage
6 | {
7 | public string Message { get; set; }
8 | public DateTime Sent { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Models/FoodItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace AspNetCoreAngularSignalR.Models
5 | {
6 | public class FoodItem
7 | {
8 | [Key]
9 | public int Id { get; set; }
10 | public string ItemName { get; set; }
11 | public DateTime Created { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Program.cs:
--------------------------------------------------------------------------------
1 | using ASPNETCore.Extensions;
2 | using ASPNETCore.Repositories;
3 | using ASPNETCore.Web;
4 | using ASPNETCore.Web.MappingProfiles;
5 | using AspNetCoreAngularSignalR.Hubs;
6 | using AspNetCoreAngularSignalR.Repositories;
7 | using AspNetCoreAngularSignalR.Services;
8 | using Microsoft.AspNetCore.Mvc.ApiExplorer;
9 | using Microsoft.EntityFrameworkCore;
10 | using Microsoft.Extensions.Options;
11 | using Newtonsoft.Json.Serialization;
12 | using Swashbuckle.AspNetCore.SwaggerGen;
13 |
14 | var builder = WebApplication.CreateBuilder(args);
15 |
16 | builder.Services.AddControllers()
17 | .AddNewtonsoftJson(options =>
18 | options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
19 |
20 | builder.Services.AddEndpointsApiExplorer();
21 | builder.Services.AddSwaggerGen();
22 |
23 | builder.Services.AddCors(options =>
24 | {
25 | options.AddPolicy("CorsPolicy",
26 | builder =>
27 | {
28 | builder
29 | .AllowAnyHeader()
30 | .AllowAnyMethod()
31 | .WithOrigins("http://localhost:4200")
32 | .AllowCredentials();
33 | });
34 | });
35 |
36 | builder.Services.AddSingleton();
37 | builder.Services.Configure(builder.Configuration.GetSection("TimeService"));
38 | builder.Services.AddSingleton();
39 |
40 | builder.Services.AddRouting(options => options.LowercaseUrls = true);
41 |
42 | builder.Services.AddSignalR();
43 |
44 | builder.Services.AddMappingProfiles();
45 |
46 | builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("FoodDb"));
47 |
48 | var app = builder.Build();
49 |
50 | if (app.Environment.IsDevelopment())
51 | {
52 | app.UseDeveloperExceptionPage();
53 | }
54 | var loggerFactory = app.Services.GetRequiredService();
55 |
56 | app.UseSwagger();
57 | app.UseSwaggerUI();
58 |
59 | app.UseHttpsRedirection();
60 | app.UseCors("CorsPolicy");
61 | app.UseDefaultFiles();
62 | app.UseStaticFiles();
63 | app.UseRouting();
64 |
65 | app.MapControllers();
66 | app.MapHub("/coolmessages");
67 |
68 | app.Run();
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:58816",
8 | "sslPort": 44389
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "swagger",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "ASPNETCore.Web": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "swagger",
24 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Repositories/FoodDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 |
3 | namespace ASPNETCore.Repositories
4 | {
5 | public class FoodDbContext : DbContext
6 | {
7 | public FoodDbContext(DbContextOptions options)
8 | : base(options)
9 | {
10 | }
11 |
12 | public DbSet TodoItems { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Repositories/FoodRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using AspNetCoreAngularSignalR.Models;
5 |
6 | namespace AspNetCoreAngularSignalR.Repositories
7 | {
8 | public class FoodRepository : IFoodRepository
9 | {
10 | readonly Dictionary _foods = new Dictionary();
11 |
12 | public FoodRepository()
13 | {
14 | _foods.Add(1, new FoodItem() { ItemName = "Spaghetti Bolognese", Id = 1, Created = DateTime.Now});
15 | }
16 |
17 | public List GetAll()
18 | {
19 | return _foods.Select(x => x.Value).ToList();
20 | }
21 |
22 | public FoodItem GetSingle(int id)
23 | {
24 | return _foods.FirstOrDefault(x => x.Key == id).Value;
25 | }
26 |
27 | public FoodItem Add(FoodItem toAdd)
28 | {
29 | int newId = !GetAll().Any() ? 1 : GetAll().Max(x => x.Id) + 1;
30 | toAdd.Id = newId;
31 | _foods.Add(newId, toAdd);
32 | return toAdd;
33 | }
34 |
35 | public FoodItem Update(FoodItem toUpdate)
36 | {
37 | FoodItem single = GetSingle(toUpdate.Id);
38 |
39 | if (single == null)
40 | {
41 | return null;
42 | }
43 |
44 | _foods[single.Id] = toUpdate;
45 | return toUpdate;
46 | }
47 |
48 | public void Delete(int id)
49 | {
50 | _foods.Remove(id);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Repositories/IFoodRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AspNetCoreAngularSignalR.Models;
3 |
4 | namespace AspNetCoreAngularSignalR.Repositories
5 | {
6 | public interface IFoodRepository
7 | {
8 | List GetAll();
9 | FoodItem GetSingle(int id);
10 | FoodItem Add(FoodItem toAdd);
11 | FoodItem Update(FoodItem toUpdate);
12 | void Delete(int id);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Services/HostedServiceBase.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Hosting;
4 |
5 | namespace AspNetCoreAngularSignalR.Services
6 | {
7 | public abstract class HostedServiceBase : IHostedService
8 | {
9 | // see for correct usage of IHostedService implementation:
10 | // https://gist.github.com/davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3
11 |
12 | private Task _executingTask;
13 | private CancellationTokenSource _cts;
14 |
15 | public Task StartAsync(CancellationToken cancellationToken)
16 | {
17 | // Create a linked token so we can trigger cancellation outside of this token's cancellation
18 | _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
19 |
20 | // Store the task we're executing
21 | _executingTask = ExecuteAsync(_cts.Token);
22 |
23 | // If the task is completed then return it, otherwise it's running
24 | return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
25 | }
26 |
27 | public async Task StopAsync(CancellationToken cancellationToken)
28 | {
29 | // Stop called without start
30 | if (_executingTask == null)
31 | {
32 | return;
33 | }
34 |
35 | // Signal cancellation to the executing method
36 | _cts.Cancel();
37 |
38 | // Wait until the task completes or the stop token triggers
39 | await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
40 |
41 | // Throw if cancellation triggered
42 | cancellationToken.ThrowIfCancellationRequested();
43 | }
44 |
45 | // Derived classes should override this and execute a long running method until
46 | // cancellation is requested
47 | protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Services/SchedulerHostedService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using AspNetCoreAngularSignalR.Hubs;
5 | using Microsoft.AspNetCore.SignalR;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Extensions.Options;
8 |
9 | namespace AspNetCoreAngularSignalR.Services
10 | {
11 | public class SchedulerHostedService : HostedServiceBase
12 | {
13 | private readonly ILogger _logger;
14 | private readonly IOptions _options;
15 | private readonly IHubContext _hubContext;
16 |
17 | private readonly Random _random = new Random();
18 |
19 | public SchedulerHostedService(
20 | ILoggerFactory loggerFactory,
21 | IOptions options,
22 | IHubContext hubContext)
23 | {
24 | _logger = loggerFactory.CreateLogger();
25 | _options = options;
26 | _hubContext = hubContext;
27 | }
28 |
29 | protected override async Task ExecuteAsync(CancellationToken cancellationToken)
30 | {
31 | while (!cancellationToken.IsCancellationRequested)
32 | {
33 | var randomValue = _random.Next(0, 100);
34 |
35 | _logger.LogInformation($"Sending newCpuValue {randomValue}...");
36 |
37 | await _hubContext.Clients.All.SendAsync("newCpuValue", randomValue);
38 |
39 | await Task.Delay(TimeSpan.FromMilliseconds(_options.Value.Period), cancellationToken);
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/Services/TimerServiceConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace AspNetCoreAngularSignalR.Services
2 | {
3 | public class TimerServiceConfiguration
4 | {
5 | public int DueTime { get; set; }
6 |
7 | public int Period { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "TimeService": {
10 | "DueTime": 3000,
11 | "Period": 1500
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.Web/wwwroot/index.html:
--------------------------------------------------------------------------------
1 | Server API runs
--------------------------------------------------------------------------------
/backend/ASPNETCore/ASPNETCore.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29318.209
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASPNETCore.Web", "ASPNETCore.Web\ASPNETCore.Web.csproj", "{33CB9E94-6C3F-4A17-AC76-3FD33AF8A979}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {33CB9E94-6C3F-4A17-AC76-3FD33AF8A979}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {33CB9E94-6C3F-4A17-AC76-3FD33AF8A979}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {33CB9E94-6C3F-4A17-AC76-3FD33AF8A979}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {33CB9E94-6C3F-4A17-AC76-3FD33AF8A979}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {5E848F73-2A9E-4B47-885A-720FA6B9F189}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/frontend/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # NewFrontend
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.4.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/frontend/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "newFrontend": {
7 | "projectType": "application",
8 | "schematics": {},
9 | "root": "",
10 | "sourceRoot": "src",
11 | "prefix": "app",
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/new-frontend",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": ["zone.js"],
20 | "tsConfig": "tsconfig.app.json",
21 | "assets": ["src/favicon.ico", "src/assets"],
22 | "styles": [
23 | "node_modules/@fortawesome/fontawesome-free/css/all.css",
24 | "src/styles.css"
25 | ],
26 | "scripts": []
27 | },
28 | "configurations": {
29 | "production": {
30 | "budgets": [
31 | {
32 | "type": "initial",
33 | "maximumWarning": "500kb",
34 | "maximumError": "1mb"
35 | },
36 | {
37 | "type": "anyComponentStyle",
38 | "maximumWarning": "2kb",
39 | "maximumError": "4kb"
40 | }
41 | ],
42 | "outputHashing": "all"
43 | },
44 | "development": {
45 | "buildOptimizer": false,
46 | "optimization": false,
47 | "vendorChunk": true,
48 | "extractLicenses": false,
49 | "sourceMap": true,
50 | "namedChunks": true,
51 | "fileReplacements": [
52 | {
53 | "replace": "src/environments/environment.ts",
54 | "with": "src/environments/environment.development.ts"
55 | }
56 | ]
57 | }
58 | },
59 | "defaultConfiguration": "production"
60 | },
61 | "serve": {
62 | "builder": "@angular-devkit/build-angular:dev-server",
63 | "configurations": {
64 | "production": {
65 | "browserTarget": "newFrontend:build:production"
66 | },
67 | "development": {
68 | "browserTarget": "newFrontend:build:development"
69 | }
70 | },
71 | "defaultConfiguration": "development"
72 | },
73 | "extract-i18n": {
74 | "builder": "@angular-devkit/build-angular:extract-i18n",
75 | "options": {
76 | "browserTarget": "newFrontend:build"
77 | }
78 | },
79 | "test": {
80 | "builder": "@angular-devkit/build-angular:karma",
81 | "options": {
82 | "polyfills": ["zone.js", "zone.js/testing"],
83 | "tsConfig": "tsconfig.spec.json",
84 | "assets": ["src/favicon.ico", "src/assets"],
85 | "styles": ["src/styles.css"],
86 | "scripts": []
87 | }
88 | }
89 | }
90 | }
91 | },
92 | "cli": {
93 | "analytics": "16050acf-7df8-46ba-b862-6cf8b42b9257"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "new-frontend",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "@fortawesome/fontawesome-free": "^6.4.2",
22 | "@microsoft/signalr": "^7.0.11",
23 | "@swimlane/ngx-charts": "^20.4.1",
24 | "rxjs": "~7.8.0",
25 | "tslib": "^2.3.0",
26 | "zone.js": "~0.13.0"
27 | },
28 | "devDependencies": {
29 | "@angular-devkit/build-angular": "^16.0.4",
30 | "@angular/cli": "~16.0.4",
31 | "@angular/compiler-cli": "^16.0.0",
32 | "@types/jasmine": "~4.3.0",
33 | "jasmine-core": "~4.6.0",
34 | "karma": "~6.4.0",
35 | "karma-chrome-launcher": "~3.2.0",
36 | "karma-coverage": "~2.2.0",
37 | "karma-jasmine": "~5.1.0",
38 | "karma-jasmine-html-reporter": "~2.0.0",
39 | "typescript": "~5.0.2"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/src/app/about/container/about/about.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/frontend/src/app/about/container/about/about.component.css
--------------------------------------------------------------------------------
/frontend/src/app/about/container/about/about.component.html:
--------------------------------------------------------------------------------
1 |
2 | about works!!
3 |
4 |
--------------------------------------------------------------------------------
/frontend/src/app/about/container/about/about.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AboutComponent } from './about.component';
4 |
5 | describe('AboutComponent', () => {
6 | let component: AboutComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | imports: [AboutComponent]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AboutComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/about/container/about/about.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-about',
5 | templateUrl: './about.component.html',
6 | styleUrls: ['./about.component.css'],
7 | standalone: true,
8 | })
9 | export class AboutComponent {}
10 |
--------------------------------------------------------------------------------
/frontend/src/app/about/index.ts:
--------------------------------------------------------------------------------
1 | import { AboutComponent } from './container/about/about.component';
2 |
3 | export const ABOUT_ROUTES = [{ path: '', component: AboutComponent }];
4 |
--------------------------------------------------------------------------------
/frontend/src/app/app-routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const APP_ROUTES: Routes = [
4 | { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
5 | {
6 | path: 'dashboard',
7 | loadChildren: () => import('./dashboard').then((m) => m.DASHBOARD_ROUTES),
8 | },
9 | {
10 | path: 'about',
11 | loadChildren: () => import('./about').then((m) => m.ABOUT_ROUTES),
12 | },
13 | ];
14 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/frontend/src/app/app.component.css
--------------------------------------------------------------------------------
/frontend/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(() => TestBed.configureTestingModule({
7 | imports: [RouterTestingModule, AppComponent]
8 | }));
9 |
10 | it('should create the app', () => {
11 | const fixture = TestBed.createComponent(AppComponent);
12 | const app = fixture.componentInstance;
13 | expect(app).toBeTruthy();
14 | });
15 |
16 | it(`should have as title 'newFrontend'`, () => {
17 | const fixture = TestBed.createComponent(AppComponent);
18 | const app = fixture.componentInstance;
19 | expect(app.title).toEqual('newFrontend');
20 | });
21 |
22 | it('should render title', () => {
23 | const fixture = TestBed.createComponent(AppComponent);
24 | fixture.detectChanges();
25 | const compiled = fixture.nativeElement as HTMLElement;
26 | expect(compiled.querySelector('.content span')?.textContent).toContain('newFrontend app is running!');
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/frontend/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
2 | import { Component } from '@angular/core';
3 | import { RouterOutlet } from '@angular/router';
4 | import { NavigationComponent } from './shared/navigation/navigation.component';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | styleUrls: ['./app.component.css'],
10 | standalone: true,
11 | imports: [
12 | NgSwitch,
13 | NgSwitchDefault,
14 | NgSwitchCase,
15 | RouterOutlet,
16 | NavigationComponent,
17 | ],
18 | })
19 | export class AppComponent {
20 | title = 'frontend';
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { provideHttpClient } from '@angular/common/http';
2 | import { ApplicationConfig, importProvidersFrom } from '@angular/core';
3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 | import { provideRouter } from '@angular/router';
5 | import { APP_ROUTES } from './app-routes';
6 |
7 | export const appConfig: ApplicationConfig = {
8 | providers: [
9 | provideRouter(APP_ROUTES),
10 | provideHttpClient(),
11 | importProvidersFrom(BrowserAnimationsModule),
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/container/dashboard/dashboard.component.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | width: 90%;
3 | min-height: 300px;
4 | border-radius: 21px 21px 0 0;
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/container/dashboard/dashboard.component.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
22 |
31 |
32 |
33 | Loading...
34 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/container/dashboard/dashboard.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { DashboardComponent } from './dashboard.component';
4 |
5 | describe('DashboardComponent', () => {
6 | let component: DashboardComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ DashboardComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(DashboardComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/container/dashboard/dashboard.component.ts:
--------------------------------------------------------------------------------
1 | import { AsyncPipe, NgIf } from '@angular/common';
2 | import { Component, OnInit, inject } from '@angular/core';
3 | import { Observable } from 'rxjs';
4 | import { ChatMessage } from '../../../models/chat-message.model';
5 | import { FoodItem } from '../../../models/foodItem.model';
6 | import { FoodDataService } from '../../../services/food-data.service';
7 | import { SignalRService } from '../../../services/signalR.service';
8 | import { ChatComponent } from '../../presentational/chat/chat.component';
9 | import { CpuComponent } from '../../presentational/cpu/cpu.component';
10 | import { FoodComponent } from '../../presentational/food/food.component';
11 |
12 | @Component({
13 | selector: 'app-dashboard',
14 | templateUrl: './dashboard.component.html',
15 | styleUrls: ['./dashboard.component.css'],
16 | standalone: true,
17 | imports: [CpuComponent, FoodComponent, ChatComponent, AsyncPipe, NgIf],
18 | })
19 | export class DashboardComponent implements OnInit {
20 | private readonly signalRService = inject(SignalRService);
21 | private readonly foodDataService = inject(FoodDataService);
22 |
23 | cpuValue$ = this.signalRService.newCpuValue$;
24 | signalrConnectionEstablished$ = this.signalRService.connectionEstablished$;
25 | foodItems$: Observable;
26 | chatmessages = [];
27 |
28 | ngOnInit() {
29 | this.signalRService.foodChanged$.subscribe(() => this.getFoodData());
30 |
31 | this.signalRService.messageReceived$.subscribe((message) => {
32 | this.chatmessages = [...this.chatmessages, message];
33 | });
34 |
35 | this.getFoodData();
36 | }
37 |
38 | saveFood(item: FoodItem) {
39 | if (item.id) {
40 | this.foodDataService
41 | .updateFood(item)
42 | .subscribe(() => console.log('food updated'));
43 | } else {
44 | this.foodDataService
45 | .addFood(item.itemName)
46 | .subscribe(() => console.log('food added'));
47 | }
48 | }
49 |
50 | deleteFood(item: FoodItem) {
51 | if (!confirm('Really delete?')) {
52 | return;
53 | }
54 |
55 | this.foodDataService
56 | .deleteFood(item.id)
57 | .subscribe(() => console.log('food deleted'));
58 | }
59 |
60 | sendChat(message: ChatMessage) {
61 | this.signalRService.sendChatMessage(message);
62 | }
63 |
64 | private getFoodData() {
65 | this.foodItems$ = this.foodDataService.getAllFood();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/index.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { DashboardComponent } from './container/dashboard/dashboard.component';
3 |
4 | export const DASHBOARD_ROUTES: Routes = [
5 | { path: '', component: DashboardComponent },
6 | ];
7 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/chat/chat.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/frontend/src/app/dashboard/presentational/chat/chat.component.css
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/chat/chat.component.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 | -
21 | {{ message.sent | date: 'medium' }}: {{ message.message }}
22 |
23 |
24 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/chat/chat.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ChatComponent } from './chat.component';
4 |
5 | describe('ChatComponent', () => {
6 | let component: ChatComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ChatComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ChatComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/chat/chat.component.ts:
--------------------------------------------------------------------------------
1 | import { DatePipe, NgFor } from '@angular/common';
2 | import {
3 | ChangeDetectionStrategy,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | Output,
8 | inject,
9 | } from '@angular/core';
10 | import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
11 |
12 | @Component({
13 | selector: 'app-chat',
14 | templateUrl: './chat.component.html',
15 | styleUrls: ['./chat.component.css'],
16 | changeDetection: ChangeDetectionStrategy.OnPush,
17 | standalone: true,
18 | imports: [DatePipe, ReactiveFormsModule, NgFor],
19 | })
20 | export class ChatComponent {
21 | private readonly formbuilder = inject(FormBuilder);
22 |
23 | @Input() chatmessages = [];
24 |
25 | @Input() connectionEstablished = false;
26 |
27 | @Output() sendChat = new EventEmitter();
28 |
29 | form = this.formbuilder.group({
30 | chatmessage: ['', Validators.required],
31 | });
32 |
33 | send() {
34 | const chatMessage = {
35 | sent: new Date().toISOString(),
36 | message: this.form.value.chatmessage,
37 | };
38 | this.sendChat.emit(chatMessage);
39 | this.form.reset();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/cpu/cpu.component.css:
--------------------------------------------------------------------------------
1 | .container-center {
2 | display: flex;
3 | justify-content: center;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/cpu/cpu.component.html:
--------------------------------------------------------------------------------
1 | CPU Value
2 |
3 |
4 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/cpu/cpu.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CpuComponent } from './cpu.component';
4 |
5 | describe('CpuComponent', () => {
6 | let component: CpuComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CpuComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CpuComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/cpu/cpu.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
2 | import { NgxChartsModule } from '@swimlane/ngx-charts';
3 |
4 | @Component({
5 | selector: 'app-cpu',
6 | templateUrl: './cpu.component.html',
7 | styleUrls: ['./cpu.component.css'],
8 | standalone: true,
9 | imports: [NgxChartsModule],
10 | })
11 | export class CpuComponent implements OnChanges {
12 | @Input() cpuValue: number;
13 |
14 | view: number[] = [400, 400];
15 | data: { name: string; value: number }[] = [];
16 |
17 | colorScheme = {
18 | domain: ['#5AA454'],
19 | };
20 |
21 | ngOnChanges(changes: SimpleChanges) {
22 | if (changes.cpuValue) {
23 | this.renderChart(changes.cpuValue.currentValue);
24 | }
25 | }
26 |
27 | renderChart(cpuValue: number) {
28 | this.data = [
29 | {
30 | name: 'CPU',
31 | value: cpuValue || 0,
32 | },
33 | ];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/food/food.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/frontend/src/app/dashboard/presentational/food/food.component.css
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/food/food.component.html:
--------------------------------------------------------------------------------
1 | FoodList
2 |
3 |
21 |
22 |
23 |
24 | -
25 | {{ item.itemName }}
26 |
27 |
35 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/food/food.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { FoodComponent } from './food.component';
4 |
5 | describe('FoodComponent', () => {
6 | let component: FoodComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ FoodComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(FoodComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/dashboard/presentational/food/food.component.ts:
--------------------------------------------------------------------------------
1 | import { NgFor } from '@angular/common';
2 | import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
3 | import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
4 | import { FoodItem } from '../../../models/foodItem.model';
5 |
6 | @Component({
7 | selector: 'app-food',
8 | templateUrl: './food.component.html',
9 | styleUrls: ['./food.component.css'],
10 | standalone: true,
11 | imports: [ReactiveFormsModule, NgFor],
12 | })
13 | export class FoodComponent {
14 | private readonly formbuilder = inject(FormBuilder);
15 | @Input() foodItems = [];
16 | @Input() connectionEstablished = false;
17 |
18 | @Output() foodSaved = new EventEmitter();
19 | @Output() foodDeleted = new EventEmitter();
20 |
21 | form = this.formbuilder.group({
22 | id: 0,
23 | itemName: ['', Validators.required],
24 | });
25 |
26 | saveFood() {
27 | this.foodSaved.emit(this.form.value as FoodItem);
28 | this.form.reset();
29 | }
30 |
31 | setFoodItemToEdit(foodItem: FoodItem) {
32 | const { itemName, id } = foodItem;
33 |
34 | this.form.patchValue({ itemName, id });
35 | }
36 |
37 | deleteFoodItem(foodItem: FoodItem) {
38 | this.foodDeleted.emit(foodItem);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/frontend/src/app/models/chat-message.model.ts:
--------------------------------------------------------------------------------
1 | export interface ChatMessage {
2 | message: string;
3 | sent: string;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/app/models/foodItem.model.ts:
--------------------------------------------------------------------------------
1 | export interface FoodItem {
2 | id: number;
3 | itemName: string;
4 | created: Date;
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/app/services/food-data.service.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable, inject } from '@angular/core';
3 | import { environment } from '../../environments/environment';
4 | import { FoodItem } from '../models/foodItem.model';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export class FoodDataService {
8 | private readonly http = inject(HttpClient);
9 |
10 | private actionUrl =
11 | environment.baseUrls.server + environment.baseUrls.apiUrl + 'foodItems/';
12 |
13 | getAllFood() {
14 | return this.http.get(this.actionUrl);
15 | }
16 |
17 | getSingleFood(id: number) {
18 | return this.http.get(this.actionUrl + id);
19 | }
20 |
21 | addFood(itemName: string) {
22 | return this.http.post(this.actionUrl, { itemName });
23 | }
24 |
25 | updateFood(foodToUpdate: FoodItem) {
26 | return this.http.put(
27 | this.actionUrl + foodToUpdate.id,
28 | foodToUpdate
29 | );
30 | }
31 |
32 | deleteFood(id: number) {
33 | return this.http.delete(this.actionUrl + id);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/app/services/signalR.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import {
3 | HubConnection,
4 | HubConnectionBuilder,
5 | HubConnectionState,
6 | LogLevel,
7 | } from '@microsoft/signalr';
8 | import { BehaviorSubject, Subject } from 'rxjs';
9 | import { environment } from '../../environments/environment';
10 | import { ChatMessage } from '../models/chat-message.model';
11 |
12 | @Injectable({ providedIn: 'root' })
13 | export class SignalRService {
14 | private hubConnection: HubConnection;
15 |
16 | private foodchanged = new Subject();
17 |
18 | private messageReceived = new Subject();
19 |
20 | private newCpuValue = new Subject();
21 |
22 | private connectionEstablished = new BehaviorSubject(false);
23 |
24 | get foodChanged$() {
25 | return this.foodchanged.asObservable();
26 | }
27 |
28 | get messageReceived$() {
29 | return this.messageReceived.asObservable();
30 | }
31 |
32 | get newCpuValue$() {
33 | return this.newCpuValue.asObservable();
34 | }
35 |
36 | get connectionEstablished$() {
37 | return this.connectionEstablished.asObservable();
38 | }
39 |
40 | constructor() {
41 | this.createConnection();
42 | this.registerOnServerEvents();
43 | this.startConnection();
44 | }
45 |
46 | sendChatMessage(message: ChatMessage) {
47 | this.hubConnection.invoke('SendMessage', message);
48 | }
49 |
50 | private createConnection() {
51 | this.hubConnection = new HubConnectionBuilder()
52 | .withUrl(environment.baseUrls.server + 'coolmessages')
53 | .withAutomaticReconnect()
54 | .configureLogging(LogLevel.Information)
55 | .build();
56 | }
57 |
58 | private startConnection() {
59 | if (this.hubConnection.state === HubConnectionState.Connected) {
60 | return;
61 | }
62 |
63 | this.hubConnection.start().then(
64 | () => {
65 | this.connectionEstablished.next(true);
66 | },
67 | (error) => console.error(error)
68 | );
69 | }
70 |
71 | private registerOnServerEvents(): void {
72 | this.hubConnection.on('FoodAdded', (data) => {
73 | this.foodchanged.next(data);
74 | });
75 |
76 | this.hubConnection.on('FoodDeleted', () => {
77 | this.foodchanged.next('this could be data');
78 | });
79 |
80 | this.hubConnection.on('FoodUpdated', () => {
81 | this.foodchanged.next('this could be data');
82 | });
83 |
84 | this.hubConnection.on('Send', (data) => {
85 | this.messageReceived.next(data);
86 | });
87 |
88 | this.hubConnection.on('newCpuValue', (data: number) => {
89 | this.newCpuValue.next(data);
90 | });
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/navigation/navigation.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/frontend/src/app/shared/navigation/navigation.component.css
--------------------------------------------------------------------------------
/frontend/src/app/shared/navigation/navigation.component.html:
--------------------------------------------------------------------------------
1 |
28 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/navigation/navigation.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { NavigationComponent } from './navigation.component';
4 |
5 | describe('NavigationComponent', () => {
6 | let component: NavigationComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ NavigationComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(NavigationComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/navigation/navigation.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-navigation',
6 | templateUrl: './navigation.component.html',
7 | styleUrls: ['./navigation.component.css'],
8 | standalone: true,
9 | imports: [RouterModule],
10 | })
11 | export class NavigationComponent {}
12 |
--------------------------------------------------------------------------------
/frontend/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/frontend/src/assets/.gitkeep
--------------------------------------------------------------------------------
/frontend/src/environments/environment.development.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false,
3 | baseUrls: {
4 | server: 'https://localhost:5001/',
5 | apiUrl: 'api/',
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/frontend/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | baseUrls: {
4 | server: 'https://aspnetcore20190520055459.azurewebsites.net/',
5 | apiUrl: 'api/',
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/frontend/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabianGosebrink/ASPNETCore-Angular-SignalR-Typescript/9d9bea4e9295246c83ef9bd216890f69dcc1c4e9/frontend/src/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NewFrontend
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { AppComponent } from './app/app.component';
3 | import { appConfig } from './app/app.config';
4 |
5 | bootstrapApplication(AppComponent, appConfig).catch((err) =>
6 | console.error(err)
7 | );
8 |
--------------------------------------------------------------------------------
/frontend/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/frontend/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": false,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": ["ES2022", "dom"]
23 | },
24 | "angularCompilerOptions": {
25 | "enableI18nLegacyMessageIdFormat": false,
26 | "strictInjectionParameters": true,
27 | "strictInputAccessModifiers": true,
28 | "strictTemplates": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------