Laravel websocket wont trigger event from vue front end - javascript

I already have setup my websocket connection with pusher. I can fire events at the websocket admin and i can show the output of it via console.log. Now i created a new event that if the user adds new product, the table will be updated whenever who is viewing it. But it seems I can add data successfully but the table wont update to other user. Can someone know why my event is not working?
ProductsEvent.php
namespace App\Events;
//show only important imports
use Illuminate\Broadcasting\Channel;
use App\Product; //Import my model
class ProductsEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $product;
public function __construct(Product $product)
{
$this->product = $product;
}
public function broadcastOn()
{
return new Channel('Products');
}
}
ProductsControlller (store)
public function store(Request $request)
{
$product = new Product;
//some validation...
//broadcast(new ProductsEvent($product)); if i put it here i got No query results for model [App\Product].
$product->barcode = $request->barcode;
$product->product_name = $request->product_name;
$product->price = $request->price;
$product->quantity = 0;
$product->category = $request->category;
$product->supplier_id = $request->supplier;
$product->save();
broadcast(new ProductsEvent($product));
}
channels.php
Broadcast::channel('Products',function(){
return true;
});
and my vue component
created(){
//when i successfully created the data,
i will call getProducts to listen in the channel for DOM updates
Echo.join('Products')
.listen('ProductsEvent',(event)=>{
this.getProducts()
})
}
If i call broadcast before save in my controller, I got something like this
No query results for model [App\Product].
I uncomented the App\Providers\BroadcastServiceProvider::class, line in config.php in order for the broadcast to work.

I dont know why .join wont work but I used window.Echo.channel i doubt this is the right thing to do.
created(){
this.getProducts()
this.getSuppliers()
// Echo.join('Products')
// .listen('ProductsEvent',(event)=>{
// // this.products.push(event.products)
// this.getProducts()
// })
// .here(()=>{
// console.log('here')
// })
window.Echo.channel('Products').listen('ProductsEvent',(e)=>{
this.getProducts()
toastr.success('Product Updated')
})
}

Related

Does "subscribe" call executes the Observable assignment expression

I am new to Angular and RxJS. I am analysing the following extract from the Angular tutorial (ng-book2-book-angular-11-r77-code).
My question is when the call this.messages = this.updates... (in the constructor method) executes - is it executing in the constructor or when addMessage (message: Message) is called?
import { Injectable } from '#angular/core';
import { Subject, Observable } from 'rxjs';
import { User } from '../user/user.model';
import { Thread } from '../thread/thread.model';
import { Message } from '../message/message.model';
const initialMessages: Message[] = [];
interface IMessagesOperation extends Function {
(messages: Message[]): Message[];
}
#Injectable()
export class MessagesService {
// a stream that publishes new messages only once
newMessages: Subject<Message> = new Subject<Message>();
// `messages` is a stream that emits an array of the most up to date messages
messages: Observable<Message[]>;
// `updates` receives _operations_ to be applied to our `messages`
// it's a way we can perform changes on *all* messages (that are currently
// stored in `messages`)
updates: Subject<any> = new Subject<any>();
// action streams
create: Subject<Message> = new Subject<Message>();
markThreadAsRead: Subject<any> = new Subject<any>();
constructor() {
this.messages = this.updates
// watch the updates and accumulate operations on the messages
.scan((messages: Message[],
operation: IMessagesOperation) => {
return operation(messages);
},
initialMessages)
// make sure we can share the most recent list of messages across anyone
// who's interested in subscribing and cache the last known list of
// messages
.publishReplay(1)
.refCount();
// `create` takes a Message and then puts an operation (the inner function)
// on the `updates` stream to add the Message to the list of messages.
//
// That is, for each item that gets added to `create` (by using `next`)
// this stream emits a concat operation function.
//
// Next we subscribe `this.updates` to listen to this stream, which means
// that it will receive each operation that is created
//
// Note that it would be perfectly acceptable to simply modify the
// "addMessage" function below to simply add the inner operation function to
// the update stream directly and get rid of this extra action stream
// entirely. The pros are that it is potentially clearer. The cons are that
// the stream is no longer composable.
this.create
.map( function(message: Message): IMessagesOperation {
return (messages: Message[]) => {
return messages.concat(message);
};
})
.subscribe(this.updates);
this.newMessages
.subscribe(this.create);
// similarly, `markThreadAsRead` takes a Thread and then puts an operation
// on the `updates` stream to mark the Messages as read
this.markThreadAsRead
.map( (thread: Thread) => {
return (messages: Message[]) => {
return messages.map( (message: Message) => {
// note that we're manipulating `message` directly here. Mutability
// can be confusing and there are lots of reasons why you might want
// to, say, copy the Message object or some other 'immutable' here
if (message.thread.id === thread.id) {
message.isRead = true;
}
return message;
});
};
})
.subscribe(this.updates);
}
// an imperative function call to this action stream
addMessage(message: Message): void {
this.newMessages.next(message);
}
}
In the following example, lazyNumber is assigned in the constructor and referenced in the printNumber method.
The expression isn't evaluated to 4 until it's called.
class a {
constructor(){
this.lazyNumber = () => 5 - 1;
}
printNumber(){
console.log( this.lazyNumber() );
}
}
The same fundamental thing is happening in your example.
You example is defining an attribute and the code that get runs when it's "called". addMessage is emitting a new value on the observable, that causes the listeners assinged above to react accordingly

Laravel Notification multiple users with pusherjs

What I'm doing in sending a Notification to multiple users with Laravel Facade
Notification::send($users, new PartenaireSendCRNotification($data));
This is the PartenaireSendCRNotification class:
// I need the Database and Broadcast on Pusher channel
public function via($notifiable)
{
return ['database', 'broadcast'];
}
// Name of the Pusher channel
public function broadcastOn()
{
return ["new-cr-from-part"];
}
// Send data
public function toArray($notifiable)
{
return [
'data' => $this->data
];
}
On the Front End (blade and js):
#if(Auth::user()->isAdmin || Auth::user()->isConfirmation)
<script>
// ... some pusher configuration
// subscribe to the channel 'new-cr-from-part'
let channel = pusher.subscribe('new-cr-from-part');
// listen for event BroadcastNotificationCreated
channel.bind(
'Illuminate\\Notifications\\Events\\BroadcastNotificationCreated',
function(data) {
console.log(data);
// Some javascript
receiveCRNotification();
});
</script>
#endif
Only 6 users match the condition to execute the script for listening to the channel.
When I test it, every thing works fine (I have 6 notifications in the Database) but the js after the binding to the channel is executed 6 times for every user.
What I'm missing?

rxjs ReplaySubject handle

i have a problem with a template i am using in angular 4.
This template implement a notification system, where you can add new notifications, but the documentation do not specify how one can delete the elements of the observer ReplaySubject.
The template implement this as a service as follows:
private notificationsList: Notification[] = [];
// a stream that publishes new notifications only once
public newNotifications: Subject<Notification> = new Subject<Notification>();
// `notifications` is a stream that emits an array of the most up to date notifications
public notifications: ReplaySubject<Notification[]> =
new ReplaySubject<Notification[]>(1);
// `updates` receives _operations_ to be applied to our `notifications`
// it's a way we can perform changes on *all* notifications (that are currently
// stored in `notifications`)
public updates: Subject<any> = new Subject<any>();
// action streams
public create: Subject<Notification> = new Subject<Notification>();
// public markThreadAsRead: Subject<any> = new Subject<any>();
constructor() {
// recois des operation, et les fais sur la liste interne, puis diffuse le
// resultat sur notifications
this.updates.subscribe((ope) => {
this.notificationsList = ope(this.notificationsList);
console.log(this.notificationsList);
this.notifications.next(this.notificationsList);
});
this.newNotifications
.map(function(notification: Notification): INotificationsOperation {
return (notifications: Notification[]) => {
return notifications.concat(notification);
};
})
.subscribe(this.updates);
}
// an imperative function call to this action stream
public addNotification(notification: Notification): void {
this.newNotifications.next(notification);
}
I try to ask to the owner how i can delete an actual element of the notification list, but he just tell me that i can access the "notifications" subject to receive the last version of it. But do not mention how i can actually delete an element of the list.
Some one know something about?
Thanks!
I added a public function that you can use.
I added a comment to let you see which part of the code you can modify if you want to delete elements by name for example, or don't want to resize the list.
Explanation at the end of my post.
private notificationsList: Notification[] = [];
// a stream that publishes new notifications only once
public newNotifications: Subject<Notification> = new Subject<Notification>();
public removeNotificationByIndex$ : Subject<number> = new Subject<number>();
// `notifications` is a stream that emits an array of the most up to date notifications
public notifications: ReplaySubject<Notification[]> =
new ReplaySubject<Notification[]>(1);
// `updates` receives _operations_ to be applied to our `notifications`
// it's a way we can perform changes on *all* notifications (that are currently
// stored in `notifications`)
public updates: Subject<any> = new Subject<any>();
// action streams
public create: Subject<Notification> = new Subject<Notification>();
// public markThreadAsRead: Subject<any> = new Subject<any>();
constructor() {
// recois des operation, et les fais sur la liste interne, puis diffuse le
// resultat sur notifications
this.updates.subscribe((ope) => {
this.notificationsList = ope(this.notificationsList);
console.log(this.notificationsList);
this.notifications.next(this.notificationsList);
});
this.newNotifications
.map(function(notification: Notification): INotificationsOperation {
return (notifications: Notification[]) => {
return notifications.concat(notification);
};
})
.subscribe(this.updates);
this.removeNotificationByIndex$
.map(function(index: number){
return (notifications: Notification[]) => {
// >>>> DELETE METHOD IS TO BE DEFINED DOWN HERE !
notifications.splice(index,1);
// >>>> DELETE METHOD IS TO BE DEFINED UP HERE !
return notifications
};
})
.subscribe(this.updates);
}
// an imperative function call to this action stream
public addNotification(notification: Notification): void {
this.newNotifications.next(notification);
}
// delete the element in the "index" position of the list.
// /!\ Resizes the list
public removeNotificationsByIndex(index: number): void {
this.removeNotificationByIndex$.next(index);
}
What are the changes ?
public removeNotificationByIndex$ : Subject<number> = new Subject<number>();
This subject will receive (asynchronously) an index, and trigger a process using this index.
this.removeNotificationByIndex$
.map(function(index: number){
return (notifications: Notification[]) => {
// >>>> DELETE METHOD IS TO BE DEFINED DOWN HERE !
notifications.splice(index,1);
// >>>> DELETE METHOD IS TO BE DEFINED UP HERE !
return notifications
};
})
.subscribe(this.updates);
When the index is emitted (i.e you use the associated imperative function), a function (ES6 arrow function) is generated from it. This is it :
(notifications: Notification[]) => {
// >>>> DELETE METHOD IS TO BE DEFINED DOWN HERE !
notifications.splice(index,1);
// >>>> DELETE METHOD IS TO BE DEFINED UP HERE !
return notifications
};
This function is passed to this.update, which will apply it. In this context, ope is this function. when received, this.notificationList is modified as follow :
this.notificationsList = ope(this.notificationsList);
Finally, this new list is published to the ReplaySubject notifications:
this.notifications.next(this.notificationsList);
Which propagate this new list to all its subscribers.
VoilĂ  :). Good luck !

TypeError cannot read property "length" of undefined - angular 4

Every time I load the webpage, I'd have to click the logo in-order my data to fully populate the local array in my component. The data fetched is located in a local JSON file. Having to refresh the page every-single-time is fairly unprofessional/annoying.
Using Angular CLI 1.3.2
Here's where my problem lies:
#Injectable()
export class LinksService implements OnInit{
siteFile : IFile[];
constructor(private http: Http) {
this.getJSON().subscribe(data => this.siteFile = data, error =>
console.log(error));
}
public getJSON(): Observable<any> {
return this.http.get('./assets/docs/links.json')
.map((res:any) => res.json());
}
getAllIpageLinks() : IPageLink[]{
var selectedIPageLinks: IPageLink[] = new Array();
var selectedFileLinks : IFile[] = new Array();
selectedFileLinks = this.siteFile;
for (var i=0; i<selectedFileLinks.length; i++)
{
selectedIPageLinks =
selectedIPageLinks.concat(selectedFileLinks[i].files);
}
return selectedIPageLinks.sort(this.sortLinks);
}
Component:
constructor(private elRef: ElementRef, private linksService: LinksService) {
this._file = this.linksService.getAllIpageLinks();
}
Edit
The title has to be clicked in order for array of IFile[] to completely render. I've tried setting IFile to an empty array (IFile[] = []) The error goes away, however, it will render empty data.
The problem seems to be in the For loop, it can't recognize .length.
Problem :
The codes are correct but the approach is wrong. Subscribing to an Observable getJSON() is async task. Before any data is being returned by getJSON(), you already calls getAllIpageLinks() and therefore you get null value on very first run. I believe since you have injected the service as singleton in component, the data gets populated in subsequent call( on refresh by clicking logo).
Solution:
Apply the changes (that you are making in getAllIpageLinks ) by using map operator on observable.
return the instance of that observable in the component.
subscribe to that observable in the component(not in .service)
Welcome to StackOverflow. Please copy paste your codes in the question instead of giving screenshot of it. I would be able than to give you along the exact codes
Reference Codes :
I haven't tested the syntax but should be enough to guide you.
1. Refactor getAllIpageLinks() as below
public getAllIpageLinks(): Observable<any> {
return this.http.get('./assets/docs/links.json')
.map((res:any) => res.json());
.map(res => {
var selectedIPageLinks: IPageLink[] = new Array();
var selectedFileLinks : IFile[] = new Array();
selectedFileLinks = res;
for (var i=0; i<selectedFileLinks.length; i++)
{
selectedIPageLinks =
selectedIPageLinks.concat(selectedFileLinks[i].files);
}
return selectedIPageLinks.sort(this.sortLinks);
});
}
call above getAllIpageLinks() in your component
and subscribe to it there

SignalR join/leave group doesn't work correctly

I have a simple application using SignalR, where I wan't to display different data for different machines, depending on which machine has been chosen by the user
My Hub class looks like this:
readonly ISprayBroadcaster _sprayBroadcaster;
readonly IWorkRecordRepository _workRecordRepository;
public SprayHub(ISprayBroadcaster sprayBroadcaster, IWorkRecordRepository workRecordRepository)
{
_sprayBroadcaster = sprayBroadcaster;
_workRecordRepository = workRecordRepository;
}
public void Broadcast(string name)
{
Process.DataProcess(_workRecordRepository, Clients, name).Wait();
}
public void SwapGroup(string previousGroup, string newGroup)
{
Groups.Remove(Context.ConnectionId, previousGroup);
Groups.Add(Context.ConnectionId, newGroup);
}
public void JoinGroup(string groupName)
{
Groups.Add(Context.ConnectionId, groupName);
}
This is how I initialize the hub on the client side and call Broadcast method on it:
$(function () {
hub = $.connection.sprayHub;
function init() {
hub.server.joinGroup("machine1");
hub.server.broadcast("machine1");
};
// Client-side hub method that the server will call
hub.client.updateData = function (shifts) {
ViewModel.Measurements(recreateArray(shifts));
}
$.connection.hub.start().done(init);
});
Broadcast method goes to the DataProcess method which populates data to the clients from the assigned group:
public static async Task DataProcess(IWorkRecordRepository workRecordRepository, IHubConnectionContext hubClients, string machine)
{
var shiftRecords = await workRecordRepository.Records(machine, DateTime.Now).ToList();
var result = SLGT.Sentinel.Core.Calculations.Shifts(shiftRecords);
hubClients.Group(machine).updateData(result);
}
At the same time I setup a broadcaster which runs in the loop and feeds clients with appropriate data. This is a broadcast method from the broadcaster which calls the same DataProcess method to populate data for each machine found in the system:
void Broadcast(object state)
{
lock (_updateLock)
{
if (_updating)
return;
_updating = true;
var machines = _workRecordRepository.Machines();
machines.Subscribe(async machine =>
{
await Process.DataProcess(_workRecordRepository, Clients, machine);
});
_updating = false;
}
}
Finally when user clicks a button for different machine on the client side I swap the groups for the appropriate data to be displayed for this client:
$(".machineButton").click(function () {
var name = $(this).attr("id");
hub.server.swapGroup(previousGroup, name);
previousGroup = name;
}
Now, when I run this application in my test environment, everything works fine. When I run it on the server, swap between the groups doesn't work correctly, and the client is constantly fed with the same set of data. Why might it be happening? As I said local version works fine so I do not know how to debug it?
The group management methods (add and remove) are async. If you don't await the returned task then send to the group immediately after you have a race condition such that the client you just added might not receive the message. Also, you should never call .Wait() from in a hub method. Make the hub method async and await it instead.
readonly ISprayBroadcaster _sprayBroadcaster;
readonly IWorkRecordRepository _workRecordRepository;
public SprayHub(ISprayBroadcaster sprayBroadcaster, IWorkRecordRepository workRecordRepository)
{
_sprayBroadcaster = sprayBroadcaster;
_workRecordRepository = workRecordRepository;
}
public async Task Broadcast(string name)
{
await Process.DataProcess(_workRecordRepository, Clients, name);
}
public async Task SwapGroup(string previousGroup, string newGroup)
{
await Groups.Remove(Context.ConnectionId, previousGroup);
await Groups.Add(Context.ConnectionId, newGroup);
}
public async Task JoinGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
}
Also, is your production environment a single web server or is it a load-balanced farm? If it's a farm you'll need to configure SignalR scale-out.

Categories