Creating directives is a big part of taking full advantage of all that the Angular framework has to offer, and the best way to take advantage of Angular is with Dart. If you've ever created a directive, it was probably an attribute directive, and if you've used Angular 2+ at all, you've definitely created the specialized form of directive known as a component. In this tutorial, you'll build a somewhat more advanced type, called a structural directive.
If you're unfamiliar with Dart or Angular, you may want to visit a more comprehensive tutorial like Build a Real-Time Chat Web App with AngularDart and Firebase, or any of the many other beginner-oriented articles on Dart Academy. Or if you're really new, try the Quickstart. For this article, we're going to concentrate entirely on the specific subject of structural directives.
Code tested with Dart SDK 1.24.2 and AngularDart 4.0.0.
What's a Structural Directive?
A structural directive is meant to change the DOM layout by adding and removing elements. Sounds simple, right? This is best illustrated with an example from Angular's built-in directives: NgIf.
<div *ngIf="true">
Hi! Isn't AngularDart just fantastic?
</div>
Typically, you would include a more interesting condition in place of true
here, but you probably already know what will happen with this code. Because the condition you passed to NgIf resolves to true
, the <div>
will be rendered in the DOM, and the message will show up on the screen.
If that condition is false
, the <div>
is not rendered. It's not just hidden, as with display: none
; it's not in the DOM at all. Use the DOM inspector to see for yourself if you don't believe me. If you discover that I'm right, be sure to treat that as a representative precedent and trust me from now on.
Another Way To Put It
What's with the asterisk in that example? If you haven't wondered this at some point, you are not paying attention to your lessons well enough. That thing is shorthand (syntactic sugar) for using the HTML 5 <template>
tag, like so:
<template [ngIf]="true">
<div>
Hi! Isn't AngularDart just fantastic?
</div>
</template>
You can totally write your code this way if you want to, but most of us prefer the shorthand. However, it's important to understand how it all unfolds when you start writing your own structural directives.
Create Your Own
Now I'll show you how you can create your own structural directive. This directive will add a template to the DOM only when its data's active
field is true
.
First, Some Data for Context
In your parent component, add some code like this list of users. Note that we're using Maps here for simplicity, but since this is Dart, you should really use classes, interfaces, and so on for a real app.
List<Map> users = [
{"name": "General Dartman", "active": true},
{"name": "Major TypeScript", "active": false},
{"name": "Private JavaScript", "active": true},
{"name": "Corporal Rust", "active": false},
{"name": "Sergeant Go", "active": true}
];
The idea here is that you want to throw all these into a list, but you only want the active ones to show up.
Next, the Directive
lib/src/directives/display_active_dir.dart
import 'package:angular/angular.dart';
@Directive(selector: '[displayActive]')
class DisplayActiveDirective {
TemplateRef _templateRef;
ViewContainerRef _viewContainer;
DisplayActiveDirective(this._templateRef, this._viewContainer);
@Input()
set displayActive(Map data) {
if (data != null && data['active']) {
_viewContainer.createEmbeddedView(_templateRef);
} else {
_viewContainer.clear();
}
}
}
The first thing to note here is how much nicer and cleaner this code is compared to that other Angular language, especially when it comes to imports, the constructor, and accessing member variables.
Import
At the top of your directive file, you import Angular, which gives you access to everything you need to create a structural directive.
Decorator
You use the @Directive()
decorator to tell Angular that your class, DisplayActiveDirective, will be a directive. You don't need to tell it anything special to make it a structural directive. The selector uses the CSS attribute syntax with the square brackets, meaning that any element with displayActive
as an attribute will have this directive applied, just like with the more common attribute directives.
Injections
To mess with DOM structure, you need access to a few of Angular's structural references, namely TemplateRef
and ViewContainerRef
. In the class's constructor, Dart's shorthand syntax for field initialization is used to make _templateRef
a reference to your directive's <template>
tag, which Angular will automatically add for you via the *
syntax when you make use of displayActive
. The _viewContainer
property gives you access to the DOM renderer.
Need Some Input
Lastly, you create an @Input() setter with the same name as the attribute from @Directive()
's selector
parameter. The directive's consumer will pass a Dart Map into the directive like this: *displayActive="myMap"
. There's that asterisk again! Be sure you don't forget it.
If the setter finds that the incoming Map has a true
active
field, it uses the _viewContainer
to render the contents of _templateRef
to the DOM. Those contents are whatever is within the invisible <template>
tag that Angular provides when you use the asterisk syntax. If the data fails to pass muster, the renderer is used to clear the deck.
Try It
To use the new directive, there are just a few steps.
Import It
You need to import your new directive into any component that will make use of it, and that will look something like this:
lib/app_component.dart
import 'src/directives/display_active_dir.dart';
Include It
In the parent component's @Component()
decorator, there is a directives
parameter. Your new directive's class name should appear in that list, something like this:
lib/app_component.dart
@Component(selector: 'my-app',
templateUrl: 'app_component.html',
directives: const [DisplayActiveDirective]
)
Use It
To use DisplayActiveDirective, apply the correct attribute to any element, like so:
lib/app_component.html
<div *ngFor="let user of users">
<p *displayActive="user">{{user['name']}}</p>
</div>
Yes, stars! Preceding displayActive
with an asterisk causes Angular to expand the code to look like this:
<template [displayActive]="user">
<p>{{user['name']}}</p>
</template>
If you forget to include the asterisk in the concise example, you'll see a terrifying error message: No provider for TemplateRef!
If you see that, you need more stars.
The Output
If you've done everything correctly, your output should be:
General Dartman
Private JavaScript
Sergeant Go
The other users are inactive, so they don't make the cut.
Conclusion
If you enjoyed this brief exploration into the world of structural directives in AngularDart, you'll really enjoy using these technologies to build amazing things in record time. It's up to you to get Dart out there, doing the good it was designed to do.