Reworked frontend to be CRUD compliant and added tests

This commit is contained in:
2022-09-26 06:28:41 +02:00
parent d25981f5ec
commit 0d4622991a
33 changed files with 425 additions and 175 deletions

View File

@@ -2,6 +2,7 @@ plugins {
id 'org.springframework.boot' version '2.7.3'
id 'io.spring.dependency-management' version '1.0.13.RELEASE'
id 'java'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
group = 'dev.cato447'
@@ -28,6 +29,14 @@ dependencies {
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation group: 'org.springframework.restdocs', name: 'spring-restdocs-mockmvc', version: '2.0.6.RELEASE'
}
asciidoctor {
sourceDir('build/generated-snippets')
outputDir('build/docs')
}
tasks.named('test') {

View File

@@ -1,13 +1,61 @@
package dev.cato447.semesteressen;
import dev.cato447.semesteressen.domain.Event;
import dev.cato447.semesteressen.repository.EventRepository;
import dev.cato447.semesteressen.service.EventService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
@SpringBootTest
class SemesterEssenApplicationTests {
private MockMvc mockMvc;
@MockBean
private EventRepository eventRepository;
@BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document("{method-name}",preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
.build();
}
@Test
void contextLoads() {
public void eventExample() throws Exception {
List<Event> eventList = new LinkedList<>();
eventList.add(new Event("Erstiessen #1", false, LocalDateTime.parse("2022-11-12T19:00:00"), 25));
when(eventRepository.findAll()).thenReturn(eventList);
this.mockMvc.perform(get("/api/events")).andExpect(status().isOk())
.andDo(document("event"));
}
}

View File

@@ -23,6 +23,7 @@
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.css"
],
"scripts": []
@@ -90,6 +91,7 @@
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.css"
],
"scripts": []

View File

@@ -9,10 +9,12 @@
"version": "0.0.0",
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/cdk": "^14.2.2",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/material": "^14.2.2",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
@@ -343,6 +345,28 @@
"@angular/core": "14.2.3"
}
},
"node_modules/@angular/cdk": {
"version": "14.2.2",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.2.2.tgz",
"integrity": "sha512-PXEnhX+QDOsmHVVnqTuoGaK7Wn9hFd5kWAmHTTU7lZr3XVu/AtDcEU+LB19wOFU0fY+kSYHMgN+BYo1TiR8vbw==",
"dependencies": {
"tslib": "^2.3.0"
},
"optionalDependencies": {
"parse5": "^5.0.0"
},
"peerDependencies": {
"@angular/common": "^14.0.0 || ^15.0.0",
"@angular/core": "^14.0.0 || ^15.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/cdk/node_modules/parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
},
"node_modules/@angular/cli": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.3.tgz",
@@ -475,6 +499,23 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material": {
"version": "14.2.2",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-14.2.2.tgz",
"integrity": "sha512-jVCaESSTTkLjRvMzSQj294s0Lz1YMVFkl0svrMtWgkUMXHEfx2Vjw6FXdrVrBXlxEIrpfhkTEXVN2DC1kkAkQw==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": "^14.0.0 || ^15.0.0",
"@angular/cdk": "14.2.2",
"@angular/common": "^14.0.0 || ^15.0.0",
"@angular/core": "^14.0.0 || ^15.0.0",
"@angular/forms": "^14.0.0 || ^15.0.0",
"@angular/platform-browser": "^14.0.0 || ^15.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/platform-browser": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.3.tgz",
@@ -12101,6 +12142,23 @@
"tslib": "^2.3.0"
}
},
"@angular/cdk": {
"version": "14.2.2",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.2.2.tgz",
"integrity": "sha512-PXEnhX+QDOsmHVVnqTuoGaK7Wn9hFd5kWAmHTTU7lZr3XVu/AtDcEU+LB19wOFU0fY+kSYHMgN+BYo1TiR8vbw==",
"requires": {
"parse5": "^5.0.0",
"tslib": "^2.3.0"
},
"dependencies": {
"parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
}
}
},
"@angular/cli": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.3.tgz",
@@ -12179,6 +12237,14 @@
"tslib": "^2.3.0"
}
},
"@angular/material": {
"version": "14.2.2",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-14.2.2.tgz",
"integrity": "sha512-jVCaESSTTkLjRvMzSQj294s0Lz1YMVFkl0svrMtWgkUMXHEfx2Vjw6FXdrVrBXlxEIrpfhkTEXVN2DC1kkAkQw==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/platform-browser": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.3.tgz",

View File

@@ -11,10 +11,12 @@
"private": true,
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/cdk": "^14.2.2",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/material": "^14.2.2",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
@@ -35,4 +37,4 @@
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2"
}
}
}

View File

@@ -1,12 +1,15 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { EventListComponent } from './event-list/event-list.component';
import { EventFormComponent } from './event-form/event-form.component';
import { EventListComponent } from './views/event-list/event-list.component';
import { EventFormComponent } from './views/event-create/event-create.component';
import { EventUpdateComponent } from './views/event-update/event-update.component';
const routes: Routes = [
{path: "events", component: EventListComponent},
{path: "create", component: EventFormComponent}
{path: '', pathMatch: 'full', redirectTo: 'event-list'},
{path: "create-event", component: EventFormComponent},
{path: "event-list", component: EventListComponent},
{path: "event-edit/:id", component: EventUpdateComponent},
];
@NgModule({

View File

@@ -6,10 +6,10 @@
<h2 class="card-title text-center text-white py-3">{{ title }}</h2>
<ul class="text-center list-inline py-3">
<li class="list-inline-item">
<a routerLink="/events" class="btn btn-info">Show Events</a>
<a routerLink="/event-list" class="btn btn-info">Show Events</a>
</li>
<li class="list-inline-item">
<a routerLink="/create" class="btn btn-info">Create Events</a>
<a routerLink="/create-event" class="btn btn-info">Create Events</a>
</li>
</ul>
</div>

View File

@@ -1,24 +1,31 @@
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { EventListComponent } from './event-list/event-list.component';
import { EventFormComponent } from './event-form/event-form.component';
import { EventService } from './service/event-service.service';
import { EventListComponent } from './views/event-list/event-list.component';
import { EventFormComponent } from './views/event-create/event-create.component';
import { EventService } from './shared/service/event-service.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { EventUpdateComponent } from './views/event-update/event-update.component';
@NgModule({
declarations: [
AppComponent,
EventListComponent,
EventFormComponent
EventFormComponent,
EventUpdateComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule
FormsModule,
ReactiveFormsModule,
BrowserAnimationsModule
],
providers: [EventService],
bootstrap: [AppComponent]

View File

@@ -1,46 +0,0 @@
<div class="card my-5">
<div class="card-body">
<form (ngSubmit)="onSubmit()" #eventForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" [(ngModel)]="event.name"
class="form-control"
id="name"
name="name"
placeholder="Enter event name"
required #name="ngModel">
</div>
<div [hidden]="!name.pristine" class="alert alert-danger">Event name is required</div>
<div class="form-group">
<label for="published">Is published</label>
<input type="checkbox" [(ngModel)]="event.published"
class="form-control"
id="published"
name="published"
#published="ngModel">
</div>
<div class="form-group">
<label for="dateTime">DateTime</label>
<input type="text" [(ngModel)]="event.dateTime"
class="form-control"
id="dateTime"
name="dateTime"
placeholder="yyyy-MM-ddTHH:MM:SS"
required #dateTime="ngModel">
</div>
<div [hidden]="!dateTime.pristine" class="alert alert-danger">DateTime is requiered</div>
<div class="form-group">
<label for="maxParticipants">Maximum Participants</label>
<input type="number" [(ngModel)]="event.maxParticipants"
class="form-control"
id="maxParticipants"
name="maxParticipants"
placeholder=10
required #maxParticipants="ngModel">
</div>
<div [hidden]="!maxParticipants.pristine" class="alert alert-danger">Max Participants is requiered</div>
<button type="submit" [disabled]="!eventForm.form.valid"
class="btn btn-info">Create new event</button>
</form>
</div>
</div>

View File

@@ -1,27 +0,0 @@
import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { EventService } from '../service/event-service.service';
import { Event } from '../module/event';
@Component({
selector: 'app-event-form',
templateUrl: './event-form.component.html',
styleUrls: ['./event-form.component.css']
})
export class EventFormComponent {
event: Event;
constructor(private route: ActivatedRoute, private router: Router, private eventService: EventService) {
this.event = new Event();
}
onSubmit() {
this.eventService.save(this.event).subscribe(result => this.gotoUserList());
}
gotoUserList() {
this.router.navigate(['/events'])
}
}

View File

@@ -1,24 +0,0 @@
<div class="card my-5">
<div class="card-body">
<table class="table table-bordered table-striped">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Published</th>
<th scope="col">Datetime</th>
<th scope="col">MaxParticipants</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let event of events">
<td>{{ event.id }}</td>
<td>{{ event.name }}</td>
<td>{{ event.published }}</td>
<td>{{ event.dateTime}}</td>
<td>{{ event.maxParticipants }}</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -1,24 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Event } from '../module/event';
import { EventService } from '../service/event-service.service';
@Component({
selector: 'app-event-list',
templateUrl: './event-list.component.html',
styleUrls: ['./event-list.component.css']
})
export class EventListComponent implements OnInit {
events: Event[];
constructor(private eventService: EventService) {
this.events = [];
}
ngOnInit(): void {
this.eventService.findAll().subscribe(data => {
this.events = data;
})
}
}

View File

@@ -1,7 +0,0 @@
import { Event } from './event';
describe('Event', () => {
it('should create an instance', () => {
expect(new Event()).toBeTruthy();
});
});

View File

@@ -1,7 +0,0 @@
export class Event {
id!: string;
name!: string;
published!: boolean;
dateTime!: string;
maxParticipants!: number;
}

View File

@@ -1,22 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Event } from '../module/event';
import { Observable } from 'rxjs';
@Injectable()
export class EventService {
private eventUrl: string;
constructor(private http: HttpClient) {
this.eventUrl = "http://localhost:8080/api/events"
}
public findAll(): Observable<Event[]> {
return this.http.get<Event[]>(this.eventUrl)
}
public save(event: Event) {
return this.http.post<Event>(this.eventUrl, event);
}
}

View File

@@ -0,0 +1,7 @@
export interface Event {
id: string,
name: string,
published: boolean,
dateTime: string,
maxParticipants: number
}

View File

@@ -0,0 +1,57 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Event } from '../models/event';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class EventService {
apiUrl = "http://localhost:8080/api";
constructor(private http: HttpClient) {}
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
}),
};
getEvents(): Observable<Event[]> {
return this.http.get<Event[]>(this.apiUrl + '/events').pipe(retry(1), catchError(this.handleError));
}
getEvent(id: any): Observable<Event> {
return this.http.get<Event>(this.apiUrl + '/events/' + id).pipe(retry(1), catchError(this.handleError));
}
createEvent(event: any): Observable<Event> {
return this.http.post<Event>(this.apiUrl + '/events', JSON.stringify(event), this.httpOptions).pipe(retry(1), catchError(this.handleError));
}
updateEvent(id: any, event: any): Observable<Event> {
return this.http.put<Event>(this.apiUrl + '/events/' + id, JSON.stringify(event), this.httpOptions).pipe(retry(1), catchError(this.handleError));
}
deleteEvent(id: any): Observable<Event> {
return this.http.delete<Event>(this.apiUrl + '/events/' + id, this.httpOptions).pipe(retry(1), catchError(this.handleError));
}
// Error handling
handleError(error: any) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Get client-side error
errorMessage = error.error.message;
} else {
// Get server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
window.alert(errorMessage);
return throwError(() => {
return errorMessage;
});
}
}

View File

@@ -0,0 +1,20 @@
<div class="container custom-container">
<div class="col-md-12">
<h3 class="mb-3 text-center">Create Event</h3>
<div class="form-group">
<input type="text" [(ngModel)]="eventDetails.name" class="form-control" placeholder="Name">
</div>
<div class="form-group">
<input type="checkbox" [(ngModel)]="eventDetails.published" class="form-control">
</div>
<div class="form-group">
<input type="text" [(ngModel)]="eventDetails.dateTime" class="form-control" placeholder="yyyy-MM-ddThh:mm:ss">
</div>
<div class="form-group">
<input type="number" [(ngModel)]="eventDetails.maxParticipants" class="form-control" placeholder=0>
</div>
<div class="form-group">
<button class="btn btn-success btn-lg btn-block" (click)="addEvent(eventDetails)">Create Event</button>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EventFormComponent } from './event-form.component';
import { EventFormComponent } from './event-create.component';
describe('EventFormComponent', () => {
let component: EventFormComponent;

View File

@@ -0,0 +1,22 @@
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { EventService } from '../../shared/service/event-service.service';
@Component({
selector: 'app-event-create',
templateUrl: './event-create.component.html',
styleUrls: ['./event-create.component.css']
})
export class EventFormComponent implements OnInit {
@Input() eventDetails = {name: '', published: false, dateTime: '', maxParticipants: 0};
constructor(private route: ActivatedRoute, private router: Router, private eventService: EventService) {
}
ngOnInit(): void {}
addEvent(dataEvent: any){
this.eventService.createEvent(this.eventDetails).subscribe((data: {}) => {this.router.navigate(['/event-list'])});
}
}

View File

@@ -0,0 +1,43 @@
<div class="container custom-container-2">
<!-- Show it when there is no employee -->
<div class="no-data text-center" *ngIf="Event.length == 0">
<p>There are no events yet!</p>
<button class="btn btn-outline-primary" routerLink="/create">
Add Event
</button>
</div>
<!-- Events list table, it hides when there is no Events -->
<div *ngIf="Event.length !== 0">
<h3 class="mb-3 text-center">Event List</h3>
<div class="col-md-12">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">Event Id</th>
<th scope="col">Name</th>
<th scope="col">Published</th>
<th scope="col">Datetime</th>
<th scope="col">MaxParticipants</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let event of Event">
<td>{{ event.id }}</td>
<td>{{ event.name }}</td>
<td>{{ event.published }}</td>
<td>{{ event.dateTime }}</td>
<td>{{ event.maxParticipants }}</td>
<td>
<span class="edit" routerLink="/event-edit/{{ event.id }}"
>Edit</span>
<span class="delete" (click)="deleteEvent(event.id)"
>Delete</span
>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import { Event } from '../../shared/models/event';
import { EventService } from '../../shared/service/event-service.service';
@Component({
selector: 'app-event-list',
templateUrl: './event-list.component.html',
styleUrls: ['./event-list.component.css'],
})
export class EventListComponent implements OnInit {
Event: any = [];
constructor(private eventService: EventService) {}
ngOnInit(): void {
this.loadEvents();
}
loadEvents() {
return this.eventService.getEvents().subscribe((data: {}) => {
this.Event = data;
})
}
deleteEvent(id: any){
if(window.confirm('Are you sure, you want to delete?')) {
this.eventService.deleteEvent(id).subscribe((data) => {
this.loadEvents();
});
}
}
}

View File

@@ -0,0 +1,23 @@
<div class="container custom-container">
<div class="col-md-12">
<h3 class="mb-3 text-center">Update Event</h3>
<div class="form-group">
<input type="text" [(ngModel)]="eventData.name" class="form-control" placeholder="Name">
</div>
<div class="form-group">
<input type="checkbox" [(ngModel)]="eventData.published" class="form-control">
</div>
<div class="form-group">
<input type="text" [(ngModel)]="eventData.dateTime" class="form-control" placeholder="yyyy-MM-ddThh:mm:ss">
</div>
<div>
<input type="number" [(ngModel)]="eventData.maxParticipants" class="form-control" placeholder=0>
</div>
<div class="form-group">
<button class="btn btn-success btn-lg btn-block" (click)="updateEvent()">Update Event</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EventUpdateComponent } from './event-update.component';
describe('EventUpdateComponent', () => {
let component: EventUpdateComponent;
let fixture: ComponentFixture<EventUpdateComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EventUpdateComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(EventUpdateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,34 @@
import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { EventService } from 'src/app/shared/service/event-service.service';
@Component({
selector: 'app-event-update',
templateUrl: './event-update.component.html',
styleUrls: ['./event-update.component.css']
})
export class EventUpdateComponent implements OnInit {
id = this.actRoute.snapshot.params['id'];
eventData: any = {};
constructor(
public eventService: EventService,
public actRoute: ActivatedRoute,
public router: Router,
) { }
ngOnInit(): void {
this.eventService.getEvent(this.id).subscribe((data: {}) => {
this.eventData = data;
})
}
updateEvent() {
if(window.confirm('Are you sure, you want to update?')) {
this.eventService.updateEvent(this.id, this.eventData).subscribe((data) => {
this.router.navigate(['/event-list'])
})
}
}
}

View File

@@ -10,8 +10,11 @@
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<body class="mat-typography">
<app-root></app-root>
</body>
</html>
</html>

View File

@@ -1 +1,4 @@
/* You can add global styles to this file, and also import other style files */
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }

View File

@@ -2,6 +2,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"lib": ["es5", "es6", "dom", "dom.iterable"],
"outDir": "./out-tsc/app",
"types": []
},