
* refactor: Instantiate component when rendering, and remove metadata stack * refactor: update test * refactor: fix linter errors * docs: remove example from changelog
14 KiB
When a component is being rendered, whether with Component.render()
or {% component %}
, a component instance is populated with the current inputs and context. This allows you to access things like component inputs.
We refer to these render-time-only methods and attributes as the "Render API".
Render API is available inside these Component
methods:
get_template_data()
get_js_data()
get_css_data()
get_context_data()
on_render_before()
on_render_after()
Example:
class Table(Component):
def on_render_before(self, context, template):
# Access component's ID
assert self.id == "c1A2b3c"
# Access component's inputs, slots and context
assert self.args == [123, "str"]
assert self.kwargs == {"variable": "test", "another": 1}
footer_slot = self.slots["footer"]
some_var = self.input.context["some_var"]
def get_template_data(self, args, kwargs, slots, context):
# Access the request object and Django's context processors, if available
assert self.request.GET == {"query": "something"}
assert self.context_processors_data['user'].username == "admin"
rendered = Table.render(
kwargs={"variable": "test", "another": 1},
args=(123, "str"),
slots={"footer": "MY_SLOT"},
)
Overview
The Render API includes:
-
Component inputs:
self.args
- The positional arguments for the current render callself.kwargs
- The keyword arguments for the current render callself.slots
- The slots for the current render callself.context
- The context for the current render callself.input
- All the component inputs
-
Request-related:
self.request
- The request object (if available)self.context_processors_data
- Data from Django's context processors
-
Provide / inject:
self.inject()
- Inject data into the component
-
Template tag metadata:
self.registry
- TheComponentRegistry
instanceself.registered_name
- The name under which the component was registeredself.outer_context
- The context outside of the{% component %}
tag
-
Other metadata:
self.id
- The unique ID for the current render call
Component inputs
Args
The args
argument as passed to
Component.get_template_data()
.
If you defined the Component.Args
class,
then the Component.args
property will return an instance of that class.
Otherwise, args
will be a plain list.
Example:
With Args
class:
from django_components import Component
class Table(Component):
class Args(NamedTuple):
page: int
per_page: int
def on_render_before(self, context: Context, template: Template) -> None:
assert self.args.page == 123
assert self.args.per_page == 10
rendered = Table.render(
args=[123, 10],
)
Without Args
class:
from django_components import Component
class Table(Component):
def on_render_before(self, context: Context, template: Template) -> None:
assert self.args[0] == 123
assert self.args[1] == 10
Kwargs
The kwargs
argument as passed to
Component.get_template_data()
.
If you defined the Component.Kwargs
class,
then the Component.kwargs
property will return an instance of that class.
Otherwise, kwargs
will be a plain dictionary.
Example:
With Kwargs
class:
from django_components import Component
class Table(Component):
class Kwargs(NamedTuple):
page: int
per_page: int
def on_render_before(self, context: Context, template: Template) -> None:
assert self.kwargs.page == 123
assert self.kwargs.per_page == 10
rendered = Table.render(
kwargs={"page": 123, "per_page": 10},
)
Without Kwargs
class:
from django_components import Component
class Table(Component):
def on_render_before(self, context: Context, template: Template) -> None:
assert self.kwargs["page"] == 123
assert self.kwargs["per_page"] == 10
Slots
The slots
argument as passed to
Component.get_template_data()
.
If you defined the Component.Slots
class,
then the Component.slots
property will return an instance of that class.
Otherwise, slots
will be a plain dictionary.
Example:
With Slots
class:
from django_components import Component, Slot, SlotInput
class Table(Component):
class Slots(NamedTuple):
header: SlotInput
footer: SlotInput
def on_render_before(self, context: Context, template: Template) -> None:
assert isinstance(self.slots.header, Slot)
assert isinstance(self.slots.footer, Slot)
rendered = Table.render(
slots={
"header": "MY_HEADER",
"footer": lambda ctx: "FOOTER: " + ctx.data["user_id"],
},
)
Without Slots
class:
from django_components import Component, Slot, SlotInput
class Table(Component):
def on_render_before(self, context: Context, template: Template) -> None:
assert isinstance(self.slots["header"], Slot)
assert isinstance(self.slots["footer"], Slot)
Context
The context
argument as passed to
Component.get_template_data()
.
This is Django's Context with which the component template is rendered.
If the root component or template was rendered with
RequestContext
then this will be an instance of RequestContext
.
Whether the context variables defined in context
are available to the template depends on the
context behavior mode:
-
In
"django"
context behavior mode, the template will have access to the keys of this context. -
In
"isolated"
context behavior mode, the template will NOT have access to this context, and data MUST be passed via component's args and kwargs.
Other inputs
You can access the most important inputs via self.args
,
self.kwargs
,
and self.slots
properties.
There are additional settings that may be passed to components.
If you need to access these, you can use self.input
property
for a low-level access to all the inputs passed to the component.
self.input
(ComponentInput
) has the mostly the same fields as the input to Component.render()
. This includes:
args
- List of positional argumentskwargs
- Dictionary of keyword argumentsslots
- Dictionary of slots. Values are normalized toSlot
instancescontext
-Context
object that should be used to render the component- And other kwargs passed to
Component.render()
liketype
andrender_dependencies
For example, you can use self.input.args
and self.input.kwargs
to access the positional and keyword arguments passed to Component.render()
.
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access component's inputs, slots and context
assert self.input.args == [123, "str"]
assert self.input.kwargs == {"variable": "test", "another": 1}
footer_slot = self.input.slots["footer"]
some_var = self.input.context["some_var"]
rendered = TestComponent.render(
kwargs={"variable": "test", "another": 1},
args=(123, "str"),
slots={"footer": "MY_SLOT"},
)
Component ID
Component ID (or render ID) is a unique identifier for the current render call.
That means that if you call Component.render()
multiple times, the ID will be different for each call.
It is available as self.id
.
The ID is a 7-letter alphanumeric string in the format cXXXXXX
,
where XXXXXX
is a random string of 6 alphanumeric characters (case-sensitive).
E.g. c1a2b3c
.
A single render ID has a chance of collision 1 in 57 billion. However, due to birthday paradox, the chance of collision increases to 1% when approaching ~33K render IDs.
Thus, there is currently a soft-cap of ~30K components rendered on a single page.
If you need to expand this limit, please open an issue on GitHub.
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access component's ID
assert self.id == "c1A2b3c"
Request and context processors
Components have access to the request object and context processors data if the component was:
- Given a
request
kwarg directly - Rendered with
RenderContext
- Nested in another component for which any of these conditions is true
Then the request object will be available in self.request
.
If the request object is available, you will also be able to access the context processors
data in self.context_processors_data
.
This is a dictionary with the context processors data.
If the request object is not available, then self.context_processors_data
will be an empty dictionary.
Read more about the request object and context processors in the HTTP Request section.
from django.http import HttpRequest
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access the request object and Django's context processors
assert self.request.GET == {"query": "something"}
assert self.context_processors_data['user'].username == "admin"
rendered = Table.render(
request=HttpRequest(),
)
Provide / Inject
Components support a provide / inject system as known from Vue or React.
When rendering the component, you can call self.inject()
with the key of the data you want to inject.
The object returned by self.inject()
To provide data to components, use the {% provide %}
template tag.
Read more about Provide / Inject.
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
# Access provided data
data = self.inject("some_data")
assert data.some_data == "some_data"
Template tag metadata
If the component is rendered with {% component %}
template tag,
the following metadata is available:
self.registry
- TheComponentRegistry
instance that was used to render the componentself.registered_name
- The name under which the component was registeredself.outer_context
- The context outside of the{% component %}
tag
{% with abc=123 %}
{{ abc }} {# <--- This is in outer context #}
{% component "my_component" / %}
{% endwith %}
You can use these to check whether the component was rendered inside a template with {% component %}
tag
or in Python with Component.render()
.
class MyComponent(Component):
def get_template_data(self, args, kwargs, slots, context):
if self.registered_name is None:
# Do something for the render() function
else:
# Do something for the {% component %} template tag