5

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:

  1. 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.

  1. 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).

  1. 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.

  1. 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.

  1. 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.

rslemos
  • 2,454
  • 22
  • 32
  • Consider to limit this to one question... – Jota.Toledo Jan 20 '18 at 17:11
  • I don't understand how does it got flagged as "too broad"... sure it's a lengthy post, but just because I did my homework. It has 5 questions, but looking closely they are all tied together to the main point (grabbing the ngFor context without breaking platform independentness); the fact that I decided to unfold it in 5 (researched and commented) questions should be praised not lambasted. – rslemos Jan 20 '18 at 18:51

0 Answers0