Skip to content

Commit 3be060c

Browse files
committed
Implement #42
- Ability to specify the module manually when using FullType - Ability to pass arbitrary Parameters to the wrapped component
1 parent eb4d45f commit 3be060c

File tree

4 files changed

+187
-61
lines changed

4 files changed

+187
-61
lines changed

demo/Demo.sln

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosts", "Hosts", "{E503BF43
3535
EndProject
3636
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LazyAreas", "LazyAreas", "{566D14F1-B2FB-463C-A627-C5EB23B763B7}"
3737
EndProject
38-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{DB1AE536-72C2-4CCD-A7D9-0A79AAEEDA54}"
38+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{DB1AE536-72C2-4CCD-A7D9-0A79AAEEDA54}"
3939
EndProject
4040
Global
4141
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@attribute [LazyName("Parametrized")]
2+
3+
<h3>Hello @Name, you are @Adjective</h3>
4+
5+
@code {
6+
[Parameter]
7+
public string Name { get; set; }
8+
9+
[Parameter]
10+
public string Adjective { get; set; }
11+
}

demo/WasmHost/Pages/Counter.razor

+37-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<LazyPlaceholder />
1414
</Loading>
1515
<Error>
16-
<h2>Component loading errored...</h2>
16+
<h2 style="color:red">Component loading errored...</h2>
1717
</Error>
1818
</Lazy>
1919

@@ -23,6 +23,38 @@
2323
</Loading>
2424
</Lazy>
2525

26+
<Lazy
27+
Name="Parametrized"
28+
Parameters='new Dictionary<string, object> { { "Name", "Ivan" }, { "Adjective", "awesome" } }'
29+
OnBeforeLoadAsync="Delay(500, 2500)">
30+
31+
<Loading>
32+
<LazyPlaceholder />
33+
</Loading>
34+
<Error>
35+
<h2 style="color:red">Component loading errored...</h2>
36+
</Error>
37+
38+
</Lazy>
39+
40+
<Lazy Name="ErrorLol" Required="false" OnBeforeLoadAsync="Delay(500, 2500)">
41+
<Loading>
42+
<LazyPlaceholder />
43+
</Loading>
44+
<Error>
45+
<h2 style="color:darkorange">(expected) Component loading errored...</h2>
46+
</Error>
47+
</Lazy>
48+
49+
<Lazy ModuleName="WasmHost" Name="WasmHost.Shared.SurveyPrompt" Required="true" OnBeforeLoadAsync="Delay(500, 2500)">
50+
<Loading>
51+
<LazyPlaceholder />
52+
</Loading>
53+
<Error>
54+
<h2 style="color:red">Component loading errored...</h2>
55+
</Error>
56+
</Lazy>
57+
2658
@code {
2759
private int currentCount = 0;
2860

@@ -33,6 +65,9 @@
3365

3466
private Func<Lazy, Task> Delay(int minMs, int maxMs)
3567
{
36-
return (Lazy c) => Task.Delay(new Random().Next(minMs, maxMs));
68+
return (Lazy c) =>
69+
{
70+
return Task.Delay(new Random().Next(minMs, maxMs));
71+
};
3772
}
3873
}

src/LazyComponents/LazyComponent/Lazy.cs

+138-58
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Linq;
45
using System.Reflection;
@@ -11,28 +12,62 @@
1112

1213
namespace BlazorLazyLoading
1314
{
15+
/// <summary>
16+
/// Renders a Component (IComponent) from a Lazy Module based on it's 'LazyName' or 'TypeFullName'.
17+
/// </summary>
1418
public class Lazy : ComponentBase
1519
{
16-
[Parameter]
17-
public string Name { get; set; } = null!;
18-
19-
[Parameter]
20-
public bool Required { get; set; } = false;
21-
22-
[Parameter]
23-
public RenderFragment? Loading { get; set; } = null;
24-
25-
[Parameter]
26-
public RenderFragment? Error { get; set; } = null;
27-
28-
[Parameter]
29-
public Func<Lazy, Task>? OnBeforeLoadAsync { get; set; } = null;
30-
31-
[Parameter]
32-
public Action<Lazy>? OnAfterLoad { get; set; } = null;
33-
20+
/// <summary>
21+
/// <br>Specifies the Component Name. This can be the 'LazyName' or the TypeFullName.</br>
22+
/// <br>'LazyName' can be set using [LazyName] attribute on a external Component inside a module.</br>
23+
/// </summary>
24+
[Parameter] public string Name { get; set; } = null!;
25+
26+
/// <summary>
27+
/// Specifies the list of parameters that will be passed to the Lazy Component.
28+
/// </summary>
29+
[Parameter] public IEnumerable<KeyValuePair<string, object>> Parameters { get; set; } = new Dictionary<string, object>();
30+
31+
/// <summary>
32+
/// <br>Specifies if the Component is required (throws exceptions if load fails) or can error gracefully.</br>
33+
/// <br>default: false</br>
34+
/// </summary>
35+
[Parameter] public bool Required { get; set; } = false;
36+
37+
/// <summary>
38+
/// Specifies a custom 'Loading' view.
39+
/// </summary>
40+
[Parameter] public RenderFragment? Loading { get; set; } = null;
41+
42+
/// <summary>
43+
/// Specifies a custom 'Error' view.
44+
/// </summary>
45+
[Parameter] public RenderFragment? Error { get; set; } = null;
46+
47+
/// <summary>
48+
/// <br>This callback will be awaited before trying to resolve the Component from the manifests.</br>
49+
/// <br>Useful for delaying a Component render and debugging with Task.Delay.</br>
50+
/// </summary>
51+
[Parameter] public Func<Lazy, Task>? OnBeforeLoadAsync { get; set; } = null;
52+
53+
/// <summary>
54+
/// This callback will be invoked after resolving and rendering the Lazy Component.
55+
/// </summary>
56+
[Parameter] public Action<Lazy>? OnAfterLoad { get; set; } = null;
57+
58+
/// <summary>
59+
/// Not recommended to use. Specifies a Module Name directly to avoid reading the manifests and uses Name as TypeFullName.
60+
/// </summary>
61+
[Parameter] public string? ModuleName { get; set; } = null;
62+
63+
/// <summary>
64+
/// Exposes the resolved Type for the Lazy Component. Can be accessed from 'OnAfterLoad'.
65+
/// </summary>
3466
public Type? Type { get; protected set; } = null;
3567

68+
/// <summary>
69+
/// Exposes the Instance the Lazy Component. Can be accessed from 'OnAfterLoad'.
70+
/// </summary>
3671
public IComponent? Instance { get; private set; } = null;
3772

3873
[Inject]
@@ -43,21 +78,100 @@ public class Lazy : ComponentBase
4378

4479
private RenderFragment? _currentFallbackBuilder = null;
4580

81+
/// <inheritdoc/>
82+
public override async Task SetParametersAsync(ParameterView parameters)
83+
{
84+
await base.SetParametersAsync(parameters);
85+
86+
if (Name == null)
87+
{
88+
throw new InvalidOperationException($"The {nameof(Lazy)} component requires a value for the parameter {nameof(Name)}.");
89+
}
90+
}
91+
92+
/// <inheritdoc/>
4693
protected override void OnInitialized()
4794
{
4895
_currentFallbackBuilder = Loading;
4996
base.OnInitialized(); // trigger initial render
5097
}
5198

99+
/// <inheritdoc/>
52100
protected override async Task OnInitializedAsync()
53101
{
54-
await base.OnInitializedAsync().ConfigureAwait(false);
102+
try
103+
{
104+
await base.OnInitializedAsync().ConfigureAwait(false);
105+
106+
if (OnBeforeLoadAsync != null)
107+
{
108+
await OnBeforeLoadAsync(this);
109+
}
110+
111+
string typeFullName = Name;
112+
113+
if (ModuleName == null)
114+
{
115+
var moduleInfo = await ResolveModuleAndType().ConfigureAwait(false);
116+
117+
if (moduleInfo == null)
118+
{
119+
DisplayErrorView(false);
120+
return;
121+
}
122+
123+
(ModuleName, typeFullName) = moduleInfo.Value;
124+
}
125+
126+
Assembly? componentAssembly = await _assemblyLoader
127+
.LoadAssemblyByNameAsync(new AssemblyName
128+
{
129+
Name = ModuleName,
130+
Version = null,
131+
})
132+
.ConfigureAwait(false);
55133

56-
if (OnBeforeLoadAsync != null)
134+
Type = componentAssembly?.GetType(typeFullName);
135+
136+
if (Type == null)
137+
{
138+
ThrowIfRequired($"Unable to load lazy component '{Name}'. Component type '{typeFullName}' not found in module '{ModuleName}'");
139+
DisplayErrorView(false);
140+
return;
141+
}
142+
}
143+
catch (Exception ex)
57144
{
58-
await OnBeforeLoadAsync(this);
145+
DisplayErrorView(false);
146+
ThrowIfRequired(ex.Message); // re-throw if Required is true
147+
}
148+
finally
149+
{
150+
StateHasChanged(); // always re-render after load
151+
}
152+
}
153+
154+
/// <inheritdoc/>
155+
protected override void BuildRenderTree(RenderTreeBuilder builder)
156+
{
157+
if (Type == null)
158+
{
159+
BuildFallbackComponent(builder);
160+
return;
59161
}
60162

163+
builder.OpenComponent(0, Type);
164+
builder.AddMultipleAttributes(0, Parameters);
165+
builder.AddComponentReferenceCapture(1, (componentRef) =>
166+
{
167+
Instance = (IComponent)componentRef;
168+
OnAfterLoad?.Invoke(this);
169+
});
170+
builder.CloseComponent();
171+
}
172+
173+
private async Task<(string, string)?> ResolveModuleAndType()
174+
{
61175
var allManifests = await _manifestRepository.GetAllAsync().ConfigureAwait(false);
62176

63177
var manifests = allManifests
@@ -98,53 +212,19 @@ protected override async Task OnInitializedAsync()
98212

99213
if (bestMatches == null || !bestMatches.Any())
100214
{
101-
DisplayErrorView();
102215
ThrowIfRequired($"Unable to find lazy component '{Name}'. Required: {(Required ? "true" : "false")}");
103-
return;
216+
return null;
104217
}
105218

106219
if (bestMatches.Count > 1)
107220
{
108-
DisplayErrorView();
109221
ThrowIfRequired($"Multiple matches for Component with name '{Name}': '{string.Join(";", bestMatches.Select(m => m.Match.TypeFullName))}'");
110-
return;
222+
return null;
111223
}
112224

113225
var bestMatch = bestMatches.First();
114226

115-
Assembly? componentAssembly = await _assemblyLoader
116-
.LoadAssemblyByNameAsync(new AssemblyName
117-
{
118-
Name = bestMatch.Manifest.ModuleName,
119-
Version = null,
120-
})
121-
.ConfigureAwait(false);
122-
123-
Type = componentAssembly?.GetType(bestMatch.Match.TypeFullName);
124-
125-
if (Type == null)
126-
{
127-
DisplayErrorView(false);
128-
}
129-
130-
StateHasChanged();
131-
}
132-
133-
protected override void BuildRenderTree(RenderTreeBuilder builder)
134-
{
135-
if (Type == null)
136-
{
137-
BuildFallbackComponent(builder);
138-
return;
139-
}
140-
141-
builder.OpenComponent(0, Type);
142-
builder.AddComponentReferenceCapture(1, (componentRef) =>
143-
{
144-
Instance = (IComponent)componentRef;
145-
OnAfterLoad?.Invoke(this);
146-
});
147-
builder.CloseComponent();
227+
return (bestMatch.Manifest.ModuleName, bestMatch.Match.TypeFullName);
148228
}
149229

150230
private void BuildFallbackComponent(RenderTreeBuilder builder)

0 commit comments

Comments
 (0)