1
1
using System ;
2
+ using System . Collections . Generic ;
2
3
using System . Diagnostics ;
3
4
using System . Linq ;
4
5
using System . Reflection ;
11
12
12
13
namespace BlazorLazyLoading
13
14
{
15
+ /// <summary>
16
+ /// Renders a Component (IComponent) from a Lazy Module based on it's 'LazyName' or 'TypeFullName'.
17
+ /// </summary>
14
18
public class Lazy : ComponentBase
15
19
{
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>
34
66
public Type ? Type { get ; protected set ; } = null ;
35
67
68
+ /// <summary>
69
+ /// Exposes the Instance the Lazy Component. Can be accessed from 'OnAfterLoad'.
70
+ /// </summary>
36
71
public IComponent ? Instance { get ; private set ; } = null ;
37
72
38
73
[ Inject ]
@@ -43,21 +78,100 @@ public class Lazy : ComponentBase
43
78
44
79
private RenderFragment ? _currentFallbackBuilder = null ;
45
80
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/>
46
93
protected override void OnInitialized ( )
47
94
{
48
95
_currentFallbackBuilder = Loading ;
49
96
base . OnInitialized ( ) ; // trigger initial render
50
97
}
51
98
99
+ /// <inheritdoc/>
52
100
protected override async Task OnInitializedAsync ( )
53
101
{
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 ) ;
55
133
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 )
57
144
{
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 ;
59
161
}
60
162
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
+ {
61
175
var allManifests = await _manifestRepository . GetAllAsync ( ) . ConfigureAwait ( false ) ;
62
176
63
177
var manifests = allManifests
@@ -98,53 +212,19 @@ protected override async Task OnInitializedAsync()
98
212
99
213
if ( bestMatches == null || ! bestMatches . Any ( ) )
100
214
{
101
- DisplayErrorView ( ) ;
102
215
ThrowIfRequired ( $ "Unable to find lazy component '{ Name } '. Required: { ( Required ? "true" : "false" ) } ") ;
103
- return ;
216
+ return null ;
104
217
}
105
218
106
219
if ( bestMatches . Count > 1 )
107
220
{
108
- DisplayErrorView ( ) ;
109
221
ThrowIfRequired ( $ "Multiple matches for Component with name '{ Name } ': '{ string . Join ( ";" , bestMatches . Select ( m => m . Match . TypeFullName ) ) } '") ;
110
- return ;
222
+ return null ;
111
223
}
112
224
113
225
var bestMatch = bestMatches . First ( ) ;
114
226
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 ) ;
148
228
}
149
229
150
230
private void BuildFallbackComponent ( RenderTreeBuilder builder )
0 commit comments