I have a component that's displaying a value in {{getData}} if I fire the onDo() with a button click. onDo() is initiating a GET request to a server.
The server is giving back the response, and I am subscribing it. I am assigning the subscribed value to this.getData and being able to show it successfully in my component template with {{getData}}.
I can also get a print of the data in the console inside .subscribe().
But I tried to print it outside the .subscribe() and it's not displaying anything.
What I actually want to do is: assign the value of getData to MyVar.
Here is my code:
#Component({
template: `
...
<p>{{getData}}</p>
...
`,
providers: [MyService]
})
export class MyComponent{
getData: string;
MyVar: string;
constructor(private _myService: MyService, private _router: Router){}
onDo(){
this._myService.getSomething()
.subscribe(
data => this.getData = JSON.stringify(data),
error => this._router.navigate(['error']),
() => console.log('Get data inside subscribe: '+ this.getData)
);
console.log('Get data outside subscribe'+this.getData);
this.MyVar = this.getData;
}
}
This code
data => this.getData = JSON.stringify(data),
is a function that is passed to subscribe(...) to be called when the data arrives from the server.
You console.log(...) is called immediately after above function is passed to subscribe, but that doesn't mean the data is already there.
Just move the code inside subscribe(...) and it will work as expected.
Related
I have an Angular Scroller component
<app-scroller></app-scroller>
that provides a skeleton for displaying an array of images
Random Component
<app-scroller [init]="getImages('cats')"></app-scroller>
<app-scroller [init]="getImages('dogs')"></app-scroller>
getImages(src: string) {
//THIS FUNCTION GETS CALLED AGAIN AND AGAIN
return {
aspect: '16/9',
res: 'min',
sources: this.imageService.getImagesFromAPI(src)
};
}
Scroller Component
public movies: string[] = [];
#Input() init: {aspect: string, res: string, sources: Promise<string[]>};
ngOnInit() {
this.init.sources.then(images => this.movies = movies);
}
but this results in the the getImages and therefore the sources Promise to be executed over and over
Is there a way I can send data to the Scroller component only once (therefore without using #Input() )
I believe you need to call your service once to get the array of images and save it inside your component as a property,
something like this
myService.getData.subscribe(data=> this.catImages = data)
If I understand your question and setup correctly, you are asking about preventing an #Input from listening, what if you instead prevent data from emitting to this input?
You could deliver an observable stream that emits just once, eg.
catImages$ = this.catDataFromService$.pipe(
take(1),
)
<app-scroller [source]="catImages$ | async"></app-scroller>
Alternatively, you could construct your own Observable and complete it when necessary.
Use property binding only to send the category id (dogs/cats) to the component and call getImages(cateogryID) only once in the child component.
Parent component
<app-scroller [categoryId]="catIdProperty"></app-scroller>
Child component:
#input()
categoryId: string;
images: [type here] = [initialization here];
ngOnInit(): void {
this.images = this.getImages(categoryId); // Btw, could getImages() reside in the imageService?
}
I'm fetching data from my service in the app component, and then passing it down to a child component via #Input. when I console.log the data in ngOnInit, it DOES show up in the child component, but when I try to pass it to a variable and use it in the child template, it's coming back undefined, and I'm not sure why.
Here's where I call my service in app.component.ts, the data that is having issues is this.colorcounts. Console logging here inside the ngOnInit DOES show the correct data. colorCounts has 3 properties: red, yellow & green:
ngOnInit() {
this.pciService.getPciInfo()
.subscribe((pciInfo) => {
this.spinner.hide();
this.pciData = pciInfo.sorted,
this.colorCounts = pciInfo.counts;
});
Here's the app.component.html template where I'm passing the data down:
<app-navbar *ngIf="colorCounts" [colorCounts] = "colorCounts"></app-navbar>
<app-system-status [pciData]="pciData"></app-system-status>
Here's the child component where I'm grabbing the data, doing a console.log in ngOnInit does work, but trying to use "red" in the template or save it in the class comes back undefined:
constructor(private pciService: PciService,
public dialog: MatDialog,
private decimalPipe: DecimalPipe) { }
AMVersion: any;
#Input() colorCounts: Colors;
openDialog(): void {
let dialogRef = this.dialog.open(AmVersionDialogComponent, {
panelClass: 'custom-dialog-container',
data: {}
});
(<AmVersionDialogComponent>dialogRef.componentInstance).AMVersion = this.AMVersion;
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
});
}
ngOnInit() {
this.pciService.getAMVersion()
.subscribe((AMInfo) => {
return this.AMVersion = AMInfo;
});
var red = this.colorCounts.red;
console.log(red)
console.log(this.colorCounts);
}
}
I know I'm probably missing something simple regarding the life cycle. Can someone point me in the right direction here?
All Observables are async so in template *ngIf condition will check variable and if it is null will return null but if You pipe variable as | async it will be checking this variable all time and when variable will apear not null it will show content ngIf.
*ngIf works only once !!! He not waiting for anny http calls and Observables are making one usualy. If You want to *ngIf wait for calls You need to use | async pipe inside.
Same as You subscribe to it in ngOnInit() and assign to variables in template. Subscription will assign those values later after template is allredy on screen. Read about what async means.
You need to know that this is a boilercode:
ngOnInit() {
this.pciService.getPciInfo()
.subscribe((pciInfo) => {
this.spinner.hide();
this.pciData = pciInfo.sorted,
this.colorCounts = pciInfo.counts;
});
It is better to do it like this:
ngOnInit() {
this.pciInfo$ = this.pciService.getPciInfo()
}
in template:
<ng-container *ngIf="pciInfo$ | async as pciInfo">
<app-navbar [colorCounts]="picInfo.counts"></app-navbar>
</ng-container>
Pipe Async will subscribe for you to an Observable.
I have a function that should return an array of objects when called. The function looks like this
loadTodo(): Todo[]{
var data
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
data = res.json()
}, error => {
console.log(error)
})
return data}
This results in unexpected behavior where data variable gets assigned correctly inside the success response block but is undefined when accessed outside the response block.
The function is assigned to a variable with type Todo[] and is invoked immediately when the variable is declared. I am quite new to TypeScript and Angular but not to JavaScript. Am I missing something with scope/closure of the function or is this issue related to TypeScript/Angular?
Whole class looks like this:
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
})
console.log('outside function')
console.log(this.parcedTodos)
return this.parcedTodos
}
}
http.get() is asynchronous, which means that when you try to print parcedTodos outside the then callback, it will still be undefined.
Asynchronous programming in JS
It is happening because http calls are asynchronous.
You need to make sure you are accessing data only after call is completed.
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
},
{
console.log(this.parcedTodos);
// This is where your call gets completed. Here you can access assigned data or call another function where you can access data.
})
console.log('outside function')
console.log(this.parcedTodos) // This is called before asynchronous call is completed. Thats why it is undefined yet.
return this.parcedTodos
}
}
Hope this helps.
this.http.get(whatever) is an async call.
Your data is undefined because you're accessing it before it is actually initialized. i.e. you're initializing it inside the success handler (the first argument to then), and probably are accessing it before initialization.
All you need to do is make sure you're doing so after the success or error handler. use Observable
I think that using res.json() not is neccesary because angular pipes already doing this works. Do you try to assign to variable res directly?
As others friends says, you are doing bad some things.
First: you must read about asynchronous methods
Second: use Observables importing rxjs/Observable; and follow its callbacks flow
Example
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo() : Observable<Todo[]>{
return this.http.get(`${this.API_URL}todos`);
}
}
And other class consummes this method
todoDataService.loadTodo().subscribe(
(response) => {
console.log("Future response", response);
}
);
I am developing an Angular 4 application. I have a search component, where the user inputs a string. Whenever the user enters a value and submits it, I am emitting the value from the SearchComponent to DisplayComponent.
Emitting value in SearchComponent
#Output() userInput: EventEmitter<string> = new EventEmitter<string>();
onFormSubmit() {
this.userInput.emit(this.searchForm.value.SearchInputValue);
}
DisplayComponent HTML looks like
<app-search (userInput)="valueFromSearch"></app-search>
<div class="some-other-html></div>
DisplayComponent TS file looks like
valueFromSearch: string;
constructor(private someService: SomeService) {}
ngOnInit() {
this.someService.getSomeData(valueFromSearch)
.subscribe((data) => {console.log("Success! data is ",data)},
(error) => {console.log("Something went wrong", error)})
}
But how to run the someService function whenever, the valueFromSearch changes? Should I use BehaviorSubject to keep listening for the changes in valueFromSearch?
Is there any function to know that the value has changed? Please help me resolve this.
you can make valueFromSearch() a method which will invoke everytime you emit from search
DisplayComponent HTML
<app-search (userInput)="valueFromSearch(value)"></app-search>
<div class="some-other-html></div>
DisplayComponent TS file
constructor(private someService: SomeService) {}
valueFromSearch(value: string) {
this.someService.getSomeData(value)
.subscribe((data) => {console.log("Success! data is ",data)},
(error) => {console.log("Something went wrong", error)})
}
ngOnChanges(changes: any) {
let data = changes.valueFromSearch.currentValue;
// This only work with a change not if it has the same value.
}
Not sure if #JayDeeEss's method will work right away but a slight modification to it definitely seems workable.
Like his suggestion, make valueFromSearch a method (remove this declaration: valueFromSearch: string; from your component) that gets invoked every time the userInput EventEmitter emits a new value.
DisplayComponent HTML (notice the change in the argument passed to valueFromSearch(); from (value) to ($event) )
<app-search (userInput)="valueFromSearch($event)"></app-search>
<div class="some-other-html></div>
DisplayComponent TS file (inside the component, the argument name doesn't matter)
valueFromSearch(value: string) {
// handle the changed 'value' based on required logic
}
I have an issue with ngonchanges not firing.
I have this component:
#Component({
selector: 'conversation-detail',
templateUrl: './conversation-detail.component.html'
})
export class ConversationDetailComponent implements OnChanges{
#Input()
selectedConversation: Conversation;
title = 'Conversation Detail';
convoMessages: Array<Message> = [];
constructor(
private _messageService: MessageService
){};
ngOnChanges(changes: SimpleChanges){
this.getMessages();
}
ngOnInit(): void{
}
private getMessages(){
if(this.selectedConversation != undefined){
this._messageService.getMessagesForConversation(this.selectedConversation.ConversationId).then(
data => {
if (data) {
this.convoMessages = data.serviceResult as Array<Message>;
} else {
//This really should never happen
console.error('Error retrieving users data: Data object is empty');
}
},
error => {
//loader.dismiss();
console.error('Error retrieving users data');
console.dir(error);
}
);
}
}
}
The ngonchanges will fire the first time selectedConversation changes. GetMessages is called and it loads in the message data. After that ngonchanges no longer fires when selectedConversation changes.
If I comment out the function call to getMessages then ngonchanges will fire every time selectedConversation changes.
so, I'm not sure what is going on in getMessages that cuts off ngonchanges from firing off. The request returns data and the call ends. Anyone have any ideas?
ngOnChanges only fire if #Input change reference, for example Input type of string, boolean, number, immutable object.
assume [selectedConversation] bind to an array arrSelected, if you do something like arrSelected.push, the array does not change ref so ngOnChanges won't fire.
so you need using immutable array, using concat, slice, etc to change the array reference
Please change your component as :
#Component({
selector: 'conversation-detail',
templateUrl: './conversation-detail.component.html',
changeDetection: ChangeDetectionStrategy.Default
})
This works fine for me.