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

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.

Related

Attempt to read property "title" on bool when trying to load a single post

I'm trying to load a single blog post in laravel, but every time I click on a single post, I get the error 'Attempt to read property "title" on bool.' I've tried looking up the error but I haven't found a solution and don't know what causes the error. Here's my code
Blade file:
#foreach($AboutUs as $about_us)
<div class="col-lg">
<img src="images/agenda.png" class="img-fluid shadow-lg" style="">
<p class="mt-3 text-success fw-bold fs-5 text-lg-start">{{$about_us->title}}</p>
<p class="fs-6 text-lg-start"> {{$about_us->description}}</p>
<p class="text-success fw-bold text-lg-start">{{$about_us->button}}</p>
<!--<img src="images/hr-2.png" class="img-fluid float-start my-2">-->
</div>
#endforeach
Controller:
public function show($id)
{
$data = AboutUs::find($id);
return view('about.show', ['AboutUs' => $data])
->with(['AboutUs' => AboutUs::find($id)]);
}
Routes:
Route::get('/about.show/{id}', 'App\Http\Controllers\WelcomeController#show')->name('about.show');
Can anyone offer any assistance?
OK, it's a bit of a mess, to be honest.
The short answer is that :
$data = AboutUs::find($id);
is returning one item, not a collection of items. You can't loop through that like you're trying to do, hence the error.
For some reason, though, you're then loading that again with when you're returning your view?
return view('about.show',['AboutUs'=>$data])->with(['AboutUs' => AboutUs::find($id),]);
So, for starters, you can probably simplify that - assuming you're wanting to load a post, in your controller you can then instead :
public function show($id) {
$post = AboutUs::find($id);
return view('about.show')->with(compact('post');
}
But then in your blade file you're trying to loop through your $AboutUs variable as though it's a collection, but it's not - find($id) returns a single model instance.
So naturally Blade is getting a bit confused.

Angular 2 Endless loop async pipe

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.

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)

images not displaying using array with input and ngFor - Angular

I'm trying to display an array of images via their source link using an ngFor but I am receiving errors and it's not working!
The image HTML code which is within my card component:
<div class="Session-Participants">
<div *ngFor="let image of {{ participantImages }}" >
<img src='{{image}}'/>
</div>
</div>
In the JS file:
#Input() participantImages? = [];
The data is coming from my other component and the html and JS are as below:
<div *ngFor="let sessions of getSortedSessionList()">
<div *ngFor="let session of sessions">
<tl-session-card
[title]="session.name"
[path]="sessionPath(session.id, session.name)"
[participantImages]="getParticipantDetails(session)" // HERE
>
</tl-session-card>
</div>
</div>
the JS:
participantImage = [];
getParticipantDetails(session) {
this.participantImage = session.roles[0].account.thumbnailURL;
return this.participantImage;
}
I'm expecting for the 'this.participantImage' to be returned and then sent to the card component and then broken down to be displayed separately.
I'm sure I am missing something?
Thanks if you can help at all?
Actually in the stackblitz link, you have a proper array and that not reflect perfectly the code you just provide above.
You need to ensure participantImage stay an array otherwise, the ngFor will not work as expected and you will see some errors in your console.
as #prashant-pimpale says, you need to append to the list of participantImage the result.
In the ts file It would be better to use
participantImage = [];
getParticipantDetails(session) {
// ensures this.participanImage keeps its array type and will process all
// other roles
this.participantImage = session.roles.map(account => account.thumbnailURL);
return this.participantImage;
}
Hope it helps
PS: would be better to set the attribute name of participantImage to participantImages since it represent a list of images ;)

How to load dynamic HTML into DIV with component? Angular5

I have been trying to find the solution of this problem from two days. Unfortunately, I can not get what I want. I am using Angular5.
<div class="form-group col-md-12" [innerHTML]="GetItemsOfHolder(item.children[1],1,
'UserConfigGroupsLabelHolder') | keepHtml"></div>
This is what my function looks like:
GetItemsOfHolder(item: any,divName:string, recursive: boolean = false,typeName:string="")
{
return html;
}
Everything works fine, unless The html which I am returning contains one package named Select2
This is what I use to add the html into this div it works very fine. Until I wanted to add the dynamic package.
What I mean is return html contains the package component like this:
itemhtml +="<select2 data-type='"+holderItem.itemType+"'
[data]='this.dropdownData."+holderItem.name+"'></select2>"
This just returns the plain text to the browser and doesn't work as expected.
What I want is the string to be turned into component or any other way which works and generates the select2 dropdown.
I have been trying to search so many things.But it doesn't works
This is good but I can not understand this And dynamiccomponentloading is deprecated.
Can anyone please give me an idea How can I resolve this problem? Any example would be a great.
As commented by #Devcon
Angular will sanitize pretty much everything so that is why you are
getting plain text. What you want to look into is ReflectiveInjector
and mainly ComponentFactoryResolver. The main idea is that components
need some other info(services, other components, etc) to be rendered,
so you use the Injector to get Dependency Injection refs then the
Component factory builds your component. You then insert this to a
ViewChild reference. There is a more complicated way of dynamically
making components that uses the compiler and requires a
ModuleWithComponentFactories, this is what angular actually uses.
And searching on the angular, I accept that angular should not be done this way.
As I have to create the fully dynamic page which must be rendered in html. I changed my json little bit and using the
ng-container and ng-template and using ngswitch
I made recursive call in the template it self and found its working very fine.
I get many advantages using this:
The HTML (I render dynamically) itself is in HTML, Code is clean and readable, easily maitainable.
The example given here is pretty much the same I have done.
https://stackoverflow.com/a/40530244/2630817
A small example is here:
<ng-template #itemsList let-itemsList>
<div *ngFor="let item of itemsList;let i = index">
<div [ngSwitch]="item.itemType">
<div class="form-group" *ngSwitchCase="'TEXT'">
<label>
{{item.label}}
</label>
<input id="{{item.name}}" value="{{item.value}}" type='text' class='form-control txtbox ui-autocomplete-input'/>
</div>
<div class="form-group" *ngSwitchCase="'PASSWORD'">
<label>
{{item.label}}
</label>
<input id="{{item.name}}" value="{{item.value}}" type='password' class='form-control txtbox ui-autocomplete-input'/>
</div>
<div class="form-group" *ngSwitchCase="'BOOLEAN'">
<label style='width:40%'>{{item.label}}</label>
<div class="form-group"><input id="{{item.name}}" type='checkbox' /></div>
</div>
<div class="form-group" *ngSwitchCase="'LABEL'">
<label class="form-control">{{item.label}}</label>
</div>
<div class="form-group" *ngSwitchDefault>
<label>
{{item.label}}
</label>
<select2 class="form-control" [data]="GetDropDowndata(item.holderId)" [cssImport]="false" [width]="300" [options]="GetOptions(item.type)"></select2>
</div>
</div>
</div>
You can load every you want in one div, you have to play with ng-template and ng-content.
First you have to create one directive:
import {Directive, ViewContainerRef} from '#angular/core';
#Directive({
selector: '[dynamic]',
exportAs: 'dynamicdirective'
})
export class DynamicDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
After you have to put it in some ng-template like:
<p>
page works!
</p>
<ng-template #sider=dynamicdirective dynamic></ng-template>
and use it like
import {Component, ComponentFactoryResolver, OnInit, ViewChild} from '#angular/core';
#Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.css']
})
export class PageComponent implements OnInit {
#ViewChild('sider')
sider;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
ngOnInit() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(SomeComponent);
this.sider.viewContainerRef.createComponent(componentFactory);
});
}
}
and normally will see you component loaded at the place of you ng-template (you can call https://angular.io/api/core/ViewContainerRef#clear if you want to reset your view)
I already play with this, you can find some code here https://github.com/nicearma/dynamic
I thought to leave this here for anyone who encounters the same issue in the future.
If you don't want to bother manually instantiating components with ComponentFactory as the other answers suggest, you can also use a library I wrote for the explicit purpose of loading components into dynamic content: ngx-dynamic-hooks.
You can give it any string that contains the selector of a desired component and it will automatically be loaded in its place. You can even load components by other text patterns other than just their selectors! See it in action in this Stackblitz.
There's a lot more bells and whistles, if you need them. In the link above, you'll find a fairly detailed documentation that should set you up easily.

Categories

Resources