This is a feature request for procedurally/dynamically compiled components.
What is the current behavior?
As of now, when declaring a component in HTML you can only hard-code the tag. This is a limitation, when you want the type of a component to be determined based on the current container's state. A common workaround for this is using ng-switch or ng-if alongside multiple hard-coded components:
<div ng-switch="dynamic.component">
<alpha ng-switch-when="alpha"></alpha>
<beta ng-switch-when="beta"></beta>
</div>
or
<alpha ng-if="dynamic.component == 'alpha'"></alpha>
<beta ng-if="dynamic.component == 'beta'"></beta>
In my opinion this is an antipattern, because
- it requires hard-coding the possible outcomes in the template
- the boilerplate code easily introduces bugs
What is the expected behavior?
What angular is currently missing are dynamic components, which types are determined at runtime. I'm therefore suggesting a new directive to be implemented, which either replaces itself with a given component or creates that component inside its own element. It would need to take two arguments: a component name (expression evaluated to string) and its parameters/attributes packed into an object.
The following syntax suggestions are separated into component definition (A and B) and arguments passing (C and D). All combinations are part of the overall suggestion.
Syntax suggestion A:
<component name="'alpha'"></component>
which would render the component inside itself:
<component name="'alpha'">
<alpha></alpha>
</component>
Note that the value of the name attribute is an Angular expression, so you could also write name="$ctrl.componentname"
Syntax suggestion B:
<component name="'alpha'"></component>
or
<ANYELEMENT component="'alpha'"></ANYELEMENT>
which would render:
<alpha component="'alpha'"></alpha>
thus de-facto replacing itself.
Syntax suggestion C:
<component name="'alpha'" args="{ numb: 123, strng : '\"arg2\"', exp: "scopeexpression" }"></component>
which would render:
<component name="'alpha'" args="{ numb : 123, strng : '\"arg2\"', exp: "scopeexpression" }">
<alpha numb="123" strng="'arg2'" exp="scopeexpression"></alpha>
</component>
The above syntax looks much better when using variables of the current scope:
<component name="parent.cmp_name" args="parent.cmp_args"></component>
which would render:
<component name="parent.cmp_name" args="parent.cmp_args">
<alpha numb="args.numb" strng="args.strng" exp="args.exp"></alpha>
</component>
As you can see, the bindings of the alpha component are served by the component's scope, which has a binding to its own "args" attribute.
Syntax suggestion D:
<component name="expr">
<placeholder numb="expr1" strng="expr2" exp="expr3"></placeholder>
</component>
which renders to:
<component name="'alpha'">
<alpha numb="expr1" strng="expr2" exp="expr3"></alpha>
</component>
In this case the component doesn't create any new elements, but only changes the tag of its contained "placeholder" elements. The bindings stay as they are and there is no need for packing them into one object. I think, this is the best solution since it doesn't mess with proxying of the arguments and enables a much simpler syntax.
What is the motivation / use case for changing the behavior?
Use case 1:
A dashboard widget is being configured. The user is presented a dropdown list (select-element) with possible widgets: gauge, line-graph, pie-chart. Once he choses one, the widget should be rendered beside. Using procedural/dynamic components you could save the component names as values in the select-element and the dynamic component would render the selected one, without knowing anything about the list's contents. This way the view is reactive and needs no adjustments when the list-entries change.
Example code:
<select ng-model="$ctrl.widget" ng-options="$ctrl.options">
<option value="gauge">Gauge</option>
<option value="linegraph">Line Graph</option>
<option value="piechart">Pie Chart</option>
</select>
<component name="$ctrl.widget"></component>
Use case 2:
Think of a typical smartphone chat app. The conversation view consists of a scrollable list of various items: incoming messages, outgoing messages, status infos, horizontal lines (separators), date indicators etc. Using Angular you would want to save all these items in one array and simply iterate in the view. If you save the type of object alongside the object itself in the list, you can use the procedural/dynamic component directive to render the component inside a ng-repeat in only one line of code.
Example code:
<component ng-repeat="item in $ctrl.conversationItems" name="item.type"></component>
alternatively:
<div ng-repeat="item in $ctrl.conversationItems">
<component name="item.type"></component>
</div>
Hint: Android implementation of WhatsApp most probably uses RecyclerView, which is basically a list widget that can hold various item-widgets. Based on the list item's data, different item-widget can be used.
Implications
This technique can act as Inversion Of Control, since the data tells how it is to be rendered, not the parent template. However, the concerns of model, view and controller stay separated, since we only carry a reference to the component - its name.
Since components are like elements to the browser, unknown components and recognized HTML elements are no problem and render normally (like hard-coded).
This is a feature request for procedurally/dynamically compiled components.
What is the current behavior?
As of now, when declaring a component in HTML you can only hard-code the tag. This is a limitation, when you want the type of a component to be determined based on the current container's state. A common workaround for this is using
ng-switchorng-ifalongside multiple hard-coded components:or
In my opinion this is an antipattern, because
What is the expected behavior?
What angular is currently missing are dynamic components, which types are determined at runtime. I'm therefore suggesting a new directive to be implemented, which either replaces itself with a given component or creates that component inside its own element. It would need to take two arguments: a component name (expression evaluated to string) and its parameters/attributes packed into an object.
The following syntax suggestions are separated into component definition (A and B) and arguments passing (C and D). All combinations are part of the overall suggestion.
Syntax suggestion A:
which would render the component inside itself:
Syntax suggestion B:
or
which would render:
thus de-facto replacing itself.
Syntax suggestion C:
which would render:
The above syntax looks much better when using variables of the current scope:
which would render:
As you can see, the bindings of the alpha component are served by the component's scope, which has a binding to its own "args" attribute.
Syntax suggestion D:
which renders to:
In this case the component doesn't create any new elements, but only changes the tag of its contained "placeholder" elements. The bindings stay as they are and there is no need for packing them into one object. I think, this is the best solution since it doesn't mess with proxying of the arguments and enables a much simpler syntax.
What is the motivation / use case for changing the behavior?
Use case 1:
A dashboard widget is being configured. The user is presented a dropdown list (select-element) with possible widgets: gauge, line-graph, pie-chart. Once he choses one, the widget should be rendered beside. Using procedural/dynamic components you could save the component names as values in the select-element and the dynamic component would render the selected one, without knowing anything about the list's contents. This way the view is reactive and needs no adjustments when the list-entries change.
Example code:
Use case 2:
Think of a typical smartphone chat app. The conversation view consists of a scrollable list of various items: incoming messages, outgoing messages, status infos, horizontal lines (separators), date indicators etc. Using Angular you would want to save all these items in one array and simply iterate in the view. If you save the type of object alongside the object itself in the list, you can use the procedural/dynamic component directive to render the component inside a ng-repeat in only one line of code.
Example code:
alternatively:
Implications
This technique can act as Inversion Of Control, since the data tells how it is to be rendered, not the parent template. However, the concerns of model, view and controller stay separated, since we only carry a reference to the component - its name.
Since components are like elements to the browser, unknown components and recognized HTML elements are no problem and render normally (like hard-coded).