2

I have created a custom structural directive similar to ngFor. When I try to use it using <template> element syntax, the binding inside the template is working but when I use * syntax, the binding is not working.

   <!-- working -->
    <template edit [editOf]="values" let-val="val">
      <table >
        <span>This is using template syntax {{val}}</span>
      </table>
    </template>
    
    <!-- not working -->
      <table *edit="let val1 of values">
        <span>This uses star syntax {{val1}}</span>
      </table>

Here is the plnkr link for this issue.

What am I doing wrong?

Update: I think, now I understand what is going on. The let-val is incorrectly set to a property val of object used for binding but I need the whole object. So, the let-val shouldn't be assigned with any value in the template and then I'll have to update the context.$implicit with the object used as binding source in the viewRef.

Working plnkr here.

Thanks to @robisim74

Community
  • 1
  • 1
kodebot
  • 1,718
  • 1
  • 22
  • 29

2 Answers2

4

Try this:

import {  ChangeDetectorRef,
  Directive,
  Input,
  DoCheck,
  IterableDiffer,
  IterableDiffers,
  TemplateRef,
  ViewContainerRef,
  EmbeddedViewRef} from "@angular/core";

@Directive({
  "selector":"[edit][editOf]"
})
export class EditableTableDirective implements DoCheck {

  private collection:any;
  private differ:IterableDiffer;
  private viewMap:Map<any,EmbeddedViewRef> = new Map<any,EmbeddedViewRef>();

  constructor(
    private changeDetector:ChangeDetectorRef,
              private differs:IterableDiffers,
              private template:TemplateRef,
              private viewContainer:ViewContainerRef){
  }

  @Input() set editOf(coll:any){
    this.collection = coll;
    if (coll && !this.differ) {
      this.differ = this.differs.find(coll).create(this.changeDetector);
    }
  }

  ngDoCheck() {
    if (this.differ) {
      const changes = this.differ.diff(this.collection);
      if (changes) {
        changes.forEachAddedItem((change) => {
          const view = this.viewContainer.createEmbeddedView(this.template, change.item);
          view.context.$implicit = change.item;
          this.viewMap.set(change.item, view);
        });
        changes.forEachRemovedItem((change) => {
          const view = this.viewMap.get(change.item);
          const viewIndex = this.viewContainer.indexOf(view);
          this.viewContainer.remove(viewIndex);
          this.viewMap.delete(change.item);
        });
      }
    }
  }

}

Then correct the template:

<table *edit="let item of values">
   <span>This uses star syntax {{item.val}}</span>
</table>  

I followed this article: http://teropa.info/blog/2016/03/06/writing-an-angular-2-template-directive.html

robisim74
  • 741
  • 4
  • 10
  • what I gather from your example is that I need to set the context.$implicit with the object that is used for binding but I don't understand why. Please can you explain? – kodebot Jul 09 '16 at 09:29
  • Not simply this. So that you can use the syntax `*edit`, you need to implement a custom Change Detection through `DoCheck` interface implementation, and in particular its `forEachAddedItem` method, otherwise Angular 2 Change Detection detects no change in the value and doesn't update the view . – robisim74 Jul 09 '16 at 11:39
  • Thanks, but I think DoCheck and other change detection mechanism are just for the efficiency not mandatory for creating structural directive. I have updated my question with working plnkr link. – kodebot Jul 09 '16 at 12:09
  • Indeed they are not necessary for the creation of the directive, but please note that if you dynamically add or remove items from the list without change detection you will not see update the view. I'm happy that at the end you've solved the way you want. Greetings – robisim74 Jul 09 '16 at 15:43
0
 <tr *cpLoop="let cat of categories" (click)="selectCategory(cat)">
                <td>{{cat.categoryId}}</td>
                <td>{{cat.categoryName}}</td>
 </tr>  

 <ng-template cpLoop let-cat [cpLoopOf]="categories" let-i="index">
                 <tr (click)="selectCategory(cat)">                        
                        <td>{{cat.categoryId}}</td>
                        <td>{{cat.categoryName}}</td>
                </tr>            
  </ng-template> 

 @Input() set cpLoopOf(rows:any){
         rows.forEach((row:any, index:any) => {
          let viewRef = this.viewContainer.createEmbeddedView(this.template, row); 
          viewRef.context.$implicit =row;
        });

      } 
Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38