Component
Bind components to Roblox instances using the Component class and CollectionService tags.
To avoid confusion of terms:
Component
refers to this module.Component Class
(e.g.MyComponent
through this documentation) refers to a class created viaComponent.new
Component Instance
refers to an instance of a component class.Roblox Instance
refers to the Roblox instance to which the component instance is bound.
Methods and properties are tagged with the above terms to help clarify the level at which they are used.
Types
ExtensionFn
type
ExtensionFn =
(
component
)
→
(
)
ExtensionShouldFn
type
ExtensionShouldFn =
(
component
)
→
boolean
Extension
interface
Extension {
ShouldExtend:
ExtensionShouldFn?
ShouldConstruct:
ExtensionShouldFn?
Constructing:
ExtensionFn?
Constructed:
ExtensionFn?
Starting:
ExtensionFn?
Started:
ExtensionFn?
Stopping:
ExtensionFn?
Stopped:
ExtensionFn?
}
An extension allows the ability to extend the behavior of components. This is useful for adding injection systems or extending the behavior of components by wrapping around component lifecycle methods.
The ShouldConstruct
function can be used to indicate
if the component should actually be created. This must
return true
or false
. A component with multiple
ShouldConstruct
extension functions must have them all
return true
in order for the component to be constructed.
The ShouldConstruct
function runs before all other
extension functions and component lifecycle methods.
The ShouldExtend
function can be used to indicate if
the extension itself should be used. This can be used in
order to toggle an extension on/off depending on whatever
logic is appropriate. If no ShouldExtend
function is
provided, the extension will always be used if provided
as an extension to the component.
As an example, an extension could be created to simply log when the various lifecycle stages run on the component:
local Logger = {}
function Logger.Constructing(component) print("Constructing", component) end
function Logger.Constructed(component) print("Constructed", component) end
function Logger.Starting(component) print("Starting", component) end
function Logger.Started(component) print("Started", component) end
function Logger.Stopping(component) print("Stopping", component) end
function Logger.Stopped(component) print("Stopped", component) end
local MyComponent = Component.new({Tag = "MyComponent", Extensions = {Logger}})
Sometimes it is useful for an extension to control whether or not a component should be constructed. For instance, if a component on the client should only be instantiated for the local player, an extension might look like this, assuming the instance has an attribute linking it to the player's UserId:
local player = game:GetService("Players").LocalPlayer
local OnlyLocalPlayer = {}
function OnlyLocalPlayer.ShouldConstruct(component)
local ownerId = component.Instance:GetAttribute("OwnerId")
return ownerId == player.UserId
end
local MyComponent = Component.new({Tag = "MyComponent", Extensions = {OnlyLocalPlayer}})
It can also be useful for an extension itself to turn on/off
depending on various contexts. For example, let's take the
Logger from the first example, and only use that extension
if the bound instance has a Log attribute set to true
:
function Logger.ShouldExtend(component)
return component.Instance:GetAttribute("Log") == true
end
ComponentConfig
interface
ComponentConfig {
Tag:
string
--
CollectionService tag to use
}
Component configuration passed to Component.new
.
- If no Ancestors option is included, it defaults to
{workspace, game.Players}
. - If no Extensions option is included, it defaults to a blank table
{}
.
Properties
Started
EventComponent ClassComponent.Started:
Signal
Fired when a new instance of a component is started.
local MyComponent = Component.new({Tag = "MyComponent"})
MyComponent.Started:Connect(function(component) end)
Stopped
EventComponent ClassComponent.Stopped:
Signal
Fired when an instance of a component is stopped.
local MyComponent = Component.new({Tag = "MyComponent"})
MyComponent.Stopped:Connect(function(component) end)
Instance
Component InstanceComponent.Instance:
Instance
A reference back to the Roblox instance from within a component instance. When
a component instance is created, it is bound to a specific Roblox instance, which
will always be present through the Instance
property.
MyComponent.Started:Connect(function(component)
local robloxInstance: Instance = component.Instance
print("Component is bound to " .. robloxInstance:GetFullName())
end)
Functions
new
ComponentCreate a new custom Component class.
local MyComponent = Component.new({Tag = "MyComponent"})
A full example might look like this:
local MyComponent = Component.new({
Tag = "MyComponent",
Ancestors = {workspace},
Extensions = {Logger}, -- See Logger example within the example for the Extension type
})
local AnotherComponent = require(somewhere.AnotherComponent)
-- Optional if UpdateRenderStepped should use BindToRenderStep:
MyComponent.RenderPriority = Enum.RenderPriority.Camera.Value
function MyComponent:Construct()
self.MyData = "Hello"
end
function MyComponent:Start()
local another = self:GetComponent(AnotherComponent)
another:DoSomething()
end
function MyComponent:Stop()
self.MyData = "Goodbye"
end
function MyComponent:HeartbeatUpdate(dt)
end
function MyComponent:SteppedUpdate(dt)
end
function MyComponent:RenderSteppedUpdate(dt)
end
HeartbeatUpdate
Component ClassComponent.
HeartbeatUpdate
(
dt:
number
) →
(
)
If this method is present on a component, then it will be
automatically connected to RunService.Heartbeat
.
Method
This is a method, not a function. This is a limitation of the documentation tool which should be fixed soon.
local MyComponent = Component.new({Tag = "MyComponent"})
function MyComponent:HeartbeatUpdate(dt)
end
SteppedUpdate
Component ClassComponent.
SteppedUpdate
(
dt:
number
) →
(
)
If this method is present on a component, then it will be
automatically connected to RunService.Stepped
.
Method
This is a method, not a function. This is a limitation of the documentation tool which should be fixed soon.
local MyComponent = Component.new({Tag = "MyComponent"})
function MyComponent:SteppedUpdate(dt)
end
RenderSteppedUpdate
This item only works when running on the client. ClientComponent ClassComponent.
RenderSteppedUpdate
(
dt:
number
) →
(
)
If this method is present on a component, then it will be
automatically connected to RunService.RenderStepped
. If
the [Component].RenderPriority
field is found, then the
component will instead use RunService:BindToRenderStep()
to bind the function.
Method
This is a method, not a function. This is a limitation of the documentation tool which should be fixed soon.
-- Example that uses `RunService.RenderStepped` automatically:
local MyComponent = Component.new({Tag = "MyComponent"})
function MyComponent:RenderSteppedUpdate(dt)
end
-- Example that uses `RunService:BindToRenderStep` automatically:
local MyComponent = Component.new({Tag = "MyComponent"})
-- Defining a RenderPriority will force the component to use BindToRenderStep instead
MyComponent.RenderPriority = Enum.RenderPriority.Camera.Value
function MyComponent:RenderSteppedUpdate(dt)
end
GetAll
Component Class
Gets a table array of all existing component objects. For example,
if there was a component class linked to the "MyComponent" tag,
and three Roblox instances in your game had that same tag, then
calling GetAll
would return the three component instances.
local MyComponent = Component.new({Tag = "MyComponent"})
-- ...
local components = MyComponent:GetAll()
for _,component in ipairs(components) do
component:DoSomethingHere()
end
FromInstance
Component Class
Gets an instance of a component class from the given Roblox
instance. Returns nil
if not found.
local MyComponent = require(somewhere.MyComponent)
local myComponentInstance = MyComponent:FromInstance(workspace.SomeInstance)
WaitForInstance
Component ClassResolves a promise once the component instance is present on a given Roblox instance.
An optional timeout
can be provided to reject the promise if it
takes more than timeout
seconds to resolve. If no timeout is
supplied, timeout
defaults to 60 seconds.
local MyComponent = require(somewhere.MyComponent)
MyComponent:WaitForInstance(workspace.SomeInstance):andThen(function(myComponentInstance)
-- Do something with the component class
end)
Construct
Component ClassComponent:
Construct
(
) →
(
)
Construct
is called before the component is started, and should be used
to construct the component instance.
local MyComponent = Component.new({Tag = "MyComponent"})
function MyComponent:Construct()
self.SomeData = 32
self.OtherStuff = "HelloWorld"
end
Start
Component ClassComponent:
Start
(
) →
(
)
Start
is called when the component is started. At this point in time, it
is safe to grab other components also bound to the same instance.
local MyComponent = Component.new({Tag = "MyComponent"})
local AnotherComponent = require(somewhere.AnotherComponent)
function MyComponent:Start()
-- e.g., grab another component:
local another = self:GetComponent(AnotherComponent)
end
Stop
Component ClassComponent:
Stop
(
) →
(
)
Stop
is called when the component is stopped. This occurs either when the
bound instance is removed from one of the whitelisted ancestors or when
the matching tag is removed from the instance. This also means that the
instance might be destroyed, and thus it is not safe to continue using
the bound instance (e.g. self.Instance
) any longer.
This should be used to clean up the component.
local MyComponent = Component.new({Tag = "MyComponent"})
function MyComponent:Stop()
self.SomeStuff:Destroy()
end
GetComponent
Component InstanceRetrieves another component instance bound to the same Roblox instance.
local MyComponent = Component.new({Tag = "MyComponent"})
local AnotherComponent = require(somewhere.AnotherComponent)
function MyComponent:Start()
local another = self:GetComponent(AnotherComponent)
end