-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
Summary
Blazor guidelines discourage users from creating Blazor components from .cs
files and at the same time they show warning BL0005: Component parameter should not be set outside of its component
. I understand that it is by design.
This behavior is okay for most cases. Until you start to work with overlay components. For example Popups or BottomSheets then it becomes quite PITA to follow this recommendation.
Motivation and goals
Reconsider or change behavior of this warnings/guidelines so that work with similar type of components is much easier.
Current state (3 options how to create popup component)
- Create the component on the page where do you use it. So in
.razor
you create element<Popup Arg1="" arg2="" />
and you either reference it by@ref
or trigger it's functionality by some special service - Create the component from the function in .razor
@code{}
section. Because only in.razor
we can use@<div></div>
as the input parameter - Create a service with hardcoded parameters. After calling this service it will fill the component somewhere
Disadvantages of these current approaches:
- code placement inconsitancy - we split all UI items into
.razor
, we dont use@code
and all functionality of the UI we extract to.razor.cs
. But in.razor.cs
we cant use the@<div></div>
- control flow - we would have to place some code responsible for filling the component arguments to
@code
part. And then call it from.razor.cs
- usually we put a little bit of C# code to blazor elements and not the other way around. So using special syntax like
@<div></div>
as the method parameter is really ugly
it will be better showcased in EXAMPLES section
In scope / Out of scope
- I really dont know how this should be handled properly
Risks / unknowns
dunno
Examples
Current state in existing component libraries:
Radzen
@<div></div>
as parameter in.razor
@code
section
@code {
async Task ShowDialogWithCustomCssClasses()
{
await DialogService.OpenAsync("Dialog with custom CSS classes", ds =>
@<div>
This dialog has custom CSS classes.
</div>, new DialogOptions() {
CssClass = "custom-dialog-class",
WrapperCssClass = "custom-dialog-wrapper-class"
});
}
}
- hardcoded arguments - required to create some
SideDialogOptions
for each type of dialog component
await DialogService.OpenSideAsync<DialogSideContent>(
"Side Panel",
options: new SideDialogOptions {
CloseDialogOnOverlayClick = closeDialogOnOverlayClick,
Position = position,
ShowMask = showMask
});
}
MudBlazor
- creating component in the
.razor
and then calling it's methods from service
<MudDialog>
<DialogContent>
Dialog Content
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Primary" OnClick="Submit">Ok</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
}
Our approach:
- wherever we inject popup service we can create the component
- for this to work we use reflection to create RenderFragment from already created component provided as input argument.
- in our case the Input (QuestionPopupComponent) is a template component. So if we want to show different popup, we create another template that implements
IPopupReturnable<TOutput>
or in case of BottomSheetsIBottomSheetReturnable<TOutput>
- input arguments are always hardcoded. Inside the popup components we inject In-memory state container service that persists data locally.
//this way of creating the popup is really nice for usage
var isSuccess = await _popupService.Show(
new QuestionPopupComponent{
Title = "Are you sure?",
Message = "Do you want to continue?",
ConfirmButtonText = "Yes",
CancelButtonText = "No"
}
);
if (isSuccess is null)
{
// user closed the popup
}
else if(isSuccess == true)
{
// user clicked the button that returns true
}
else if(isSuccess == false){
// user clicked the button that returns false
}
Discussion
a) Are any of my claims/assumptions incorrect ?
b) What do you think about this way of creating popups, bottom-sheets and other components that overlay the original UI layer ?
c) Is it possible to do this even without reflection ? If RenderFragment
builder OpenComponent
could accept IComponent
or ComponentBase
as an argument I could get rid of whole reflection logic.
d) Are there any other ways how such components can be created except those examples I provided ?