Ya no hay ningun error, pero no se ve nada cuando ejecuto
Si la aplicación compila sin errores pero no muestra nada cuando la ejecutas, hay varios aspectos que debemos revisar. Vamos a solucionar este problema paso a paso:
Primero, asegúrate de que el archivo app.component.html esté incluyendo correctamente los componentes:
<div class="app-container">
<app-header></app-header>
<main class="main-content">
<app-wanted-person-list></app-wanted-person-list>
</main>
<app-footer></app-footer>
</div>Asegúrate de que todos los componentes estén correctamente importados en app.component.ts:
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { TranslateModule } from '@ngx-translate/core';
// Importa los componentes
import { HeaderComponent } from './domains/public/components/header/header.component';
import { FooterComponent } from './domains/public/components/footer/footer.component';
import { WantedPersonListComponent } from './domains/wanted/components/wanted-person-list/wanted-person-list.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [
CommonModule,
TranslateModule,
HeaderComponent,
FooterComponent,
WantedPersonListComponent
],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor(private translate: TranslateService) {}
ngOnInit() {
// Set default language
this.translate.setDefaultLang('en');
this.translate.use('en');
}
}Revisa el servicio que obtiene los datos de la API. Asegúrate de que esté manejando correctamente las respuestas y errores:
// src/app/domains/wanted/services/wanted-person.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, catchError, map, of } from 'rxjs';
import { WantedPerson } from '../model/wanted-person.model';
import { WantedPersonAssemblerService } from './wanted-person-assembler.service';
@Injectable({
providedIn: 'root'
})
export class WantedPersonService {
private apiUrl = 'https://api.fbi.gov/wanted/v1/list';
constructor(
private http: HttpClient,
private assembler: WantedPersonAssemblerService
) { }
getWantedPersons(): Observable<any> {
return this.http.get(this.apiUrl)
.pipe(
map((response: any) => {
console.log('API Response:', response); // Añade este log para depuración
return {
total: response.total,
page: response.page,
items: this.assembler.toWantedPersons(response.items)
};
}),
catchError(error => {
console.error('Error fetching data:', error);
return of({ total: 0, page: 0, items: [] });
})
);
}
}Asegúrate de que el componente wanted-person-list.component.ts esté importando correctamente los módulos necesarios y manejando los datos:
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatButtonModule } from '@angular/material/button';
import { TranslateModule } from '@ngx-translate/core';
import { WantedPersonService } from '../../services/wanted-person.service';
import { WantedPerson } from '../../model/wanted-person.model';
import { WantedPersonCardComponent } from '../wanted-person-card/wanted-person-card.component';
@Component({
selector: 'app-wanted-person-list',
standalone: true,
imports: [
CommonModule,
MatProgressSpinnerModule,
MatButtonModule,
TranslateModule,
WantedPersonCardComponent
],
templateUrl: './wanted-person-list.component.html',
styleUrls: ['./wanted-person-list.component.css']
})
export class WantedPersonListComponent implements OnInit {
wantedPersons: WantedPerson[] = [];
loading = true;
error = false;
constructor(private wantedPersonService: WantedPersonService) { }
ngOnInit(): void {
console.log('WantedPersonListComponent initialized');
this.loadWantedPersons();
}
loadWantedPersons(): void {
this.loading = true;
this.error = false;
console.log('Loading wanted persons...');
this.wantedPersonService.getWantedPersons()
.subscribe({
next: (response) => {
console.log('Received response:', response);
this.wantedPersons = response.items;
this.loading = false;
console.log('Wanted persons loaded:', this.wantedPersons.length);
},
error: (err) => {
console.error('Error loading wanted persons', err);
this.error = true;
this.loading = false;
}
});
}
}Asegúrate de que el HTML del componente de lista esté mostrando correctamente los datos:
<div class="wanted-list-container">
<h2 class="section-title">{{ 'APP.SUBTITLE' | translate }}</h2>
<div *ngIf="loading" class="loading-container">
<mat-spinner aria-label="Loading wanted persons data"></mat-spinner>
<p>Loading data...</p>
</div>
<div *ngIf="error" class="error-container">
<p>Error loading data. Please try again later.</p>
<button mat-raised-button color="primary" (click)="loadWantedPersons()">Retry</button>
</div>
<div *ngIf="!loading && !error && wantedPersons.length === 0" class="empty-container">
<p>No wanted persons found.</p>
</div>
<div *ngIf="!loading && !error && wantedPersons.length > 0" class="cards-grid">
<div *ngFor="let person of wantedPersons" class="card-container">
<app-wanted-person-card [wantedPerson]="person"></app-wanted-person-card>
</div>
</div>
</div>Asegúrate de que HttpClient esté correctamente configurado en app.config.ts:
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideAnimations } from '@angular/platform-browser/animations';
import { HttpClient, HttpClientModule, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
// Factory function for TranslateHttpLoader
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAnimations(),
provideHttpClient(withInterceptorsFromDi()),
importProvidersFrom(
HttpClientModule,
TranslateModule.forRoot({
defaultLanguage: 'en',
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
)
]
};Abre las herramientas de desarrollo del navegador (F12) y revisa la consola para ver si hay errores. Esto puede darte pistas sobre lo que está fallando.
Es posible que la API del FBI tenga restricciones CORS. Verifica en la consola del navegador si hay errores relacionados con CORS. Si es así, podrías necesitar configurar un proxy en tu aplicación Angular.
Crea un archivo proxy.conf.json en la raíz del proyecto:
{
"/api": {
"target": "https://api.fbi.gov",
"secure": true,
"changeOrigin": true,
"pathRewrite": {
"^/api": ""
}
}
}Luego modifica el servicio para usar esta ruta:
private apiUrl = '/api/wanted/v1/list';Y actualiza el comando de inicio en package.json:
"start": "ng serve --proxy-config proxy.conf.json",Si continúas teniendo problemas con la API, puedes implementar una solución temporal con datos estáticos para verificar que el resto de la aplicación funciona correctamente:
// En wanted-person.service.ts
getWantedPersons(): Observable<any> {
// Datos de ejemplo
const mockData = {
total: 2,
page: 1,
items: [
{
id: '1',
title: 'John Doe',
description: 'Wanted for bank robbery',
images: [{ original: 'https://via.placeholder.com/300', thumb: 'https://via.placeholder.com/100', large: 'https://via.placeholder.com/500' }],
sex: 'Male',
status: 'Wanted',
details: 'Armed and dangerous',
warning_message: 'Do not approach',
publication: '2025-01-01',
poster_classification: 'Most Wanted',
url: 'https://www.fbi.gov'
},
{
id: '2',
title: 'Jane Smith',
description: 'Wanted for fraud',
images: [{ original: 'https://via.placeholder.com/300', thumb: 'https://via.placeholder.com/100', large: 'https://via.placeholder.com/500' }],
sex: 'Female',
status: 'Wanted',
details: 'May be armed',
warning_message: 'Call authorities if seen',
publication: '2025-02-01',
poster_classification: 'Wanted',
url: 'https://www.fbi.gov'
}
]
};
// Retorna los datos de ejemplo
return of(mockData).pipe(
map(response => ({
total: response.total,
page: response.page,
items: this.assembler.toWantedPersons(response.items)
}))
);
}Asegúrate de que los estilos CSS no estén ocultando los elementos. Verifica styles.css y los archivos de estilo de los componentes.
Siguiendo estos pasos, deberías poder identificar y solucionar el problema que impide que tu aplicación muestre contenido. Si después de implementar estas soluciones sigues teniendo problemas, por favor proporciona más detalles sobre lo que ves en la consola del navegador.
El inglés y español no cambia y dónde consigo el logo del FBI
El problema con el cambio de idioma podría deberse a varias razones. Vamos a revisar y corregir la implementación:
Primero, asegúrate de que el servicio de traducción esté correctamente configurado en app.config.ts:
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideAnimations } from '@angular/platform-browser/animations';
import { HttpClient, HttpClientModule, provideHttpClient } from '@angular/common/http';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
// Factory function for TranslateHttpLoader
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAnimations(),
provideHttpClient(),
importProvidersFrom(
HttpClientModule,
TranslateModule.forRoot({
defaultLanguage: 'en',
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
)
]
};Asegúrate de que el componente Header esté implementando correctamente el cambio de idioma:
// src/app/domains/public/components/header/header.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { TranslateModule } from '@ngx-translate/core';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-header',
standalone: true,
imports: [
CommonModule,
TranslateModule,
MatToolbarModule,
MatButtonModule
],
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent {
constructor(private translate: TranslateService) {
console.log('Current language:', this.translate.currentLang);
}
/**
* Change the current language
* @param lang - Language code (en or es)
*/
changeLanguage(lang: string): void {
console.log('Changing language to:', lang);
this.translate.use(lang);
}
}Asegúrate de que los botones de cambio de idioma estén correctamente implementados:
<!-- src/app/domains/public/components/header/header.component.html -->
<mat-toolbar color="primary">
<div class="toolbar-container">
<div class="toolbar-left">
<img src="assets/fbi-logo.png" alt="FBI Logo" class="logo" aria-hidden="true">
<span class="title">{{ 'APP.TITLE' | translate }}</span>
</div>
<div class="toolbar-right">
<button mat-button (click)="changeLanguage('en')" aria-label="Switch to English">EN</button>
<span>|</span>
<button mat-button (click)="changeLanguage('es')" aria-label="Switch to Spanish">ES</button>
</div>
</div>
</mat-toolbar>Asegúrate de que los archivos de traducción estén correctamente ubicados y formateados:
Ruta: src/assets/i18n/en.json
{
"APP": {
"TITLE": "FBI Wanted Persons",
"SUBTITLE": "All Wanted Persons Information",
"FOOTER": {
"COPYRIGHT": "Copyright © 2025 FBI. All rights reserved.",
"DEVELOPED_BY": "Developed by"
}
},
"WANTED": {
"CARD": {
"VIEW_DETAILS": "View Details",
"DESCRIPTION": "Description",
"STATUS": "Status",
"SEX": "Sex",
"WARNING": "Warning",
"DETAILS": "Details",
"CLASSIFICATION": "Classification"
}
}
}Ruta: src/assets/i18n/es.json
{
"APP": {
"TITLE": "Personas Buscadas por el FBI",
"SUBTITLE": "Información de Todas las Personas Buscadas",
"FOOTER": {
"COPYRIGHT": "Copyright © 2025 FBI. Todos los derechos reservados.",
"DEVELOPED_BY": "Desarrollado por"
}
},
"WANTED": {
"CARD": {
"VIEW_DETAILS": "Ver Detalles",
"DESCRIPTION": "Descripción",
"STATUS": "Estado",
"SEX": "Sexo",
"WARNING": "Advertencia",
"DETAILS": "Detalles",
"CLASSIFICATION": "Clasificación"
}
}
}Añade logs para verificar que el cambio de idioma se está ejecutando correctamente:
// En header.component.ts
changeLanguage(lang: string): void {
console.log('Changing language to:', lang);
this.translate.use(lang);
// Verificar después de un breve retraso
setTimeout(() => {
console.log('Current language after change:', this.translate.currentLang);
}, 100);
}Para obtener el logo del FBI, tienes varias opciones:
Puedes descargar el logo oficial del FBI desde su sitio web:
src/assets/fbi-logo.pngSi no puedes obtener el logo oficial, puedes usar un placeholder:
<!-- En header.component.html -->
<div class="toolbar-left">
<div class="fbi-logo">FBI</div>
<span class="title">{{ 'APP.TITLE' | translate }}</span>
</div>/* En header.component.css */
.fbi-logo {
background-color: #0d47a1;
color: white;
font-weight: bold;
padding: 8px;
border-radius: 4px;
margin-right: 16px;
}Puedes crear un logo simple usando CSS:
<!-- En header.component.html -->
<div class="toolbar-left">
<div class="fbi-logo">
<span class="fbi-text">FBI</span>
</div>
<span class="title">{{ 'APP.TITLE' | translate }}</span>
</div>/* En header.component.css */
.fbi-logo {
display: flex;
align-items: center;
justify-content: center;
background-color: #0d47a1;
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 16px;
}
.fbi-text {
color: white;
font-weight: bold;
font-size: 14px;
}Puedes usar una imagen de stock o un servicio de generación de logos:
src/assets/fbi-logo.pngPuedes usar servicios como Font Awesome o Material Icons para crear un icono que represente al FBI:
<!-- En header.component.html -->
<div class="toolbar-left">
<mat-icon class="fbi-icon">security</mat-icon>
<span class="title">{{ 'APP.TITLE' | translate }}</span>
</div>/* En header.component.css */
.fbi-icon {
margin-right: 16px;
font-size: 24px;
height: 24px;
width: 24px;
}Asegúrate de importar MatIconModule en tu componente:
import { MatIconModule } from '@angular/material/icon';
@Component({
// ...
imports: [
// ...
MatIconModule
],
// ...
})Implementa estas soluciones para resolver los problemas con el cambio de idioma y el logo del FBI. Si sigues teniendo problemas con el cambio de idioma, revisa la consola del navegador para ver si hay errores relacionados con la carga de los archivos de traducción.
Changing language to: es
header.component.ts:32
GET http://localhost:4200/assets/i18n/es.json 404 (Not Found)
scheduleTask @ zone.js:2183
scheduleTask @ zone.js:388
onScheduleTask @ core.mjs:6055
scheduleTask @ zone.js:382
onScheduleTask @ zone.js:271
scheduleTask @ zone.js:382
scheduleTask @ zone.js:205
scheduleMacroTask @ zone.js:228
scheduleMacroTaskWithCurrentZone @ zone.js:691
(anonymous) @ zone.js:2222
proto.<computed> @ zone.js:1003
(anonymous) @ module-BHk9jdTn.mjs:2495
Observable2._trySubscribe @ Observable.js:33
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ switchMap.js:16
OperatorSubscriber2._this._next @ OperatorSubscriber.js:14
Subscriber2.next @ Subscriber.js:32
(anonymous) @ innerFrom.js:51
Observable2._trySubscribe @ Observable.js:33
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ switchMap.js:12
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ finalize.js:5
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ finalize.js:5
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
doInnerSub @ mergeInternals.js:21
outerNext @ mergeInternals.js:15
OperatorSubscriber2._this._next @ OperatorSubscriber.js:14
Subscriber2.next @ Subscriber.js:32
(anonymous) @ innerFrom.js:51
Observable2._trySubscribe @ Observable.js:33
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
mergeInternals @ mergeInternals.js:54
(anonymous) @ mergeMap.js:20
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ filter.js:6
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ map.js:6
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ share.js:72
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ take.js:9
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
(anonymous) @ take.js:9
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:27
errorContext @ errorContext.js:23
Observable2.subscribe @ Observable.js:23
use @ ngx-translate-core.mjs:452
changeLanguage @ header.component.ts:32
HeaderComponent_Template_button_click_12_listener @ header.component.html:11
executeListenerWithErrorHandling @ core.mjs:29228
wrapListenerIn_markDirtyAndPreventDefault @ core.mjs:29211
(anonymous) @ dom_renderer-DGKzginR.mjs:709
invokeTask @ zone.js:402
(anonymous) @ core.mjs:6060
onInvokeTask @ core.mjs:6060
invokeTask @ zone.js:401
onInvokeTask @ core.mjs:6380
invokeTask @ zone.js:401
runTask @ zone.js:159
invokeTask @ zone.js:483
invokeTask @ zone.js:1138
globalCallback @ zone.js:1169
globalZoneAwareCallback @ zone.js:1202Understand this error
header.component.ts:32 ERROR HttpErrorResponse {headers: _HttpHeaders, status: 404, statusText: 'Not Found', url: 'http://localhost:4200/assets/i18n/es.json', ok: false, …}