When a model that eager-loads other models is passed to an event that implements ShouldBroadcast and that is received by Laravel Echo, the eager-loaded models are not received by the client.
Here's is an example with a Post model that belongs to a User.
Controller method where the post is created:
$post = Post::create([
'user_id' => 1,
'topic_id' => request('topic_id'),
'body' => request('body'),
]);
$post = Post::with('user')->find($post->id);
event(new PostCreated($post));
Here is the event class:
class PostCreated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $post;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($post)
{
$this->post = $post;
echo $post->user->name; // This works, name is echo'd
}
/**
* Get the channels the event should broadcast on.
*
* @return Channel|array
*/
public function broadcastOn()
{
return new Channel('topics');
}
}
And finally the JS code :
Echo.channel('topics')
.listen('PostCreated', (e) => {
console.log(e.post); // Can see the post object, but not the user property
console.log(e.post.user); // Undefined
});
Why is post.user undefined in the JS code, while $post->user is not in the event class?
Do I need to do something special to ensure that the eager-loaded models are passed along when the event is broadcasted?
I'm using Pusher, if that matters.
undefined When using eloquent models in broadcasting events, it looks like eager loaded relationship data isn't accounted for. They're available server side in your listeners but not client side.
My workaround is as follows. I'm sure there are better solutions, but it works just fine for me:
// TaskController.php
public function test()
{
$task = Task::with('user')->first();
event(new TaskUpdated($task->toJson()));
}
// TaskUpdated.php
public $task;
public function __construct($task)
{
$this->task = $task;
}
public function broadcastOn()
{
return ['tasks'];
}
md5-7e1639a5d2bef800c339bec186df9617
// TaskComponent.vue
Echo.channel('tasks')
.listen('TaskUpdated', (e) => {
console.log( JSON.parse(e.task) );
});
So I just convert to JSON then parse on the client and all data including relationship data is available.
Again, there must be a more elegant solution, but this is how I've always handled it.
Thank you @devcircus , it works very well.
I hope that in the future this can be fixed so that it isn't necessary, but for now that will do the trick.
This also happens to me.
Same here, but!
If I use something like this in the channel name, the article 'eager loaded' so I can use it in JS:
public function broadcastOn()
{
return new Channel('comment.'.$this->comment->article->slug);
}
Now the console.log(comment.article) is working.
But I'd like to eager load the comment->creator as well, but I can't. So I use an other public variable:
public function __construct(Comment $comment)
{
$this->comment = $comment;
$this->creator = $comment->creator;
}
The creator in my comment model looks like this:
public function creator()
{
return $this->belongsTo('App\User', 'user_id')
->select(['id', 'name']);
}
But the Event gives back the whole User model, so I can see all the fields not just the id and name.
This could be fixed if we send the data through broadcastWith()
/**
* Get the data to broadcast.
*
* @return array
*/
public function broadcastWith()
{
return [
'user' => $this->user->load('userable'),
];
}
Thanks it works.
Note:
In this case the broadcast returns only this array, so the public variable not included. But it's totally fine.
@themsaid I think this should be documented. I mean maybe some note in the docs if the relation not loaded on the public variable or something.
Most helpful comment
When using eloquent models in broadcasting events, it looks like eager loaded relationship data isn't accounted for. They're available server side in your listeners but not client side.
My workaround is as follows. I'm sure there are better solutions, but it works just fine for me:
So I just convert to JSON then parse on the client and all data including relationship data is available.
Again, there must be a more elegant solution, but this is how I've always handled it.