简体   繁体   中英

Trying to pass object from api to child component using Input Decorator

I have a parent component that receives data from an api using a service. I need to pass this data to the child component, as I don't want to make another call to the api (strict rule). I can access the data in my parent component, but I don't get it in my child component. I've tried many different ways.

import { Component, OnInit, ViewChild } from '@angular/core';
import {ActivatedRoute} from "@angular/router";
import {SwapiService} from "../../models/swapi.service";
import {Starship} from "../../models/starship";
// import {MatTableDataSource} from '@angular/material/table';
// import {MatPaginator} from '@angular/material/paginator';


@Component({
  selector: 'app-starships',
  templateUrl: './starships.component.html',
  styleUrls: ['./starships.component.css']
})
export class StarshipsComponent implements OnInit {
  public starship: Starship[]
  url: string;

  constructor(private swapi: SwapiService, private route: ActivatedRoute) {


  }


  ngOnInit(): void {
  this.loadStarshipList();

  }
  loadStarshipList(): void {
    this.swapi.getStarshipList().subscribe(
      res => {
        this.starship = res;
        console.log(res);

      }
    )
  }

}

parent template

span *ngIf="starship">
<p *ngFor="let res of starship?.results" >{{res?.name}}
  <app-starship *ngFor="let res of starship.results" [data]="res?.results"></app-starship>
<!--  <span [res1]="res1"></span>-->
  <button routerLink="/starships/{{res?.url}}">Starship Details</button>
</p>
</span>

child component

import { Component, Input, OnInit } from '@angular/core';
// import {Starships} from "../../models/starship";
import {SwapiService} from "../../models/swapi.service";
import {ActivatedRoute} from "@angular/router";
import {StarshipsComponent} from "../starships/starships.component";

@Component({
  selector: 'app-starship',
  templateUrl: './starship.component.html',
  styleUrls: ['./starship.component.css']
})
export class StarshipComponent implements OnInit {
  // public starship: Starship[]
  private url: string;
  @Input() data;

  constructor(private swapi: SwapiService,
              private route: ActivatedRoute,
              private activatedRoute: ActivatedRoute,

  ) {
  }

  ngOnInit(): void {
    this.url = this.route.snapshot.paramMap.get('url');
    console.log(this.url);


  }

}

child template

<span *ngIf="data"></span>
<div *ngFor="let res of data">
  <h1>{{data?.name}}</h1>
  <p>{{data?.url}}</p>
</div>
hi

there is no response.

I am trying to use BehaviorSubject so I can share the data among several components. My problem is, I don't know how to transfer the data from my function to a variable so that I can use it in the other components.

this is my service file:

import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {BehaviorSubject, Observable} from "rxjs";
import {map, skipWhile, tap} from 'rxjs/operators';
import {Starship} from "./starship";


@Injectable({
  providedIn: 'root'
})
export class SwapiService {
  private apiData = new BehaviorSubject<Starship[]>(null)
  public currentData = this.apiData.asObservable()
  baseURL = 'https://swapi.dev/api/';

  constructor(private http: HttpClient) { }

  getStarshipList(page?: number): Observable<Starship[]> {
    return this.http.get<Starship[]>(`${this.baseURL}starships?format=json${this.getByPage(page)}`)
      .pipe(
        map(resp => resp['results']),)
    ;
  }

  getByPage(page: number): string {
    if (page) { return '&page=' + page; } else { return ''; }
  }


  setData(data) {
    this.apiData.next(data)
    }


}

how do I transfer the data received by getStarshipList to a variable, so I can then pass it using the setData function. I know it seems like a stupid questions, but I've tried many different ways and can't get it right.

@Abhinav Aggarwal

I figured out how to use it in both components without declaring the variable in the service, but I would still like to know

Note import required library, ex: Subject

Child component

@Input() data: BehaviorSubject<any>;
ngUnsubscribe: any = new Subject();
ngOnInit(){
this.listenParentDataChanges();
}
listenParentDataChanges() {
    if (this.data) {
      this.data.asObservable().takeUntil(this.ngUnsubscribe)
        .subscribe(responseData => {
           console.log("-data--",data);
        });
    } 
  }

Parent Component

shareData: BehaviorSubject<any> = new BehaviorSubject({});

below code write where you are receiving data from backend.

this.shareData.next({ data: responseData});

html

<app-starship *ngFor="let res of starship.results" [data]="shareData"></app-starship>

You can also use BehaviorSubject

service.ts

private sList = new BehaviorSubject<any>(null);

getStarshipList() {
return this.http.get(`url`).pipe(
  tap((data: any) => this.sList.next(data);
);
}

getSlist() {
return this.sList.asObservable().pipe(skipWhile(val => val === null));
}

parent.component.ts

ngOnInit(){
   this.loadStarshipList();

  }
  loadStarshipList(){
    this.swapi.getStarshipList().subscribe(
      res => {
        this.starship = res;
        console.log(res);

      }
    )
  }

child.component.ts

data:any;

ngOnInit()
this.swapi.getSList().subscribe(
      res => {
        this.data = res;
        console.log(res);

      }
    )
    }

You don't need multiple ngFor on parent template. Pass res directly to the child which has a result array into it.

<p *ngFor="let res of starship?.results" >{{res?.name}}
 <app-starship [data]="res"></app-starship>

Child component:

  ngOnInit(): void {
   console.log(this.data); //check you have data or not here
 }

Advice:- First understand how the lifecycle hooks works. And why angular has introduces ngOnChanges. Study lifecycle hooks and Change Detection.

You problem :- You are passing variable from parent to child. which on init stage of parent does not have data and is undefined. Which you have already passed to the child component hence you don't have data. Api gives data after both views are already created. and angular component does not know that data is changed.

Solution :-

Option 1 : You should create child Component when parent component receives the data.

or

Option 2 : Subscribe to the parent component for change detection

or

Option 3 : Do a change detection on both parent and child component.

In your child component ... after the ngOnInIt method use above code and import & implements ngOnChanges from angular/core, I hope it will work.

Imports : import { OnChanges } from '@angular/core';

Implements: export class StarshipComponent implements OnInit, OnChanges { }

ngOnChanges(changes: SimpleChanges): void {
   if (changes.data.currentValue) {
     this.data = changes.data.currentValue;
   }
}

  

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM