Асинхронный вызов в angular с использованием генератора событий и служб для межкомпонентной связи - PullRequest
1 голос
/ 22 марта 2020

не может сохранить значение, полученное от метода подписки, в переменной шаблона.

компонент детали фотографии

import { Component, OnInit, Input } from "@angular/core";
import { PhotoSevice } from "../photo.service";
import { Photo } from "src/app/model/photo.model";

@Component({
  selector: "app-photo-detail",
  templateUrl: "./photo-detail.component.html",
  styleUrls: ["./photo-detail.component.css"]
})
export class PhotoDetailComponent implements OnInit {

  url: string;

  constructor(private photoService: PhotoSevice) {
    this.photoService.photoSelected.subscribe(data => {
      this.url = data;
      console.log(this.url);
    });
    console.log(this.url);
  }

  ngOnInit() {

  }
}

внешняя консоль .log дает неопределенное значение, и в представлении ничего не отображается, но внутри метода subscibe я вижу значение. Итак, как я могу отобразить его в моем представлении?

компонент фотографии

import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { FnParam } from "@angular/compiler/src/output/output_ast";
import { AlbumService } from "../service/album.service";
import { Photo } from "../model/photo.model";
import { PhotoSevice } from "./photo.service";

@Component({
  selector: "app-photos",
  templateUrl: "./photos.component.html",
  styleUrls: ["./photos.component.css"]
})
export class PhotosComponent implements OnInit {
  selectedAlbumId: string;
  photoList: Photo[] = [];
  photoSelected: Photo;
  isLoading: Boolean;
  constructor(
    private rout: ActivatedRoute,
    private albumService: AlbumService,
    private router: Router,
    private photoService: PhotoSevice
  ) { }

  ngOnInit() {
    this.isLoading = true;
    this.rout.params.subscribe((params: Params) => {
      this.selectedAlbumId = params["id"];
      this.getPhotos(this.selectedAlbumId);
    });
  }

  getPhotos(id: string) {
    this.albumService.fetchPhotos(this.selectedAlbumId).subscribe(photo => {
      this.photoList = photo;
      this.isLoading = false;
    });
  }

  displayPhoto(url: string, title: string) {

    console.log(url);
    this.photoService.photoSelected.emit(url);
    this.router.navigate(["/photo-detail"]);
  }
}

, пожалуйста, объясните мне, как это работает и как обойти это, чтобы я мог сохранить и отобразить значение, полученное от подписки и асинхронного вызова в представлении шаблона.

вот представления из двух компонентов ---

photo.component. html

<div *ngIf="isLoading">
  <h3>Loading...</h3>
</div>

<div class="container" *ngIf="!isLoading">
  <div class="card-columns">
    <div *ngFor="let photo of photoList" class="card">
      <img
        class="card-img-top"
        src="{{ photo.thumbnailUrl }}"
        alt="https://source.unsplash.com/random/300x200"
      />
      <div class="card-body">
        <a
          class="btn btn-primary btn-block"
          (click)="displayPhoto(photo.url, photo.title)"
          >Enlarge Image</a
        >
      </div>
    </div>
  </div>
</div>

photo-detail.component.ts

<div class="container">
  <div class="card-columns">
    <div class="card">
      <img class="card-img-top" src="{{ url }}" />
    </div>
  </div>
</div>

photo.service.ts

import { Injectable } from "@angular/core";
import { EventEmitter } from "@angular/core";

@Injectable({ providedIn: "root" })
export class PhotoSevice {
  photoSelected = new EventEmitter();
 // urlService: string;
}

Вот ссылка на мой репозиторий Github, я сохранил код в комментариях и использовал там другой подход. Если вы проверите там компонент «альбомы», я также подписался на запрос http и присвоил значение в переменной шаблона компонента «альбомы». там также значение приходит как неопределенное вышестоящее метод subscibe, но я могу получить к нему доступ в шаблоне.

https://github.com/Arpan619Banerjee/angular-accelerate

вот подробности компонента и обслуживания альбомов Пожалуйста, сравните это с делом источника событий и объясните мне, в чем разница-- album.component.ts

import { Component, OnInit } from "@angular/core";
import { AlbumService } from "../service/album.service";
import { Album } from "../model/album.model";

@Component({
  selector: "app-albums",
  templateUrl: "./albums.component.html",
  styleUrls: ["./albums.component.css"]
})
export class AlbumsComponent implements OnInit {
  constructor(private albumService: AlbumService) {}
  listAlbums: Album[] = [];
  isLoading: Boolean;

  ngOnInit() {
    this.isLoading = true;
    this.getAlbums();
  }

  getAlbums() {
    this.albumService.fetchAlbums().subscribe(data => {
      this.listAlbums = data;
      console.log("inside subscibe method-->" + this.listAlbums); // we have data here
      this.isLoading = false;
    });
    console.log("outside subscribe method----->" + this.listAlbums); //empty list==== but somehow we have the value in the view , this doesn t work
    //for my photo and photo-detail component.
  }
}

album.component . html

<div *ngIf="isLoading">
  <h3>Loading...</h3>
</div>
<div class="container" *ngIf="!isLoading">
  <h3>Albums</h3>
  <app-album-details
    [albumDetail]="album"
    *ngFor="let album of listAlbums"
  ></app-album-details>
</div>

album.service.ts

import { Injectable } from "@angular/core";
import { HttpClient, HttpParams } from "@angular/common/http";
import { map, tap } from "rxjs/operators";
import { Album } from "../model/album.model";
import { Observable } from "rxjs";
import { UserName } from "../model/user.model";

@Injectable({ providedIn: "root" })
export class AlbumService {
  constructor(private http: HttpClient) {}
  albumUrl = "http://jsonplaceholder.typicode.com/albums";
  userUrl = "http://jsonplaceholder.typicode.com/users?id=";
  photoUrl = "http://jsonplaceholder.typicode.com/photos";

  //get the album title along with the user name
  fetchAlbums(): Observable<any> {
    return this.http.get<Album[]>(this.albumUrl).pipe(
      tap(albums => {
        albums.map((album: { userId: String; userName: String }) => {
          this.fetchUsers(album.userId).subscribe((user: any) => {
            album.userName = user[0].username;
          });
        });
        // console.log(albums);
      })
    );
  }

  //get the user name of the particular album with the help of userId property in albums
  fetchUsers(id: String): Observable<any> {
    //let userId = new HttpParams().set("userId", id);
    return this.http.get(this.userUrl + id);
  }

  //get the photos of a particular album using the albumId
  fetchPhotos(id: string): Observable<any> {
    let selectedId = new HttpParams().set("albumId", id);
    return this.http.get(this.photoUrl, {
      params: selectedId
    });
  }
}

enter image description here

Я добавил журналы консоли в четных источниках, как сказано в комментариях, и это поведение, которое я получил, которое ожидается.

Ответы [ 2 ]

1 голос
/ 22 марта 2020

Вопрос двухсторонний.

Часть 1 - фотографии и фото-детали

  1. EventEmitter is используется для передачи переменных, оформленных декоратором @Output из дочернего компонента (не service ) в родительский компонент. Затем он может быть связан с родительским компонентом в своем шаблоне . Простой и хороший пример можно найти здесь . Обратите внимание на (notify)="receiveNotification($event)" в шаблоне компонента приложения.

  2. В вашем случае использование Subject или BehaviorSubject является лучшая идея. Разницу между ними можно найти в моем другом ответе здесь . Попробуйте следующий код

photo.service.ts

import { Injectable } from "@angular/core";
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: "root" })
export class PhotoSevice {
  private photoSelectedSource = new BehaviorSubject<string>(undefined);

  public setPhotoSelected(url: string) {
    this.photoSelectedSource.next(url);
  }

  public getPhotoSelected() {
    return this.photoSelectedSource.asObservable();
  }
}

photos.component.ts

export class PhotosComponent implements OnInit {
  .
  .
  .
  displayPhoto(url: string, title: string) {
    this.photoService.setPhotoSelected(url);
    this.router.navigate(["/photo-detail"]);
  }
}

photo-detail.component. ts

  constructor(private photoService: PhotoSevice) {
    this.photoService.getPhotoSelected().subscribe(data => {
      this.url = data;
      console.log(this.url);
    });
    console.log(this.url);
  }

photo-detail.component. html

<ng-container *ngIf="url">
  <div class="container">
    <div class="card-columns">
      <div class="card">
        <img class="card-img-top" [src]="url"/>
      </div>
    </div>
  </div>
</ng-container>

Часть 2 - Компоненты и обслуживание альбомов

Вызов this.albumService.fetchAlbums() возвращает наблюдаемый ответ HTTP GET. Вы подписываетесь на него и обновляете значение переменной-члена и используете его в шаблоне.

Из вашего комментария к другому ответу:

Я понимаю поведение и почему внешняя консоль .log недоопределен, потому что контекст выполнения diff для вызовов asyn c, и он сначала выполняет код syn c, а затем приходит asyn c code

Боюсь, что Разница между синхронным и асинхронным вызовом не так проста. Пожалуйста, смотрите здесь для хорошего объяснения разницы между ними.

album.components.ts

  getAlbums() {
    this.albumService.fetchAlbums().subscribe(data => {
      this.listAlbums = data;
      console.log("inside subscibe method-->" + this.listAlbums); // we have data here
      this.isLoading = false;
    });
    console.log("outside subscribe method----->" + this.listAlbums); //empty list==== but somehow we have the value in the view , this doesn t work
    //for my photo and photo-detail component.
  }

album.component. html

<div *ngIf="isLoading">
  <h3>Loading...</h3>
</div>
<div class="container" *ngIf="!isLoading">
  <h3>Albums</h3>
  <app-album-details
    [albumDetail]="album"
    *ngFor="let album of listAlbums"
  ></app-album-details>
</div>

Вопрос заключался в том, чтобы объяснить, почему шаблон отображает альбомы, несмотря на console.log("outside subscribe method----->" + this.listAlbums); печать undefined. Проще говоря, когда вы делаете вне консольного журнала, this.listAlbums на самом деле undefined в том смысле, что он еще не был инициализирован. Но в шаблоне есть проверка загрузки *ngIf="!isLoading". А из кода контроллера, isLoading устанавливается только на false, когда listAlbums назначено значение. Поэтому, когда вы устанавливаете isLoading на false, гарантируется, что listAlbums содержит данные для отображения.

1 голос
/ 22 марта 2020

Я думаю, что вы пытаетесь отобразить выбранное изображение в компоненте детализации фотографий, который заставляет фотографию отображаться из службы.

В вопросе не упоминается, как создается компонент детализации фотографий.

  1. Создан ли компонент после того, как пользователь выбрал фотографию для отображения?
  2. Создан ли компонент еще до того, как пользователь выбрал фотографию для отображения?

I думаю, что первое, что вы пытаетесь сделать. Если это так, есть две вещи ...

  1. Когда вы подписываетесь внутри конструктора, код внутри подписки запускается через некоторое время, когда наблюдаемое излучает. в то же время будет запущен код после подписки, т.е. console.log (url) (внешний), и поэтому он будет неопределенным.

  2. Если подписка происходит после события т.е. вы отправили событие с URL, но к тому времени компонент не подписался на сервисное событие. так что событие потеряно и вы ничего не получите. Для этого вы можете сделать несколько вещей

    a. Добавьте фотографию, данные которой должны отображаться в URL, и добавьте ее в компонент сведений о фотографии.

    b. Преобразование субъекта / источника событий в сервисе в поведенческий субъект. Это гарантирует, что даже если вы подпишетесь в более позднее время, вы все равно получите событие, которое было отправлено последним.

    c. Если компонент сведений о фотографии находится внутри шаблона компонента фотографий, отправьте URL-адрес в качестве входного параметра (привязка @Input ()).

Надеюсь, это поможет

...