11using System ;
2+ using System . Collections . Generic ;
23using System . Diagnostics ;
34using System . Linq ;
45using System . Reflection ;
1112
1213namespace 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