Angular 2 Endless loop async pipe - javascript

My hotel.service.ts
getRoomList(): Observable<RoomListType[]> {
return this.http.get<RoomListType[]>('http://localhost:3001/rooms');
}
my content.ts is
get roomList$() {
return this.hotelService.getRoomList().pipe(shareReplay(1));
}
my content.html is
<div class="col-md-9" *ngFor="let item of (roomList$ | async)">
<div class="row">
<div class="col-md-4">{{ item.RoomType }}</div>
<div class="col-md-8 d-flex justify-content-end">
<button class="ml-1 btn btn-outline-danger btn-sm" (click)="openScrollableContent(longContent)"><i class="fa fa-bed"></i>Oda Ă–zellikleri</button>
</div>
</div>
...
</div>
My goal is I want to bind hotel rooms in my html file. I read some article on stackoverflow to use shareReplay(1) but I didnt work for me. How can I achieve this.

You've created an infinite loop by triggering an http request inside that getter.
When change detection occurs, your getter will be called. Your getter then makes an http request, which triggers change detection, which calls your getter, etc.
The roomList$ Observable you're passing to the async pipe should be created once, probably in ngOnInit.
So your content.ts would look something like this:
roomList$: Observable<RoomListType[]>;
ngOnInit() {
this.roomList$ = this.hotelService.getRoomList();
}
shareReplay doesn't seem necessary in your situation—that's used if you might have late subscribers to your Observable who should receive the last emitted value immediately upon subscription rather than having to wait for the Observable to emit again.
And if you did have that situation, you would configure it more like this:
getRoomList() {
return this.roomList$.pipe(shareReplay(1));
}
rather than with a getter that triggers a new http request every time it's referenced.
Here's a StackBlitz with your basic scenario not triggering an infinite loop.

Related

Angular doesn't automatically display the changes in my array of string

let signalRServerEndPoint = 'https://localhost:44338';
this.connection = $.hubConnection(signalRServerEndPoint);
this.proxy = this.connection.createHubProxy('MessagesHub');
this.proxy.on("ReceiveMessage", (message) => {
console.log(message); //LOG IS OKAY
this.listMessages.push(message); // PUSH IS OKAY
console.log(this.listMessages); // LOG IS OKAY IS OKAY
});
The listmessages is an array of string[]. Both console.log() works fine, also the this.listMessages.push(message) works fine because the second console.log display the right array of string. But my problem is in my UI it doesn't automatically populate the new listMessages. It will only display the new populated array when I type something in the textbox or when I click a send button again then it display the latest I've sent earlier. Anyone can help me what's wrong with this?
<div *ngFor="let listMessage of listMessages;let indexs = index;">
<div class="row align-items-center" style="margin-bottom: 5px;" *ngIf="indexs % 2 == 0">
<div class="col-lg-7">
<div class="row">
<img [src]="createImagePath('userphoto/202431775reyes.jpg')" class="avatar avatar-sm rounded-circle" style="margin-left: 20px; max-width: 70px; max-height: 50px;" />
<div class="card-header bg-gradient-success" style="margin-left: 20px; border-radius: 25px; background-color: #f1f0f0;">
<h4 style="margin-bottom: -10px; margin-top: -10px;" class="text-white">{{listMessage}}</h4>
</div>
</div>
<h6 class="text-primary" style="margin-left: 10px;">Anthony Earl Cuartero: 12:00 PM | Aug 13</h6>
</div>
</div>
</div>
This is most probably a case of change detection not being triggered because the reference to the variable isn't modified. You could force it using destructuring operator instead of the push. Try replacing the push statement with
this.listMessages = [...this.listMessages, message];
Or you could retain the push statement and trigger change detection manually using Angular ChangeDetectorRef.
import { Component, ChangeDetectorRef } from '#angular/core';
export class AppComponent {
constructor(private changeDetectorRef: ChangeDetectorRef) { }
this.proxy.on("ReceiveMessage", (message) => {
this.listMessages.push(message);
this.changeDetectorRef.detectChanges();
});
the issue is that jquery isn't well integrated with angular so the events aren't triggering change detection... you should ideally find a way to stop using jquery, you can use signalR without it and there are better integrated signalR / angular libraries out there.
in the meantime, this is untested, but I believe it will work if you leverage NgZone to bring it into angular's change detection... something like...
constructor(private ngZone: NgZone) {}
this.proxy.on("ReceiveMessage", (message) => {
this.ngZone.run(() => {
console.log(message); //LOG IS OKAY
this.listMessages.push(message); // PUSH IS OKAY
console.log(this.listMessages); // LOG IS OKAY IS OKAY
});
});
you'll probably want to implement some websocket wrapper service that does this for you with all websocket events so you don't need to use NgZone everywhere you use websockets.
You are using push to update Array, so the array reference is same and hence it is not updating the UI, you can create a new Array using array.filter or spread operator. In short you have to create a new array using old array and updated data. Array is a reference type in JavaScript.

Angular component multiple instance binding input problem

I am using an Angular Wrapper for JSON Editor like this:
<div *ngFor="let act of editedActions" class="w-100-p p-24">
{{act.test_step_id}}
<json-editor [options]="editorOptions" [(data)]="act.action_json" [(eventParams)]="act.test_step_id" (jsonChange)="changeStepActions($event)"></json-editor>
<button mat-raised-button class="w-100-p mt-24" color="primary" (click)="editRecordJson(act.test_step_id)">
<span>Update</span>
</button>
</div>
The problem is that eventParams should be different for each editor but it is not varying.
I think problem is this component code (but not sure) (This line is in the component taken from github):
#ViewChild('jsonEditorContainer', { static: true }) jsonEditorContainer: ElementRef;
The component is behaving like a singleton. Any help?
Edit: I edited this repo and added jsonchange event. Details here
You may want to use #ViewChildren with a direct reference to the component instead of a template variable string, to get all the JSON editors references:
#ViewChildren(JsonEditorComponent) jsonEditorContainers: QueryList<ElementRef>;
// ...
jsonEditorContainers.find(...);
It returns a QueryList that allows you to iterate through all ElementRef, and monitor the changes with an Observable changes.
What is eventParams? What is jsonChange? I could be wrong, but data doesn't seem to be two way bindable either, according to the source code.
It seems like you might be looking for something like this:
<div *ngFor="let act of editedActions" class="w-100-p p-24">
<json-editor [options]="editorOptions"
[data]="act.action_json"
(change)="changeStepActions($event, act.test_step_id)">
</json-editor>
</div>
You can then read the test_step_id in your changeStepActions method. If this works, I don't know how you made it compile in the first place.. are you using a CUSTOM_ELEMENTS_SCHEMA?
Its not necessary to use #ViewChildren for that you have to rewrite the entire code of component, make sure while using #ViewChild you pass correct editor reference.
As following
#ViewChild('carEditor' ) carEditor: JsonEditorComponent;
#ViewChild('mobileEditor') mobileEditor: JsonEditorComponent;
Stackblitz example for refernce :
Click here for code example
To use multiple jsoneditors in your view you cannot use the same editor options.
You should have something like:
<div *ngFor="let prd of data.products" class="w-100-p p-24" >
<json-editor [options]="makeOptions()" [data]="prd" (change)="showJson($event)"></json-editor>
</div>
makeOptions = () => {
return new JsonEditorOptions();
}

Getting data from declared component in vue

Currently trying to wrap my head around Vue and templates.
I read that you pass data from child -> parent with $emit()
app.js
Vue.component('tweet-postbox', require('./components/tweetPostBox.vue').default);
const app = new Vue({
el: '#app',
methods: {
addTweet (tweet) {
//from the tweetPostBox.vue postTweet method
console.log(tweet)
}
}
});
tweetPostBox.vue
<template>
<div class="post-box">
<div class="w-100 d-flex align-items-center">
<div class="profile-image rounded-circle"></div>
<input v-model="message" type="text" id="tweetText" placeholder="Whats happening?">
</div>
<div class="controls d-flex align-items-center w-100">
<button class="btn btn-primary ml-auto" #click="postTweet" id="postTweet">Tweet</button>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
message: ''
}
},
methods: {
postTweet: async function(){
let response = await axios.post('/post', {
message: this.message
})
//How to get this response data to the main vue instance?
this.$emit('addTweet', response);
}
}
}
</script>
I'm trying to get the value into my app.js from the component file... but nothing is console logged. Where am I going wrong?
Update: Added HTML
<div class="container" id="app">
<tweet-postbox></tweet-postbox>
</div>
You should just need to change the template to:
<div class="container" id="app">
<tweet-postbox #add-tweet="addTweet"></tweet-postbox>
</div>
The #add-tweet part registers an event listener for the add-tweet event. I've used kebab case to avoid browser case-sensitivity problems. You'd need to emit the event with the same name, this.$emit('add-tweet', response). See the offical documentation to confirm that kebab case is the way to go.
The ="addTweet" parts assigns the method addTweet as the listener.
https://v2.vuejs.org/v2/guide/events.html#Method-Event-Handlers
Found this great answer on another post:
https://stackoverflow.com/a/47004242/2387934
Component 1:
<!-- language: lang-js -->
this.$root.$emit('eventing', data);
Component 2:
<!-- language: lang-js -->
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
First of all, please fix your coding style, there's a lot of issues including indentation errors, try using eslint maybe.
Second, let's break this.$emit('addTweet') down a bit:
this is in that line refers to the instance of the Vue component, thus an instance of TweetPostBox.
When you call $emit with a addTweet, you're dispatching an event within the component.
Now that addTweet is dispatched, what's going to happen next? Vue is going to find all event handlers that handle addTweet and executes them.
Now in your case, you do not have any event handlers for this event. addTweet in your parent component is simply a local function in that component, it is not an event listener by any means.
To register an event listener, Vue provides # syntax, and you're already using that with #click, so just like you are doing #click="dosomething" (thus registering an event handler for onClick you need to use #add-tweet. (#addTweet also works but it is against coding style standards, Vue automatically handles the conversion for you)

Angular Material selectionChanged returning previous step instead of current

I have a page consisting of two columns. One contains a vertical stepper and the other one should contain a description for each step. I want to update this description according to the current step and therefore listen to the selectionChange-Event. The problem is, that I get the previously chosen step when I look for the selectedIndex, not the one I am now on.
HTML:
<div class="row">
<div class="col-7 stepperColumn">
<mat-vertical-stepper (selectionChange)="onStepChange(eventCreationStepper)" #eventCreationStepper>
<!--some steps-->
</mat-vertical-stepper>
</div>
<div class="col-5">
<!--some descriptions-->
</div>
</div>
JS:
public onStepChange(stepper: MatStepper) {
console.log(stepper.selectedIndex);
}
At first this seems like odd behaviour, but after thinking about it I realise it's because you're passing through the template ref before you've changed the step, so the object that arrives in your method is still pointing at the previous step.
It looks like the solution is to grab the new index from the event object that is passed by the selectionChange method, like this:
HTML:
<mat-vertical-stepper (selectionChange)="onStepChange($event)">
TS:
public onStepChange(event: any): void {
console.log(event.selectedIndex);
}
This should solve your problem.

Invoking a function on text click: AngularJS

I am trying to invoke a specific controller method when click on some text. This function makes some remote calls to another server and configure showing or hiding another div. I also need to pass some parameters to such functions, one of which is static text and the other is a angularJS variable (i.e. an element of a list I am iterating on.
I am not sure the best way to do it, as the following code does not seem to work:
<div ng-repeat="item in item_list">
<div ><p ng-click="functionToInvoke({{item.name}},
'static text')">{{item.name}}</p></div>
I get a compile error on the paragraph.
How do I manage this situation?
When specifing a function in ng-click you dont need {{}}
<div ng-repeat="item in item_list">
<div >
<p ng-click="functionToInvoke(item.name, 'static text')">{{item.name}}</p>
</div>
</div>
Your function then looks something like this:
$scope.functionToInvoke = function(var, static){
console.log('This is the variable:' + var);
console.log('This is the static:' + static);
}

Categories

Resources