Reputation: 16496
I have an array of objects. Each of these objects has a "component" property with a string value. Now I would like to loop through the list and render each of the referenced components. Other properties of the looped objects are supposed to provide parameters for the components (not included in example below).
My solution so far works, but requires stating the allowed elements in a switch-case and creates unwanted wrapper elements:
angular.module('switchExample', [])
.controller('ExampleController', ['$scope',
function($scope) {
$scope.items = [{
component: "alpha"
}, {
component: "beta"
}, {
component: "alpha"
}];
}
])
.component('alpha', {
template: "this is component alpha",
})
.component('beta', {
template: "this is component beta"
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
</head>
<body ng-app="switchExample">
<div ng-controller="ExampleController">
<div ng-repeat="s in items" ng-switch="s.component">
<alpha ng-switch-when="alpha"></alpha>
<beta ng-switch-when="beta"></beta>
</div>
</div>
</body>
</html>
Is there a way to include the components procedurally without the round-trip of string comparison and explicit invocation?
Something like this, maybe?:
<div ng-repeat="s in items">
<component ng-component="s.component"></component>
</div>
or even better:
<div ng-repeat="s in items" ng-component="s.component"></div>
or alternatively:
<div ng-component="s.component for s in items"></div>
Upvotes: 8
Views: 1459
Reputation: 16496
Full documentation and download at GitHub: https://github.com/hubertgrzeskowiak/angular-component-directive.
Based on my answer below, I have re-implemented this in a rather clean and more versatile fashion. Demo: http://plnkr.co/edit/uDxIUulQPx4C3s11b5cG?p=preview
Usage (see GitHub fore full docs):
<component name="expr" args="expr" replace="expt"></component>
...where "name" expression must evaluate to a component name (string), "args" (optional) must evaluate to an object and "replace" (optional) must evaluate to a boolean. args are unpacked to attributes for the new component, so every value in that object is an expression. If "replace" is true, the component replaces itself.
Upvotes: 2
Reputation: 16496
There is no such thing as ng-component that would do this seamlessly. This is going to end up as a directive that recompiles its contents (not to mention performance penalty). You can become familiar with the approach by searching something like 'dynamic directive'
estus
Thanks, estus!
I did exactly that!
The custom directive component
you can see below creates components based on passed name
parameter and takes optional args
.
Example input:
<component name="'alpha'"></component>
Output:
<component name="'alpha'">
<alpha>
whatever alpha component decides to render in its template
</alpha>
</component>
With args:
<component name="'alpha'" args="{foo:'bar', num:42, o:{a:1}}"></component>
Output:
<component name="'alpha'" args="{foo:'bar', num:42, o:{a:1}}">
<alpha foo="'bar'" num="42" o="{a:1}">
whatever alpha component decides to render in its template with the given args
</alpha>
</component>
angular.module('switchExample', [])
.directive('component', ['$compile',
function($compile) {
return {
restrict: 'AEM',
scope: {
name: '<',
args: '<'
},
controller: function($scope, $element) {
var args = $scope.args;
//console.log(args)
var argsStr = ""
if (typeof args !== 'undefined' && args.constructor === Object) {
for (arg in args) {
argsStr += ' ' + arg + '="args.' + arg + '"';
}
}
var elem = "<" + $scope.name + argsStr + "></" + $scope.name + ">";
//console.log(elem)
var component = $compile(elem)($scope)[0];
$element.append(component);
}
}
}
])
.controller('ExampleController', ['$scope',
function($scope) {
$scope.items = [{
component: "alpha",
args: {
foo: "first elem"
}
}, {
component: "alpha",
args: {
foo: "the last one has no foo"
}
}, {
component: "alpha",
args: {}
}];
}
])
.component('alpha', {
bindings: {
foo: '<?'
},
template: "this is component alpha. foo arg: {{ $ctrl.foo }}"
})
.component('beta', {
template: "this is component beta"
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
</head>
<body ng-app="switchExample">
<h2>Procedural component</h2>
<component name="'alpha'"></component>
<br>
<component name="'alpha'" args="{foo:'this was passed!', num:123, obj:{}}"></component>
<br>
<div component name="'alpha'" args="{foo:'as was this'}"></div>
<hr>
<h2>Looping</h2>
<div ng-controller="ExampleController">
<div ng-repeat="s in items" ng-switch="s.component">
<component name="s.component" args="s.args"></component>
</div>
</div>
</body>
</html>
I couldn't get rid of the extra HTML elements wrapping my components, but at least I don't need to explicitly tell the loop what is allowed and what not.
Upvotes: 1