dotnet/aspnetcore

Enhancements to components

danroth27 opened this issue · 14 comments

Enhancements to components

Core app model features underway:

  • Interaction between components
    • Passing parameters to child components
    • Passing descendant elements to child components
      • Being able to render the captured nodes (e.g., basic wrapper element)
      • Being able to extract structured information from the captured nodes (e.g., dialog with title and body)
      • Being able to transform the captured nodes (e.g., auto wiring up tab buttons in a tab set control)
    • Capturing references to children via ref keyword
      • For components
      • For basic (HTML) element
    • Passing parameters down through a subtree like React's context
    • Notifying a renderer that another component's state has changed out of band
    • Configuring a parameter name on the [Parameter] attribute for (e.g., non-C#-legal names)
    • Controlling whether a parameter is required
    • Controlling whether arbitrary non-declared parameters can be passed
    • In tag helper intellisense, highlight bindables exposed by a component somehow
  • Lifecycle
    • DI support on construction
    • Init
    • Parameters changed
    • Dispose
    • Async variants where appropriate (e.g., Task InitAsync())
    • Notifying that a component's own state has changed out of band (e.g., after HTTP response)
  • Attributes
    • Short-term mechanism for applying attributes to Razor components
    • Longer term, proper tooling support for attribute directive
  • Actions on basic (HTML) elements
    • Capturing via ref
    • @bind or equivalent
    • Accessing the DOM node in JS calls (e.g., to set focus or play media)
  • Rendering
    • Support key to guarantee preservation of elements and component instances
    • API for passing ParameterCollection to dynamically-added root components

Under consideration:

  • Middleware for component activation and parameter passthrough
  • Improved Razor templates tooling support (e.g., void DisplayPerson(RenderTreeBuilder builder, Person person) => @<h1>@person.Name</h1>;). Ideally allowing inline C# control too.
exyi commented

Hi, I don't really like how the component initialization and state management works now. In short - it seems to be too much reflection based. Do you consider redesigning it anyway? Sometimes it seems almost ironic that you have a comment "we could consider removing the 'name equality' check entirely for perf" just before assigning the property by reflection :)

IMO, it would be nicer to have a "component descriptor" that would be able to create a new instance and to update an existing instance (and say if it has been updated) and that would be generated by the Razor compiler. Something like:

private SomeType lastValue_component1_attribute1;
private Descriptor component1 = new Descriptor<MyComponent>(
     create: () => {
            return new MyComponent() {
                  SomeHardcodedAttr = "123"
            }
     },
     update: (component) => {
            bool updated = false;
            var atribute1 = this.Property123; // just get value from closure
            if (attribute1 == lastValue_component1_attribute1) { // no virtual dispatch for comparison
                 component.Attribute1 = lastValue_component1_attribute1 = attribute1;
                 updated = true;
            }
            return updated;
     }
);
void Build(... builder) {
     builder.AddComponent(1, component1);
}

This approach would be much more extensible for alternative component state management approaches. I'm also pretty sure that it would be more performant. While the code might look significantly longer in C#, I'm pretty sure that the difference will not be that dramatic when compiled to MSIL or WASM, and it will also give LLVM much more room for optimizations.

Of course, you could leave the current mechanism as a sugar above this mechanism, noone says that the create functions can't be a Activator.CreateInstance call and the update some foreach with reflection ;)

@danroth27 This is done enough for 0.0.5 so I'm moving it to 0.1.0 now for the remaining items.

Is “Controlling whether a parameter is required” the same as “Allowing optional parameters” ? (e.g. @page(“user/{name}/{id}?”)) If so, then I don’t have to open a new enhancement issue.

@AxDSan in Blazor i don't think it supports the optional parameter yet... i tried to have an optional indicator "?" but got the below error

Error: System.InvalidOperationException: Invalid template 'feed/{FeedId}?'. Missing '}' in parameter segment '{FeedId}?'.

the way to do this, is to have multiple route definition in the page. For e.g. in my case i wanted "/feed/{FeedId}". the FeedId can be empty. so to handle this situation you will need to have the following route defined in the page

@page "/feed"
@page "/feed/{FeedId}"

hope this helps

Yup that's a workaround :D thank you!

Can someone explain in a few words what is: "Controlling whether arbitrary non-declared parameters can be passed". Why should we have this possibility? What are the benefits?

@Andrzej-W Currently, although component A written in Razor can declare parameters via [Parameter], another component B can pass any arbitrary set of other name-value pairs to A as parameters. No error occurs just because A doesn't have a corresponding [Parameter]. In fact, by overriding OnParametersSet, A can programmatically read the list of supplied name-value pairs. There are good use cases for this (consider a wrapper component around a DOM element that wants to output all name/values supplied as DOM attributes, without listing every possible DOM attribute name). There are other cases where it serves no purpose and would be less confusing if prevented.

Quick question, I'm looking through the list of features, but do we have that one thing that allows components to output data back to the parent component? something like Angular maybe like the @output directive? - because [Parameter] is basically our @input() but I don't really see the way of sending data back to a parent component just yet.

@AxDSan Yes, bind (docs). Or manually have the parent pass a callback to the child.

Thank you @SteveSandersonMS for explanation.

Moving out of the preview3 milestone because nothing specific in this issue is planned for preview 3.

I've moved the "key" item to a new issue at #8232

And as for what remains besides that, it's more a list of mostly-outdated ideas than things we're committed to. We can create new issues for anything that still comes up as a requirement.

A moment of contemplation, if you will, as we finally close what was the original issue 1 at https://github.com/aspnet/blazor/issues/1.

Press F to pay respects