Я работал над SPA с Angular 16, TypeScript и The Movie Database (TMDB).
Я сделал компонент, который отображает фильмы по жанрам:
import { Component } from '@angular/core';
import { GenreResponse, Genre } from '../../models/Genre';
import { MovieResponse, Movie } from '../../models/Movie';
import { MovieService } from '../../services/movie-service.service';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-movies-by-genre',
templateUrl: './movies-by-genre.component.html',
styleUrls: ['./movies-by-genre.component.scss']
})
export class MoviesByGenreComponent {
public movieResponse!: MovieResponse;
public movies: Movie[] | undefined = [];
public genreResponse!: GenreResponse;
public genres: Genre[] | undefined = [];
public genreName: string | undefined = '';
constructor(
private activatedRoute: ActivatedRoute,
private movieService: MovieService
) { }
public getMoviesByGenre(): void {
// Get genre id (from URL parameter)
const genre_id = Number(this.activatedRoute.snapshot.paramMap.get('id'));
// Get genre name from genres array
this.movieService.getAllMovieGenres().subscribe((response) => {
this.genreResponse = response;
this.genres = this.genreResponse.genres;
if (this.genres && this.genres.length) {
let currentGenre = this.genres.find(genre => genre.id === genre_id);
if (currentGenre) {
this.genreName = currentGenre.name;
}
}
});
// Get movies by genre id
this.movieService.getMoviesByGenre(genre_id).subscribe((response) => {
this.movieResponse = response;
this.movies = this.movieResponse.results;
})
}
ngOnInit() {
this.getMoviesByGenre();
}
}
В сервисе, используемом вышеуказанным компонентом, у меня есть:
import { environment } from '../../environments/environment';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { MovieResponse, Movie } from '../models/Movie';
import { GenreResponse } from '../models/Genre';
import { TrailerResponse } from '../models/Trailer';
@Injectable({
providedIn: 'root'
})
export class MovieService {
constructor(private http: HttpClient) { }
public getAllMovieGenres(): Observable<GenreResponse> {
return this.http.get<GenreResponse>(`${environment.apiUrl}/genre/movie/list?api_key=${environment.apiKey}`);
}
public getMoviesByGenre(id: Number): Observable<MovieResponse> {
return this.http.get<MovieResponse>(`${environment.apiUrl}/discover/movie?api_key=${environment.apiKey}&with_genres=${id}`);
}
}
В модуле маршрутизации у меня есть:
const routes: Routes = [
{
path: '',
component: HomePageComponent,
data: { title: 'Now playing', animation: 'isRight' },
},
{
path: 'by-genre/:id',
component: MoviesByGenreComponent,
data: { title: '', animation: 'isLeft' },
},
{
path: 'movie/:id',
component: MovieDetailsComponent,
data: { title: '', animation: 'isLeft' },
},
{
path: 'actor/:id',
component: ActorDetailsComponent,
data: { title: '', animation: 'isRight' },
},
{
path: '**',
component: NotFoundComponent,
data: { title: '', animation: 'isRight' },
},
];
Из модуля маршрутизации, если свойство title в объекте data не пусто, я отображаю заголовок страницы в app\app.component.html:
<div class = "container">
<h1 *ngIf = "title.length" class = "page-title text-success mt-2 mb-3">{{ title }}</h1>
<router-outlet></router-outlet>
</div>
Для MoviesByGenreComponent я хочу отправить динамически полученное название жанра (переменная genreName) из компонента в маршрутизатор и присвоить genreName свойству title.
Есть stackblitz со всем имеющимся у меня кодом.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете сохранить содержимое заголовка сообщения в сервисе и использовать функцию getFullTitle, чтобы всегда получать последнее значение. Единственное предостережение в отношении этого подхода — вам необходимо убедиться, что заголовок сервиса устанавливается в пустую строку каждый раз, когда компонент уничтожается!
фильм жанра.com.ts
import { Component } from '@angular/core';
import { GenreResponse, Genre } from '../../models/Genre';
import { MovieResponse, Movie } from '../../models/Movie';
import { MovieService } from '../../services/movie-service.service';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-movies-by-genre',
templateUrl: './movies-by-genre.component.html',
styleUrls: ['./movies-by-genre.component.scss'],
})
export class MoviesByGenreComponent {
public movieResponse!: MovieResponse;
public movies: Movie[] | undefined = [];
public genreResponse!: GenreResponse;
public genres: Genre[] | undefined = [];
public genreName: string | undefined = '';
constructor(
private activatedRoute: ActivatedRoute,
private movieService: MovieService
) {}
public getMoviesByGenre(): void {
// Get genre id (from URL parameter)
const genre_id = Number(this.activatedRoute.snapshot.paramMap.get('id'));
// Get genre name from genres array
this.movieService.getAllMovieGenres().subscribe((response) => {
this.genreResponse = response;
this.genres = this.genreResponse.genres;
if (this.genres && this.genres.length) {
let currentGenre = this.genres.find((genre) => genre.id === genre_id);
if (currentGenre) {
this.genreName = currentGenre.name || '';
this.movieService.postTitle = ` - ${this.genreName}`;
}
}
});
// Get movies by genre id
this.movieService.getMoviesByGenre(genre_id).subscribe((response) => {
this.movieResponse = response;
this.movies = this.movieResponse.results;
});
}
ngOnInit() {
this.movieService.postTitle = '';
this.getMoviesByGenre();
}
}
app.com.html
<app-top-bar></app-top-bar>
<div class = "container" [@routeAnimations] = "prepareRoute(outlet)">
<h1
*ngIf = "getFullTitle() as finalTitle"
class = "page-title text-success mt-2 mb-3"
>
{{ finalTitle }}
</h1>
<router-outlet #outlet = "outlet"></router-outlet>
</div>
<app-footer class = "mt-auto"></app-footer>
app.com.ts
import { Component } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { RouterOutlet } from '@angular/router';
import { slider } from './route-animations';
import { MovieService } from './services/movie-service.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [slider],
})
export class AppComponent {
public title: String = 'Movies';
constructor(
private route: ActivatedRoute,
private router: Router,
private movieService: MovieService
) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd && this.route.root.firstChild) {
this.title = this.route.root.firstChild.snapshot.data['title'];
}
});
}
public getFullTitle() {
return this.title + this.movieService.postTitle;
}
public prepareRoute(outlet: RouterOutlet) {
return (
outlet &&
outlet.activatedRouteData &&
outlet.activatedRouteData['animation']
);
}
}
Пожалуйста, ознакомьтесь с обновлением вопроса. Спасибо!
@RazvanZamfir, на мой взгляд, это лучший подход: мы не можем обновить родительский маршрутизатор, вместо этого мы можем просто использовать службу и отображать заголовок на основе переменной службы. Я не уверен, что не так с моим подходом!
В этом нет ничего плохого, но это может быть немного слишком сложно. Я оставлю вопрос открытым, на случай, если появятся лучшие идеи. Если их нет, я применю этот.
Я рефакторил ваш код в нескольких местах. Демо-версия Stackblitz была соответствующим образом обновлена. Большинство файлов содержат комментарии о том, как можно улучшить свой код.
Я не удалил ваш предыдущий код, чтобы вы могли сравнить его с отрефакторенной версией.
Проверяйте каждый файл или ищите новые комментарии.
В общем, Angular предоставляет различные способы обмена данными между компонентами . В вашем случае у вас должна быть возможность поделиться своими
genreNameданными из дочернего компонентаMoviesByGenreComponentс родительским компонентомAppComponentкак отношение родитель-потомок, описанное в следующем разделе: Обмен данными между дочерними и родительскими директивами и компонентами