I'm developing an Angular directive to detect (and delete) an empty item in an *ngFor
list.
Instead of adding an X
button (per item) that will trigger deletion, I'm more for a content based approach: if the user erases -- either completely or just some key fields of -- an item, it will cease to exist as soon as the user focuses on another part of the document.
I've managed to make it (and to my surprise it was not difficult at all). I'll highlight the key aspects here:
Using the directive
template
<div *ngFor="let item of data; index as i"
[appInPlaceDelete]="isEmpty"
(delete)="onDelete(i)"
>
component
onDelete(i: number) {
// code to erase ith item
}
private isEmpty(item): boolean {
// logic to answer if item is empty or not
}
Implementation
@Directive({
selector: '[appInPlaceDelete]'
})
export class InPlaceDeleteDirective<T> implements OnInit, DoCheck {
@Input() appInPlaceDelete: (t: T) => boolean;
@Output() delete: EventEmitter<undefined> = new EventEmitter();
// ...
private $implicit: T;
private focusstream = new Subject<string>();
constructor(private containerRef: ViewContainerRef) {
}
ngOnInit() {
const context: NgForOfContext<T> = this.containerRef['_view'].context;
this.$implicit = context.$implicit
// code forwarding 'focusstream' to 'delete' output
}
// code that gives visual indication that the item will be deleted
// ...
// a bunch of @HostListeners feeding the focusstream
// ...
}
Now, I'm pretty much satisfied with what I did, except for that one line:
const context: NgForOfContext<T> = this.containerRef['_view'].context;
because acessing the _view
property is not platform portable (perhaps this property only exists in platformBrowserDynamic
).
Here come the questions:
- Is there any way to capture the
context
platform-independent-wise?
NgForOf
stuffs this context there platform independently, as seen on its source code. Maybe there is (or should be) a way to retrieve that context portably. I know, the developer (me) still has the responsibility of knowing what does that context holds (its type). Hang on, that's for the next question.
- Can I make my
selector
more restrictive to hook only inside an*ngFor
host?
For this one I know we should bear in mind that angular translates the original template to:
<ng-template ngFor let-item [ngForOf]="data" let-i="index">
<div
[appInPlaceDelete]="isEmpty"
(delete)="onDelete(i)"
>
</ng-template>
So the actual divs
the directive is hooking into are embedded views created dynamically by NgForOf
directive.
I wonder if there is some way to enforce this (I tried selector: '[ngFor] > [appInPlaceDelete]'
to no avail).
- Is there a workaround?
The delete
output clearly uses the context variable i
(the directive itself knows nothing about i
; it just issues an event through the delete
channel).
I tried [appInPlaceDelete]="isEmpty(item)"
though I knew in advance that it would bind false
to appInPlaceDelete
.
I also tried [appInPlaceDelete]="() => isEmpty(item)"
and ...="function() { return isEmpty(item); }"
expecting that angular would create a closure grabbing item
(and isEmpty
) and pass that closure along. Also no success.
Though somewhat ugly, that would be better than a non-portable directive.
- Iff there is no other way to answer positively the 1st question, and also no workaround in the 3rd, how can I cover at least the most common platforms?
Though not advisable, perhaps I could have some way to determine in which platform my directive is running under and access the context
specifically.
In this matter I have to confess I have no clue about how to use (even less debug) anything else other than platformBrowserDynamic
.
- If you've come so far, is there any other completely different way of doing it?
The first 2 paragraphs in this post delineate what I'm trying to accomplish here. Everything else is just my solution (and the problems it entails).
Maybe there is a completely different way of doing it. Maybe someone already did/packed/published a better solution.