使用事件发射器和服务进行跨组件通信的角度异步调用 [英] Asynchronous call in angular using event emitter and services for cross-component communication

查看:66
本文介绍了使用事件发射器和服务进行跨组件通信的角度异步调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

无法将从订阅方法接收的值存储在模板变量中。



照片细节组件

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

@Component({
选择器: app-photo-detail,
templateUrl: ./ photo-detail.component.html,
styleUrls: [ ./photo-detail.component.css]
})
导出类PhotoDetailComponent实现OnInit {

url:string;

构造函数(私有photoService:PhotoSevice){
this.photoService.photoSelected.subscribe(data => {
this.url = data;
console.log (this.url);
});
console.log(this.url);
}

ngOnInit(){

}
}

外部console.log给出了未定义的内容,并且视图中未呈现任何内容,但是在subscibe方法内部,我可以看到该值。那么,如何在视图中显示它? p>

照片组件

  import {组件, OnInit}来自 @ angular / core; 
import {ActivatedRoute,Params,Router} from @ angular / router;
从{@ angular / compiler / src / output / output_ast导入{FnParam};
import {AlbumService} from ../service/album.service;
从 ../model/photo.model中导入{图片};
import {PhotoSevice} from ./photo.service;

@Component({
选择器: app-photos,
templateUrl: ./ photos.component.html,
styleUrls:[ ./ photos.component.css]
})
导出类PhotosComponent实现OnInit {
selectedAlbumId:string;
photoList:照片[] = [];
photoSelected:照片;
isLoading:布尔值;
构造函数(
私有路由:ActivatedRoute,
私有相册服务:AlbumService,
私有路由器: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 =照片;
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>正在加载...< / h3>
< / div>

< div class = container * ngIf =!isLoading>
< div class = card-columns>
< div * ngFor =让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)
>放大图片< ; / 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

 从 @ angular / core导入{可注入}; 
从 @ angular / core导入{EventEmitter};

@ Injectable({provedIn: root})
导出类PhotoSevice {
photoSelected = new EventEmitter();
// urlService:字符串;
}

这是我的github存储库的链接,我一直保存注释中的代码,并在那里使用了不同的方法。
如果您检查相册组件,那么我也已经订阅了http请求,并在相册组件的模板变量中分配了值。
那里的值也随着未定义的subscibe方法而来,但是我能够在模板中访问它。



用于从子组件 (不是组件)发出由 @Output 装饰器修饰的变量。 > service )添加到父组件。然后可以通过 template 中的父组件对其进行绑定。可以在此处找到一个简单而良好的示例。注意应用程序组件模板中的(notify)= receiveNotification($ event)


  • 对于您的案例,使用 主题 BehaviorSubject 是一个更好的主意。它们之间的区别可以在我的其他答案此处中找到。尝试以下代码


  • photo.service.ts

      import {Injectable} from @ angular / core; 
    从 rxjs导入{BehaviorSubject};

    @ Injectable({provedIn: root})
    导出类PhotoSevice {
    private photoSelectedSource = new BehaviorSubject< string>(未定义);

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

    public getPhotoSelected(){
    返回this.photoSelectedSource.asObservable();
    }
    }

    photos.component.ts

     导出类PhotosComponent实现了OnInit {



    displayPhoto(url:string,title:string){
    this.photoService.setPhotoSelected(url);
    this.router.navigate([ / photo-detail]);
    }
    }

    photo-detail.component.ts

     构造函数(私有photoService:PhotoSevice){
    this.photoService.getPhotoSelected()。subscribe(data => {
    this.url =数据;
    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响应。您正在订阅它,并更新成员变量值,并在模板中使用它。



    从对另一个答案的评论中:


    我了解其行为以及为什么外部console.log的
    定义不够,其原因是异步调用
    的执行上下文不同,因此它首先执行同步代码,然后是异步代码


    恐怕同步和异步调用之间的区别并不那么简单。请参阅此处以了解它们之间的区别。



    albums.components.ts

      getAlbums(){
    this.albumService.fetchAlbums ().subscribe(data => {
    this.listAlbums = data;
    console.log( inside subscibe method-> + this.listAlbums); //我们这里有数据
    this.isLoading = false;
    });
    console.log(外部订阅方法-----> + this.listAlbums); //空列表=====但是以某种方式我们在视图中具有该值,因此对于我的照片和照片细节组件,这不起作用

    }

    albums.component.html

     < div * ngIf = isLoading> 
    < h3>正在加载...< / h3>
    < / div>
    < div class = container * ngIf =!isLoading>
    < h3>相册< / h3>
    < app-album-details
    [albumDetail] = album
    * ngFor =让listAlbums的相册
    >< / app-album-details>
    < / div>

    问题是要解释为何尽管 console.log该模板仍显示专辑(外部订阅方法-----> + this.listAlbums); 打印未定义。简而言之,当您在外部控制台日志中进行操作时, this.listAlbums 实际上是 undefined ,因为还没有尚未初始化。但是在模板中,有一个加载检查 * ngIf =!isLoading 。并且从控制器代码中,当 listAlbums isLoading 仅设置为 false 。 $ c>被分配一个值。因此,当您将 isLoading 设置为 false 时,可以确保 listAlbums 包含要显示的数据。


    cannot store the value received from subscribe method in a template variable.

    photo-detail component

    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() {
    
      }
    }
    

    the outside console.log gives undefined, and nothing is rendered in the view, but inside the subscibe method i can see the value.So, how can i display it in my view?

    photos component

    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"]);
      }
    }
    

    please explain me how this works and how to work around it so that i can store and display the value received from subscribing and asynchronous call in a template view.

    here are the views of the two components---

    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;
    }
    
    

    here is a link to my github repo, i have kept the code in comments and used a different approach there. If you check the albums component there also i have subscribed to http request and assigned the value in the template variable of albums component. there also the value comes as undefined oustide the subscibe method, but i am able to access it in template.

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

    here are the details of albums component and service pls compare this with the event emitter case and explain me whats the difference-- albums.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.
      }
    }
    
    

    albums.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
        });
      }
    }
    

    I have added console logs in the even emitters as told in the comments and this is the behavior i got which is expected.

    解决方案

    Question's a two-parter.

    Part 1 - photos and photo-detail component

    1. EventEmitter is used to emit variables decorated with a @Output decorator from a child-component (not a service) to parent-component. It can then be bound to by the parent component in it's template. A simple and good example can be found here. Notice the (notify)="receiveNotification($event)" in app component template.

    2. For your case, using a Subject or a BehaviorSubject is a better idea. Difference between them can be found in my other answer here. Try the following code

    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>
    

    Part 2 - albums component and service

    The call this.albumService.fetchAlbums() returns a HTTP GET Response observable. You are subscribing to it and updating the member variable value and using it in the template.

    From your comment on the other answer:

    i understand the behaviour and why the outside console.log is underfined, its beacuse the execution context is diff for async calls and it first executes the sync code and then comes the async code

    I am afraid the difference between synchronous and asynchronous call is not as simple as that. Please see here for a good explanation of difference between them.

    albums.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.
      }
    

    albums.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>
    

    The question was to explain why the template displays the albums despite console.log("outside subscribe method----->" + this.listAlbums); printing undefined. In simple words, when you do outside console log, this.listAlbums is actually undefined in that it hasn't been initialized yet. But in the template, there is a loading check *ngIf="!isLoading". And from the controller code, isLoading is only set to false when listAlbums is assigned a value. So when you set isLoading to false it is assured that listAlbums contains the data to be shown.

    这篇关于使用事件发射器和服务进行跨组件通信的角度异步调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆