Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] Add a BladeCompiler::renderComponent() method to render a component instance #40745

Merged
merged 3 commits into from
Feb 2, 2022

Conversation

tobyzerner
Copy link
Contributor

@tobyzerner tobyzerner commented Feb 1, 2022

This PR is similar to the functionality introduced in #40425.

This new method allows you to call BladeCompiler::renderComponent($component), where $component is an instance of Illuminate\View\Component, and get the HTML of the rendered component returned. Alternatively you may call the renderComponent method from the Blade facade: Blade::renderComponent($component).

Example

class HelloComponent extends Component
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function render()
    {
        return "Hello, $this->name";
    }
}

Blade::renderComponent(new HelloComponent('Claire'));

// Returns 'Hello, Claire'

Rationale

Components are a great way to encapsulate parts of the view. Rendering a component programatically is useful if you want to render a component's HTML directly into a response. For example, if you wanted to dynamically update a component via XMLHttpRequest.

Currently to achieve this you would need to create a new view partial to return in the response, for the sole purpose of rendering the component:

<x-hello-component :name="$name"/>
return view('partials.hello-component', ['name' => 'Claire']);

The Blade::render() function makes this a bit easier, as you could render the component as a Blade string into the response, rather than needing to resort to a partial:

return Blade::render('<x-hello-component :name="$name"/>', ['name' => 'Claire']);

However, this still doesn't have optimal ergonomics, especially with increasing numbers of properties. The renderComponent method cuts out the intermediate step and makes this simple:

return Blade::renderComponent(new HelloComponent('Claire'));

Note that if needed, attributes can be set on the component instance like so:

return Blade::renderComponent((new HelloComponent('Claire'))->withAttributes(['class' => 'red']));

Implementation

The implementation is based on the renderComponent method of the Illuminate\View\Concerns\ManagesComponents trait.

@browner12
Copy link
Contributor

I'm not following how

return Blade::renderComponent(new HelloComponent('Claire'));

is better than

return Blade::render('<x-hello-component name="Claire"/>']);

I'm also struggling to understand how returning a component is better than returning a partial?


Couldn't you also just

return (new HelloComponent('Claire'))->render();

if you're going to instantiate the class anyway?

@tobyzerner
Copy link
Contributor Author

tobyzerner commented Feb 2, 2022

@browner12 You can't always interpolate prop values into a template string. Imagine if one of the props the HelloComponent takes is an object, like a Model instance:

$user = User::find(1);
return Blade::render('<x-hello-component user="'.$user.'"/>'); // doesn't work!

The next best thing is to pass that data into the BladeCompiler::render method. But imagine if there are a lot of props we need to pass in:

return Blade::render('<x-hello-component :user="$user" :foo="$foo" :bar="$bar" :qux="$qux"/>', ['user' => $user, 'foo' => $foo, 'bar' => $bar, 'qux' => $qux]);

This works, but it's not very ergonomic – there's a lot of repetition. We also lose the benefit of type hinting from the component's constructor parameters. This is much nicer:

return Blade::renderComponent(new HelloComponent($user, $foo, $bar, $qux));

Regarding partials – they are a valid way to go about rendering a component. But it requires going to the effort of creating a new template file for every component you want to render, and again, the repetition of passing data into the view and then into the component.

You can't call render directly on a component instance, because it doesn't always simply return a string. It can, in fact, return any one of the following:

  • A blade template string
  • A View instance
  • A Htmlable instance
  • A closure that accepts data and returns any one of the above.

So, some logic is needed to determine what's been returned, and how to convert it into a string. This makes up the bulk of the renderComponent method.

@taylorotwell taylorotwell merged commit bcaebba into laravel:8.x Feb 2, 2022
@tobyzerner tobyzerner deleted the render-component branch February 2, 2022 19:54
@driesvints
Copy link
Member

Cool, thanks @tobyzerner 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants