await fetch Angular between components via Input - javascript

I'm making a request with fetch to the reqres api users in app.component, then i share data to his child component (hello.component) via Input. I get the correct user names in child template, I'm trying to print the users in console but i get an empty array. It's there a way to 'await' the response of another component? i guess this is an asynchronous issue. Here is the link: https://stackblitz.com/edit/angular-ivy-b3m1kp?file=src%2Fapp%2Fhello.component.ts
Thanks in advance.
app.component:
import { Component, VERSION, OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public usuarios;
constructor(){
this.usuarios = [];
}
ngOnInit(){
this.getUsers();
}
getUsers(){
fetch('https://reqres.in/api/users')
.then(data => data.json())
.then(users => {
this.usuarios = users.data;
console.log(this.usuarios);
});
}
}
hello.component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1 *ngFor="let user of usuarios">Hello {{user.first_name}} </h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent{
#Input() usuarios;
ngOnInit(){
console.log(this.usuarios);
}
}
app.component.html:
<hello [usuarios]="usuarios"></hello>

As the fetch operation is asynchronous, usuarios array would be empty upon initialization for the child. To detect the value changes move logic which will use the fetched results to ngOnChanges.
Like this:
ngOnChanges(changes: SimpleChanges) {
const { previousValue, currentValue } = changes.usuarios;
if (previousValue !== currentValue) {
console.log(this.usuarios);
}
}
Having a condition to check if the value has changed inside ngOnChanges is essential, otherwise the logic will be constantly triggered.

<hello *ngIf="usuarios.length>0" [usuarios]="usuarios"></hello>
<p>
Start editing to see some magic happen :)
</p>

Related

Observable input is null in child component. Why?

I am facing a situation I don't understand. I have a parent component (app.component in the example) that gets its data from an API as an observable
This data is then passed down to the child (hello.component) using the async pipe.
That child then receives the input, but when ngOnInit runs in the child, the input is null.
I don't understand why, and I don't know how to make it so that the input is the actual returned value from the API instead. The call to detectChanges() in app.component was a desperate attempt to trigger change detection in the child but that doesn't re-run ngOnInit so it's kinda moot. I left it there because that's how the actual code I'm working with looked like.
I know this code is terrible.I didn't write it. Unfortunately, the component I'm working with is like that and I can't refactor it because, you guessed it, there are no unit tests. I'm working on cleaning it all up, but for now I have to reuse that component the way it is.
Stackblitz: https://stackblitz.com/edit/angular-ivy-rw1xte?devtoolsheight=33&file=src/app/hello.component.ts
// app.component.ts
import { ChangeDetectorRef, Component, VERSION } from "#angular/core";
import { interval, Observable, of } from "rxjs";
import { mapTo, tap } from "rxjs/operators";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
public name$: Observable<any> = of({});
constructor(private cdRef: ChangeDetectorRef) {}
public ngOnInit() {
this.name$ = interval(3000).pipe(
mapTo(() => {
first: "Eunice";
}),
tap(() => this.cdRef.detectChanges())
);
}
}
<!-- app.component.html -->
<hello [name]="name$ | async"></hello>
<p>
Start editing to see some magic happen :)
</p>
// hello.component.ts
import { Component, Input } from "#angular/core";
#Component({
selector: "hello",
template: `
<div *ngIf="name">
<h1>Hello {{ this.name.first }}!</h1>
<h1>{{ greetings }}</h1>
</div>
`,
styles: [
`
h1 {
font-family: Lato;
}
`
]
})
export class HelloComponent {
#Input() name: { first?: string }
public greetings: string = "";
public firstName: string = "";
public async ngOnInit() {
console.log('name:',this.name); // name: null
if (this.name) {
// this conditional is always false because this.name is always null
// and so this never runs.
console.log("got name");
this.firstName = this.name.first || "fallback";
this.greetings = await new Promise(resolve =>
resolve("how do you do, ${firstName}?")
);
}
}
}
this.name$ = interval(3000).pipe(
mapTo({
first: "Eunice"
}),
tap(() => this.cdRef.detectChanges())
);
Please fix it like the above. mapTo doesn't need to accept a function. It didnt make an error for you? :)
Also, the name input is null right when the hello component is mounted.
You need to check the name in ngOnChanges or you need to mount the hello component only when the name is available.
For example:
<hello *ngIf="name$ | async as name" [name]="name"></hello>
Or
public async ngOnChanges() {
console.log('name:', this.name); // name: null
if (this.name) {
...
}
}

How do I pass data from one component to another (New Browser Tab) in angular?

I'm new to angular and I don't know how to pass data between two components using routers. This is my first component view,
when I press view report button I need to call another component with the first component data. This is my first component view report click button code.
<button type="button" (click)="onFuelViewReport()" class="btn btn-success ">
<b>view Report</b>
</button>
when clicking the button it calls onFuelViewReport() function in the first component and using this function it opens the second component view with a new browser window (tab). What I want is to pass data from the first component to the second component from here. Please help me to do this.
onFuelViewReport() {
this.router.navigate([]).then(result => {
window.open("/pages/view-report", "_blank");
});
}
If you want to share data from child component to parent component, you can use #Output event emitter or if your are trying to share data within unrelated components, you can use BehaviourSubject (This also works in case of parent to child component communication and vice versa).
Child to Parent: Sharing Data via Output() and EventEmitter
parent.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
child.component.ts
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "Hola Mundo!"
#Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
Unrelated Components: Sharing Data with a Service
data.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject('default message');
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
parent.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-parent',
template: `
{{message}}
`,
styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
}
sibling.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-sibling',
template: `
{{message}}
<button (click)="newMessage()">New Message</button>
`,
styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
newMessage() {
this.data.changeMessage("Hello from Sibling")
}
}
The window.open looks absolutely awful. Use this.router.navigate(['/heroes']);.
So if I understand correctly you have a list of items and when you click on one of the items, the details page of that item should open?
Best practice is to allow the detail route to have a property to set. the Angular Routing & Navigation page is very complete. It shows that you should use :id - { path: 'hero/:id', component: HeroDetailComponent }. When you open the detail page, you get the id variable and then get the data for it.

Trigger a change to children components in Angular 2, even though the data/state is not changes

I have this parent components, that has a Boolean attribute, that can be set by user through a button in UI.
I have a recursive set of children components, a tree in fact, that need to response to change of that Boolean attribute, even when the value of that Boolean attribute is not changed, but user has pressed that button.
In other words, even if that Boolean attribute is false, and user clicks that button, and because of some logic the Boolean attribute is not changed at all and remains false, I still want the children components to be notified.
I'm using ngOnChanges and I'm trying to trigger the event manually.
But in case of false to false, which means no change, the ngOnChanges on children components is not called.
What do I miss here?
You can pass a Subject as an input to the child components.
In your parent component, create a Subject, like so:
import { Component } from '#angular/core';
import { Subject } from 'rxjs';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
subject = new Subject<string>();
handleClick() {
// This will send a message via the subject
this.subject.next("TEST");
}
// Complete the subject when your component is destroyed to avoid memory leaks
ngOnDestroy() {
this.subject.complete();
}
}
And pass it to the child component:
<hello [notifier]="subject"></hello>
In your child component, subscribe to the Subject, and do whatever you need to do in the subscribe callback:
import { Component, Input } from '#angular/core';
import { Subject } from 'rxjs';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
#Input() notifier: Subject<any>;
ngOnInit() {
if (this.notifier != null) {
this.notifier.subscribe((event) => {
// This will be logged every time the subject.next is called.
console.log("HelloComponent heard", event);
})
}
}
}
Here is a StackBlitz example

Fetch the data from GITHUB API

I want to get all the data from github API. But it doesn't work for me.
My .ts file is below:
import { Component } from '#angular/core';
import { GitTakeService } from "app/git-take.service";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
user:any;
constructor(private gittakeService:GitTakeService ){
this.gittakeService.getUser().subscribe(user=>{
debugger;
this.user=user;
console.log(user);
})
}
}
My service is below:
import { Injectable } from '#angular/core';
import {Http,Response, Headers} from '#angular/http'
import'rxjs/add/operator/map';
#Injectable()
export class GitTakeService {
constructor(private http:Http) { }
getUser(){
debugger;
return this.http.get("http://api.github.com/users")
.map(
(resp:Response)=>{
return resp.json().response;
}
);
}
}
When consoling the user in .ts file, it shows undefined. My view file is like this:
{{user}}
Anyone please help me to solve this problem?
What you are receiving is an array, so you want to use resp.json() instead of resp.json().response there is no such property like response in your response. So your map should look like this:
getUser(){
debugger;
return this.http.get("http://api.github.com/users")
.map((resp:Response)=>{
return resp.json();
});
}
and in your component I would name the array users instead of user, since there are several users in your response. Also I suggest you keep anything unnecessary from the constructor and use OnInit instead:
users = [];
constructor(private gittakeService:GitTakeService ){ }
ngOnInit() {
this.gittakeService.getUser()
.subscribe(data => {
this.users = data;
});
}
Then you can iterate the array and use the property names to show the properties of one user object:
<div *ngFor="let user of users">
{{user.login}}
</div>
resp.json().response is undefined resp.json() is what you want
the service function:
getUser(){
return this.http.get("http://api.github.com/users")
.map(
(resp:Response)=>{
return resp.json();
}
);
}`
and the component:
this.gittakeService.getUser().subscribe(users=>{
this.user=users[0];
console.log(user);
})

Making a reusable angular2 component that can be used anywhere on the a site

Use Case: When making asynchronous calls, I want to show some sort of a processing screen so that end users knows something is happening rather than just staring at the screen. Since I have multiple places throughout the site where I want to use this, I figured making it a component at the "global" level is the best approach.
Problem: Being slightly new to angular2, I'm not getting if this is a problem of it being outside the directory in which the main component exists and the OverlayComponent being in another location or if I'm just all together doing it wrong. I can get the component to work fine but I need to be able to call functions to hide/destroy the component and also display the component. I have tried making it a service but that didn't get me any further so I'm back to square one. Essentially my question revolves around building a reusable component that has methods to hide/show itself when invoked from whatever component it's being called from.
Below is my current code:
Assume OverlayComponent.html is at /public/app/templates/mysite.overlay.component.html
Assume OverlayComponent.ts is at /public/app/ts/app.mysite.overlay.component
Assume mysite.tracker.component is at \public\app\ts\pages\Tracker\mysite.tracker.component.ts
OverlayComponent.html
<div class="overlay-component-container">
<div class="overlay-component" (overlay)="onShowOverlay($event)">
<div>{{processingMessage}}</div>
<div>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
</div>
</div>
OverlayComponent.ts
import { Component } from '#angular/core';
#Component({
selector: 'overlay-component',
templateUrl: '/public/app/templates/mysite.overlay.component.html',
styleUrls: ['public/app/scss/overlay.css']
})
export class OverlayComponent {
onShowOverlay(e) {
$('.overlay-component').fadeIn(1000);
}
hideOverlay(e) {
$('.overlay-component').fadeOut(1000);
}
}
TrackerComponent.ts
import { Component, Output, OnInit, EventEmitter } from '#angular/core';
import { Http } from '#angular/http';
import { TrackerService } from './Tracker.service';
import { MenuCollection } from "./MenuCollection";
import { Menu } from "./Menu";
#Component({
moduleId: module.id,
selector: 'tracker-component',
templateUrl: '/public/app/templates/pages/tracker/mysite.tracker.component.html',
styleUrls: ['../../../scss/pages/racker/tracker.css'],
providers: [TrackerService]
})
export class TrackerComponent implements OnInit{
MenuCollection: MenuCollection;
#Output()
overlay: EventEmitter<any> = new EventEmitter();
constructor(private http: Http, private TrackerService: TrackerService) {
let c = confirm("test");
if (c) {
this.onShowOverlay();
}
}
ngOnInit(): void {
this.MenuCollection = new MenuCollection();
this.MenuCollection.activeMenu = new Menu('Active Menu', []);
this.TrackerService.getTrackerData().then(Tracker => {
this.MenuCollection = Tracker;
this.MenuCollection.activeMenu = this.MenuCollection.liveMenu;
console.log(this.MenuCollection);
},
error => {
alert('error');
})
}
onShowOverlay() { //This doesn't seem to 'emit' and trigger my overlay function
this.overlay.emit('test');
}
}
At a high level, all I'm wanting to do is invoke a components function from another component. Thanks in advance for any helpful input
You can use the #ContentChild annotation to accomplish this:
import { Component, ContentChild } from '#angular/core';
class ChildComponent {
// Implementation
}
// this component's template has an instance of ChildComponent
class ParentComponent {
#ContentChild(ChildComponent) child: ChildComponent;
ngAfterContentInit() {
// Do stuff with this.child
}
}
For more examples, check out the #ContentChildren documentation.

Categories

Resources