title: Acessing Angular NgForOfContext in a directive
body: <p>I'm developing an Angular directive to detect (and delete) an empty item in an <code>*ngFor</code> list.</p>
  
  <p>Instead of adding an <code>X</code> 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.</p>
  
  <p><a href="https://plnkr.co/edit/dUkJjLzMtkx9C0kUoomS" rel="noreferrer" title="my solution">I've managed to make it</a> (and to my surprise it was not difficult at all). I'll highlight the key aspects here:</p>
  
  <p><strong>Using the directive</strong></p>
  
  <p><em>template</em></p>
  
  <pre><code>  &lt;div *ngFor="let item of data; index as i"
      [appInPlaceDelete]="isEmpty"
      (delete)="onDelete(i)"
    &gt;
  </code></pre>
  
  <p><em>component</em></p>
  
  <pre><code>  onDelete(i: number) {
      // code to erase ith item
    }
  
    private isEmpty(item): boolean {
      // logic to answer if item is empty or not
    }
  </code></pre>
  
  <p><strong>Implementation</strong></p>
  
  <pre><code>@Directive({
    selector: '[appInPlaceDelete]'
  })
  export class InPlaceDeleteDirective&lt;T&gt; implements OnInit, DoCheck {
    @Input() appInPlaceDelete: (t: T) =&gt; boolean;
    @Output() delete: EventEmitter&lt;undefined&gt; = new EventEmitter();
  
    // ...
  
    private $implicit: T;
    private focusstream = new Subject&lt;string&gt;();
  
    constructor(private containerRef: ViewContainerRef) {
    }
  
    ngOnInit() {
      const context: NgForOfContext&lt;T&gt; = 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
    // ...
  }
  </code></pre>
  
  <p>Now, I'm pretty much satisfied with what I did, <strong>except</strong> for that one line:</p>
  
  <pre><code>    const context: NgForOfContext&lt;T&gt; = this.containerRef['_view'].context;
  </code></pre>
  
  <p>because acessing the <code>_view</code> property is not platform portable (perhaps this property only exists in <code>platformBrowserDynamic</code>).</p>
  
  <p><strong>Here come the questions:</strong></p>
  
  <ol>
  <li><strong>Is there any way to capture the <code>context</code> platform-independent-wise?</strong></li>
  </ol>
  
  <p><code>NgForOf</code> stuffs this context there platform independently, <a href="https://github.com/angular/angular/blob/af4eb00c9191f3eedfbb2b0bc6c471cd55f892a6/packages/common/src/directives/ng_for_of.ts#L160" rel="noreferrer" title="source of NgForOf showing the creation of an embedded view with context">as seen on its source code</a>. 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.</p>
  
  <ol start="2">
  <li><strong>Can I make my <code>selector</code> more restrictive to hook only inside an <code>*ngFor</code> host?</strong></li>
  </ol>
  
  <p>For this one I know we should bear in mind that angular translates the original template to:</p>
  
  <pre><code>&lt;ng-template ngFor let-item [ngForOf]="data" let-i="index"&gt;
    &lt;div
      [appInPlaceDelete]="isEmpty"
      (delete)="onDelete(i)"
    &gt;
  &lt;/ng-template&gt;
  </code></pre>
  
  <p>So the actual <code>divs</code> the directive is hooking into are embedded views created dynamically by <code>NgForOf</code> directive.</p>
  
  <p>I wonder if there is some way to enforce this (I tried <code>selector: '[ngFor] &gt; [appInPlaceDelete]'</code> to no avail).</p>
  
  <ol start="3">
  <li><strong>Is there a workaround?</strong></li>
  </ol>
  
  <p>The <code>delete</code> output clearly uses the context variable <code>i</code> (the directive itself knows nothing about <code>i</code>; it just issues an event through the <code>delete</code> channel).</p>
  
  <p>I tried <code>[appInPlaceDelete]="isEmpty(item)"</code> though I knew in advance that it would bind <code>false</code> to <code>appInPlaceDelete</code>.</p>
  
  <p>I also tried <code>[appInPlaceDelete]="() =&gt; isEmpty(item)"</code> and <code>...="function() { return isEmpty(item); }"</code> expecting that angular would create a closure grabbing <code>item</code> (and <code>isEmpty</code>) and pass that closure along. Also no success.</p>
  
  <p>Though somewhat ugly, that would be better than a non-portable directive.</p>
  
  <ol start="4">
  <li><strong>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?</strong></li>
  </ol>
  
  <p>Though not advisable, perhaps I could have some way to determine in which platform my directive is running under and access the <code>context</code> specifically.</p>
  
  <p>In this matter I have to confess I have no clue about how to use (even less debug) anything else other than <code>platformBrowserDynamic</code>.</p>
  
  <ol start="5">
  <li><strong>If you've come so far, is there any other completely different way of doing it?</strong></li>
  </ol>
  
  <p>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).</p>
  
  <p>Maybe there is a completely different way of doing it. Maybe someone already did/packed/published a better solution.</p>