Exporting Component And Directive References In Angular 2 Beta 17
Within an Angular 2 template, you can create references to component instances using the "#var" template syntax. This allows you to both reference the component's public API within your template as well as query for that component reference and inject it into your Controller. By default, components expose an implicit export. But, if you have multiple directives bound to a single element, each directive can expose its own bindable public API using the exportAs metadata.
Run this demo in my JavaScript Demos project on GitHub.
In Angular 2, a Component is really just a Directive with a View. And, an element can have any number of directives bound to it; but, only one of those directives can provide the view template. That said, each bound directive can expose its own distinct API in the form of inputs, outputs, and methods. In an Angular 2 template, we can define a reference to each one of these APIs, even when multiple exist on a single element. These API references can then be invoked later on within the same template; or, they can be injected into the parent controller.
To demonstrate, I've put together a small demo with a single component and two additional directives. All three of these directives (remember that components are just directives with views) are bound to the same element. Then, using multiple #var expressions on said element, we're going to both reference each API from within the template as well as inject them into the parent controller using ViewChild queries.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Exporting Component And Directive References In Angular 2 Beta 17
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></lin>
</head>
<body>
<h1>
Exporting Component And Directive References In Angular 2 Beta 17
</h1>
<my-app>
Loading...
</my-app>
<!-- Load demo scripts. -->
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/es6-shim.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/Rx.umd.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/angular2-polyfills.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/angular2-all.umd.js"></script>
<!-- AlmondJS - minimal implementation of RequireJS. -->
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/almond.js"></script>
<script type="text/javascript">
// Defer bootstrapping until all of the components have been declared.
requirejs(
[ /* Using require() for better readability. */ ],
function run() {
ng.platform.browser.bootstrap( require( "App" ) );
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I provide the root application component.
define(
"App",
function registerApp() {
// Configure the App component definition.
ng.core
.Component({
selector: "my-app",
directives: [
require( "ComponentA" ),
require( "DirectiveB" ),
require( "DirectiveC" )
],
// Setup ViewChild queries for the #ref instances.
queries: {
qTestImplicit: new ng.core.ViewChild( "testImplicit" ),
qTestA: new ng.core.ViewChild( "testA" ),
qTestB: new ng.core.ViewChild( "testB" ),
qTestC: new ng.core.ViewChild( "testC" )
},
// In the following template, notice that we are setting up
// multiple references to our component, including an implicit
// #var reference that requires no label.
template:
`
<my-component
#testImplicit
#testA="ComA"
#testB="DirB"
#testC="DirC">
</my-component>
<p>
testImplicit: {{ testImplicit.testLabel }}<br />
testA: {{ testA.testLabel }}<br />
testB: {{ testB.testLabel }}<br />
testC: {{ testC.testLabel }}<br />
</p>
`
})
.Class({
constructor: AppController,
// Define the life-cycle methods on the prototype so that
// Angular will pick them up at runtime.
ngAfterViewInit: function noop() {}
})
;
return( AppController );
// I control the App component.
function AppController() {
var vm = this;
// Expose the public methods.
vm.ngAfterViewInit = ngAfterViewInit;
// ---
// PUBLIC METHODS.
// ---
// I get called once after the view is initialized / checked for the
// first time. At this point, all of the DOM-queries have been linked.
function ngAfterViewInit() {
console.group( "ViewChild Queries" );
console.log( "qTestImplicit:", vm.qTestImplicit );
console.log( "qTestA:", vm.qTestA );
console.log( "qTestB:", vm.qTestB );
console.log( "qTestC:", vm.qTestC );
console.groupEnd();
}
}
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I provide a component that exports a bindable reference.
define(
"ComponentA",
function registerComponentA() {
// Configure the ComponentA component definition.
return ng.core
.Component({
selector: "my-component",
// Here, we are exporting the directive class instance as a
// bindable reference (for the #var syntax).
// --
// NOTE: Components export an implicit reference that can be
// bound without a label. This is in comparison to an Attribute
// Directive, which can only be bound-to with an explicit name.
exportAs: "ComA",
template:
`
<div>
Such component! Much Directive!
</div>
`
})
.Class({
constructor: function ComponentAController() {
this.testLabel = "ComA";
}
})
;
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I provide a directive that exports a bindable reference.
define(
"DirectiveB",
function registerDirectiveB() {
// Configure the DirectiveB directive definition.
return ng.core
.Directive({
selector: "my-component",
// Here, we are exporting the directive class instance as a
// bindable reference (for the #var syntax).
exportAs: "DirB"
})
.Class({
constructor: function DirectiveBController() {
this.testLabel = "DirB";
}
})
;
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I provide a directive that exports a bindable reference.
define(
"DirectiveC",
function registerDirectiveC() {
// Configure the DirectiveC directive definition.
return ng.core
.Directive({
selector: "my-component",
// Here, we are exporting the directive class instance as a
// bindable reference (for the #var syntax).
exportAs: "DirC"
})
.Class({
constructor: function DirectiveCController() {
this.testLabel = "DirC";
}
})
;
}
);
</script>
</body>
</html>
Notice that all but one of the #var attributes has an attribute value (which is the name of the exported instance). The one without a value - #testImplicit - is the implicit binding. An implicit binding is only exposed by a Component directive; attribute directives do not expose implicit binding and must be bound-to with an explicit name.
When we run the above code, we can see that the instance references were consumed successfully in both the template as well as the parent controller:
I really like the fact that you can access each individual directive API, even when multiple directives are bound to the same element. I don't know if I have a great use-case for this off the top of my head. But, I am sure this will come in handy when building Angular 2 applications.
Want to use code from this post? Check out the license.
Reader Comments