36
loading...
This website collects cookies to deliver better user experience
CsdFor
directive. Let's start off by using Angular CLI to create a new module, and directive files:ng generate module for
ng generate directive for/for --module for
# or shorthand
# ng g m for
# ng g d for/for --module for
CsdIf
directive.TemplateRef
, and ViewContainerRef
injected@Input
property to hold the array of items that we want to display
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[csdFor]',
})
export class ForDirective<T> {
constructor(
private templateRef: TemplateRef<unknown>,
private vcr: ViewContainerRef
) {}
@Input() csdForOf: T[] = [];
}
ngOnInit
hook we can render all the items using the provided template:export class ForDirective<T> implements OnInit {
private items: T[] = [];
constructor(
private templateRef: TemplateRef<unknown>,
private vcr: ViewContainerRef
) {}
@Input() csdForOf: T[] = [];
ngOnInit(): void {
this.renderItems();
}
private renderItems(): void {
this.vcr.clear();
this.csdForOf.map(() => {
this.vcr.createEmbeddedView(this.templateRef);
});
}
}
AppComponent
.<div *csdFor="let item of [1, 2, 3, 4, 5]">
<p>This is item</p>
</div>
csdForOf
property to be a setter and rerender items then:export class ForDirective<T> {
private items: T[] = [];
constructor(
private templateRef: TemplateRef<unknown>,
private vcr: ViewContainerRef
) {}
@Input() set csdForOf(items: T[]) {
this.items = items;
this.renderItems();
}
private renderItems(): void {
this.vcr.clear();
this.items.map(() => {
this.vcr.createEmbeddedView(this.templateRef);
});
}
}
"no content"
for each template rendered.<div *csdFor="let item of [1, 2, 3, 4, 5]">
<p>This is item: {{ item || '"no content"' }}</p>
</div>
createEmbeddedView
method of ViewContainerRef
.export class ForDirective<T> {
/* rest of the class */
private renderItems(): void {
this.vcr.clear();
this.items.map((item) => {
this.vcr.createEmbeddedView(this.templateRef, {
// provide item value here
});
});
}
}
item
variable in the template. In our case, the item is a default param, and Angular uses a reserved $implicit
key to pass that variable. With that knowledge, we can finishrenderItems
method:export class ForDirective<T> {
/* rest of the class */
private renderItems(): void {
this.vcr.clear();
this.items.map((item) => {
this.vcr.createEmbeddedView(this.templateRef, {
$implicit: item,
});
});
}
}
NgForOf
directives allows developers to access a set of useful properties on an item's template:index
- the index of the current item in the collection.count
- the length of collectionfirst
- true when the item is the first item in the collectionlast
- true when the item is the last item in the collectioneven
- true when the item has an even index in the collectionodd
- true when the item has an odd index in the collection$implicit
parameter:export class ForDirective<T> {
/* rest of the class */
private renderItems(): void {
this.vcr.clear();
this.items.map((item, index, arr) => {
this.vcr.createEmbeddedView(this.templateRef, {
$implicit: item,
index,
first: index === 0,
last: index === arr.length - 1,
even: (index & 1) === 0,
odd: (index & 1) === 1,
count: arr.length,
});
});
}
}
<div
*csdFor="
let item of [1, 2, 3, 4, 5];
let i = index;
let isFirst = first;
let isLast = last;
let isEven = even;
let isOdd = odd;
let size = count
"
>
<p>This is item: {{ item }}.</p>
<pre>
Index: {{ i }}
First: {{ isFirst }}
Last: {{ isLast }}
Even: {{ isEven }}
Odd: {{ isOdd }}
Count: {{ size }}
</pre
>
</div>
csdFor
directive. This is very useful as it will make sure we don't mistype the property name as well as we only use the item
, and additional properties properly. Angular's compiler allows us to define a static ngTemplateContextGuard
methods on a directive that it will use to type-check the variables defined in the template. The method has a following shape:static ngTemplateContextGuard(
dir: DirectiveClass,
ctx: unknown): ctx is DirectiveContext {
return true;
}
DirectiveClass
will need to conform to DirectiveContext
. In our case, this can be the following:interface ForDirectiveContext<T> {
$implicit: T;
index: number;
first: boolean;
last: boolean;
even: boolean;
odd: boolean;
count: number;
}
@Directive({
selector: '[csdFor]',
})
export class ForDirective<T> {
static ngTemplateContextGuard<T>(
dir: ForDirective<T>,
ctx: unknown
): ctx is ForDirectiveContext<T> {
return true;
}
/* rest of the class */
}
<div *csdFor="let item of [1, 2, 3, 4, 5]">
<p>This is item: {{ item.someProperty }}.</p>
</div>
<div *csdFor="let item of [1, 2, 3, 4, 5]; let isFirst = firts">
<p>This is item: {{ item }}.</p>
</div>
NgForOf
directive. The same approach can be used to create any other custom directive that your project might need. As you can see, implementing a custom directive with additional template properties and great type checking experience is not very hard.