forked from jasder/angular-my-ap
"https"
This commit is contained in:
parent
21a6ff53d8
commit
cfc3c0a9e4
|
@ -2,35 +2,41 @@ import { NgModule } from '@angular/core';
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ComponentOverviewComponent } from './components/component-overview/component-overview.component';
|
||||
import { HeroesComponent } from './components/heroes/heroes.component';
|
||||
import { MessagesComponent } from './components/messages/messages.component';
|
||||
import { HeroDetailComponent } from './components/hero-detail/hero-detail.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
||||
|
||||
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
|
||||
import { InMemoryDataService } from './in-memory-data.service';
|
||||
import { InMemoryDataService } from './services/in-memory-data.service';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
||||
import { HeroDetailComponent } from './components/hero-detail/hero-detail.component';
|
||||
import { HeroesComponent } from './components/heroes/heroes.component';
|
||||
import { HeroSearchComponent } from './components/hero-search/hero-search.component';
|
||||
import { MessagesComponent } from './components/messages/messages.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ComponentOverviewComponent,
|
||||
HeroesComponent,
|
||||
MessagesComponent,
|
||||
HeroDetailComponent,
|
||||
DashboardComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
AppRoutingModule,
|
||||
HttpClientModule,
|
||||
|
||||
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
|
||||
// and returns simulated server responses.
|
||||
// Remove it when a real server is ready to receive requests.
|
||||
HttpClientInMemoryWebApiModule.forRoot(
|
||||
InMemoryDataService, { dataEncapsulation: false }
|
||||
)
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
declarations: [
|
||||
AppComponent,
|
||||
DashboardComponent,
|
||||
HeroesComponent,
|
||||
HeroDetailComponent,
|
||||
MessagesComponent,
|
||||
HeroSearchComponent
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
export class AppModule { }
|
|
@ -1,4 +1,9 @@
|
|||
<a *ngFor="let hero of heroes"
|
||||
routerLink="/detail/{{hero.id}}">
|
||||
{{hero.name}}
|
||||
</a>
|
||||
<h2>Top Heroes</h2>
|
||||
<div class="heroes-menu">
|
||||
<a *ngFor="let hero of heroes"
|
||||
routerLink="/detail/{{hero.id}}">
|
||||
{{hero.name}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<app-hero-search></app-hero-search>
|
|
@ -6,4 +6,5 @@
|
|||
<input id="hero-name" [(ngModel)]="hero.name" placeholder="Hero name"/>
|
||||
</div>
|
||||
<button (click)="goBack()">go back</button>
|
||||
<button (click)="save()">save</button>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { HeroService } from '../../services/hero.service';
|
|||
styleUrls: [ './hero-detail.component.scss' ]
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
@Input() hero?: Hero;
|
||||
hero: Hero;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
|
@ -24,12 +24,17 @@ export class HeroDetailComponent implements OnInit {
|
|||
}
|
||||
|
||||
getHero(): void {
|
||||
const id = Number(this.route.snapshot.paramMap.get('id'));
|
||||
this.heroService.getHero(id)
|
||||
const id = this.route.snapshot.paramMap.get('id');
|
||||
this.heroService.getHero(Number(id))
|
||||
.subscribe(hero => this.hero = hero);
|
||||
}
|
||||
|
||||
goBack(): void {
|
||||
this.location.back();
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.heroService.updateHero(this.hero)
|
||||
.subscribe(() => this.goBack());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<div id="search-component">
|
||||
<label for="search-box">Hero Search</label>
|
||||
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
|
||||
|
||||
<ul class="search-result">
|
||||
<li *ngFor="let hero of heroes$ | async" >
|
||||
<a routerLink="/detail/{{hero.id}}">
|
||||
{{hero.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -0,0 +1,47 @@
|
|||
/* HeroSearch private styles */
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: .5rem;
|
||||
|
||||
}
|
||||
input {
|
||||
padding: .5rem;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: #336699 auto 1px;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
.search-result li a {
|
||||
border-bottom: 1px solid gray;
|
||||
border-left: 1px solid gray;
|
||||
border-right: 1px solid gray;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: .5rem;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.search-result li a:hover {
|
||||
background-color: #435A60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
ul.search-result {
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HeroSearchComponent } from './hero-search.component';
|
||||
|
||||
describe('HeroSearchComponent', () => {
|
||||
let component: HeroSearchComponent;
|
||||
let fixture: ComponentFixture<HeroSearchComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ HeroSearchComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HeroSearchComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import {
|
||||
debounceTime, distinctUntilChanged, switchMap
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { Hero } from '../../interface/hero.interface';
|
||||
import { HeroService } from '../../services/hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-search',
|
||||
templateUrl: './hero-search.component.html',
|
||||
styleUrls: [ './hero-search.component.scss' ]
|
||||
})
|
||||
export class HeroSearchComponent implements OnInit {
|
||||
heroes$: Observable<Hero[]>;
|
||||
private searchTerms = new Subject<string>();
|
||||
|
||||
constructor(private heroService: HeroService) {}
|
||||
|
||||
// Push a search term into the observable stream.
|
||||
search(term: string): void {
|
||||
this.searchTerms.next(term);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.heroes$ = this.searchTerms.pipe(
|
||||
// wait 300ms after each keystroke before considering the term
|
||||
debounceTime(300),
|
||||
|
||||
// ignore new term if same as previous term
|
||||
distinctUntilChanged(),
|
||||
|
||||
// switch to new search observable each time the term changes
|
||||
switchMap((term: string) => this.heroService.searchHeroes(term)),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,21 @@
|
|||
<h2>My Heroes</h2>
|
||||
|
||||
<div>
|
||||
<label id="new-hero">Hero name: </label>
|
||||
<input for="new-hero" #heroName />
|
||||
|
||||
<!-- (click) passes input value to add() and then clears the input -->
|
||||
<button class="add-button" (click)="add(heroName.value); heroName.value=''">
|
||||
Add hero
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul class="heroes">
|
||||
<li *ngFor="let hero of heroes">
|
||||
<a routerLink="/detail/{{hero.id}}">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
|
||||
<li *ngFor="let hero of heroes">
|
||||
<a routerLink="/detail/{{hero.id}}">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</a>
|
||||
<button class="delete" title="delete hero"
|
||||
(click)="delete(hero)">x</button>
|
||||
</li>
|
||||
</ul>
|
|
@ -1,52 +1,89 @@
|
|||
/* HeroesComponent's private CSS styles */
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 15em;
|
||||
}
|
||||
.heroes li {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 0;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #2c3a41;
|
||||
background-color: #e6e6e6;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes li.selected {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #505050;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:active {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color:#405061;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: .5rem;
|
||||
}
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: .5rem;
|
||||
margin: 1rem 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.heroes li {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.heroes li:hover {
|
||||
left: .1em;
|
||||
}
|
||||
|
||||
.heroes a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.heroes a:hover {
|
||||
color: #2c3a41;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.heroes a:active {
|
||||
background-color: #525252;
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color:#405061;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
padding: .5rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
color: white;
|
||||
background-color: #42545C;
|
||||
}
|
||||
|
||||
button.delete {
|
||||
position: absolute;
|
||||
left: 210px;
|
||||
top: 5px;
|
||||
background-color: white;
|
||||
color: #525252;
|
||||
font-size: 1.1rem;
|
||||
padding: 1px 10px 3px 10px;
|
||||
}
|
||||
|
||||
button.delete:hover {
|
||||
background-color: #525252;
|
||||
color: white;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Hero } from '../../interface/hero.interface';
|
||||
import { HeroService } from '../../services/hero.service';
|
||||
import { MessageService } from '../../services/message.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-heroes',
|
||||
|
@ -9,23 +9,31 @@ import { MessageService } from '../../services/message.service';
|
|||
styleUrls: ['./heroes.component.scss']
|
||||
})
|
||||
export class HeroesComponent implements OnInit {
|
||||
heroes: Hero[];
|
||||
|
||||
selectedHero?: Hero;
|
||||
|
||||
heroes: Hero[] = [];
|
||||
|
||||
constructor(private heroService: HeroService, private messageService: MessageService) { }
|
||||
constructor(private heroService: HeroService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.getHeroes();
|
||||
}
|
||||
|
||||
onSelect(hero: Hero): void {
|
||||
console.log("heroes.component.ts excite onSelect")
|
||||
this.selectedHero = hero;
|
||||
this.messageService.add(`HeroesComponent: Selected hero id=${hero.id}`);
|
||||
}
|
||||
getHeroes(): void {
|
||||
this.heroService.getHeroes()
|
||||
.subscribe(heroes => this.heroes = heroes); }
|
||||
.subscribe(heroes => this.heroes = heroes);
|
||||
}
|
||||
|
||||
add(name: string): void {
|
||||
name = name.trim();
|
||||
if (!name) { return; }
|
||||
this.heroService.addHero({ name } as Hero)
|
||||
.subscribe(hero => {
|
||||
this.heroes.push(hero);
|
||||
});
|
||||
}
|
||||
|
||||
delete(hero: Hero): void {
|
||||
this.heroes = this.heroes.filter(h => h !== hero);
|
||||
this.heroService.deleteHero(hero.id).subscribe();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +1,122 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
|
||||
import { Hero } from '../interface/hero.interface';
|
||||
import { HEROES } from '../interface/mock-heroes';
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HeroService {
|
||||
|
||||
constructor(private messageService: MessageService) { }
|
||||
private heroesUrl = 'api/heroes'; // URL to web api
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||
};
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private messageService: MessageService) { }
|
||||
|
||||
/** GET heroes from the server */
|
||||
getHeroes(): Observable<Hero[]> {
|
||||
const heroes = of(HEROES);
|
||||
this.messageService.add('HeroService: fetched heroes');
|
||||
return heroes;
|
||||
return this.http.get<Hero[]>(this.heroesUrl)
|
||||
.pipe(
|
||||
tap(_ => this.log('fetched heroes')),
|
||||
catchError(this.handleError<Hero[]>('getHeroes', []))
|
||||
);
|
||||
}
|
||||
|
||||
/** GET hero by id. Return `undefined` when id not found */
|
||||
getHeroNo404<Data>(id: number): Observable<Hero> {
|
||||
const url = `${this.heroesUrl}/?id=${id}`;
|
||||
return this.http.get<Hero[]>(url)
|
||||
.pipe(
|
||||
map(heroes => heroes[0]), // returns a {0|1} element array
|
||||
tap(h => {
|
||||
const outcome = h ? `fetched` : `did not find`;
|
||||
this.log(`${outcome} hero id=${id}`);
|
||||
}),
|
||||
catchError(this.handleError<Hero>(`getHero id=${id}`))
|
||||
);
|
||||
}
|
||||
|
||||
/** GET hero by id. Will 404 if id not found */
|
||||
getHero(id: number): Observable<Hero> {
|
||||
// For now, assume that a hero with the specified `id` always exists.
|
||||
// Error handling will be added in the next step of the tutorial.
|
||||
const hero = HEROES.find(h => h.id === id) as Hero;
|
||||
this.messageService.add(`HeroService: fetched hero id=${id}`);
|
||||
return of(hero);
|
||||
const url = `${this.heroesUrl}/${id}`;
|
||||
return this.http.get<Hero>(url).pipe(
|
||||
tap(_ => this.log(`fetched hero id=${id}`)),
|
||||
catchError(this.handleError<Hero>(`getHero id=${id}`))
|
||||
);
|
||||
}
|
||||
|
||||
/* GET heroes whose name contains search term */
|
||||
searchHeroes(term: string): Observable<Hero[]> {
|
||||
if (!term.trim()) {
|
||||
// if not search term, return empty hero array.
|
||||
return of([]);
|
||||
}
|
||||
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
|
||||
tap(x => x.length ?
|
||||
this.log(`found heroes matching "${term}"`) :
|
||||
this.log(`no heroes matching "${term}"`)),
|
||||
catchError(this.handleError<Hero[]>('searchHeroes', []))
|
||||
);
|
||||
}
|
||||
|
||||
//////// Save methods //////////
|
||||
|
||||
/** POST: add a new hero to the server */
|
||||
addHero(hero: Hero): Observable<Hero> {
|
||||
return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
|
||||
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
|
||||
catchError(this.handleError<Hero>('addHero'))
|
||||
);
|
||||
}
|
||||
|
||||
/** DELETE: delete the hero from the server */
|
||||
deleteHero(id: number): Observable<Hero> {
|
||||
const url = `${this.heroesUrl}/${id}`;
|
||||
|
||||
return this.http.delete<Hero>(url, this.httpOptions).pipe(
|
||||
tap(_ => this.log(`deleted hero id=${id}`)),
|
||||
catchError(this.handleError<Hero>('deleteHero'))
|
||||
);
|
||||
}
|
||||
|
||||
/** PUT: update the hero on the server */
|
||||
updateHero(hero: Hero): Observable<any> {
|
||||
return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
|
||||
tap(_ => this.log(`updated hero id=${hero.id}`)),
|
||||
catchError(this.handleError<any>('updateHero'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Http operation that failed.
|
||||
* Let the app continue.
|
||||
* @param operation - name of the operation that failed
|
||||
* @param result - optional value to return as the observable result
|
||||
*/
|
||||
private handleError<T>(operation = 'operation', result?: T) {
|
||||
return (error: any): Observable<T> => {
|
||||
|
||||
// TODO: send the error to remote logging infrastructure
|
||||
console.error(error); // log to console instead
|
||||
|
||||
// TODO: better job of transforming error for user consumption
|
||||
this.log(`${operation} failed: ${error.message}`);
|
||||
|
||||
// Let the app keep running by returning an empty result.
|
||||
return of(result as T);
|
||||
};
|
||||
}
|
||||
|
||||
/** Log a HeroService message with the MessageService */
|
||||
private log(message: string) {
|
||||
this.messageService.add(`HeroService: ${message}`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InMemoryDataService } from './in-memory-data.service';
|
||||
|
||||
describe('InMemoryDataService', () => {
|
||||
let service: InMemoryDataService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(InMemoryDataService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
import { Hero } from '../interface/hero.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
const heroes = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
return {heroes};
|
||||
}
|
||||
|
||||
// Overrides the genId method to ensure that a hero always has an id.
|
||||
// If the heroes array is empty,
|
||||
// the method below returns the initial number (11).
|
||||
// if the heroes array is not empty, the method below returns the highest
|
||||
// hero id + 1.
|
||||
genId(heroes: Hero[]): number {
|
||||
return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue