Using An Item Template With An HTML Dropdown Menu Component In Angular 2 RC 3

转载 2016年08月30日 16:27:34

A while ago, I played around with trying to create an HTML Dropdown menu component in Angular 2. This was quite a non-trivial task, but very worthwhile. Recently, however, I discovered that you could pass Template references into components for dynamic rendering. And, while not exactly the same thing, I wanted to see if the use of a TemplateRef could significantly decrease the complexity of a custom HTML dropdown component in Angular 2 RC 3.

         
   
     

Run this demo in my JavaScript Demos project on GitHub.

The idea behind a custom HTML dropdown menu component is that the rendering of each item can be controlled by the calling context. In my previous HTML downdown post, I did this by nesting Item components inside the Dropdown component. In that approach, each Item component had to inject and then communicate with the parent Dropdown component. This worked, but required intricate communication, management of the component life-cycle, and imperative references to the HTML markup.

If we could replace that nested Item component with a simple TemplateRef, it could remove a lot of complexity around that communication and component life-cycle management. Not to mention that we might be able to remove any imperative manipulation of the HTML. To explore this idea, I put together a simple HTML dropdown menu that looks for a TemplateRef in its content children:

  // Import the core angular services.
  import { Component } from "@angular/core";
   
  // Import the application components and services.
  import { HtmlDropdownComponent } from "./html-dropdown.component";
   
  interface Friend {
  id: number;
  name: string;
  avatar: string;
  }
   
  @Component({
  selector: "my-app",
  directives: [ HtmlDropdownComponent ],
   
  // I our view, notice that we are providing a TemplateRef as a child element of the
  // HtmlDropdownComponent. The dropdown component will query for this template and
  // then use it to render both the option items as well as the root item.
  template:
  `
  <p>
  <strong>Best Friend</strong>: {{ bestFriend?.name || "None selected" }}
  &mdash;
  <a (click)="clearSelection()">Clear selection</a>
  </p>
 
  <html-dropdown
  [items]="friends"
  [(value)]="bestFriend"
  placeholder="Select Friend">
 
  <template let-friend="item">
  <img [src]="( './img/' + friend.avatar )" />
  <span class="name">
  {{ friend.name }}
  </span>
  </template>
 
  </html-dropdown>
  `
  })
  export class AppComponent {
   
  // I hold the friend that is selected as the best friend.
  public bestFriend: Friend;
   
  // I hold the collection of friends.
  public friends: Friend[];
   
   
  // I initialize the component.
  constructor() {
   
  this.bestFriend = null;
  this.friends = [
  {
  id: 1,
  name: "Joanna",
  avatar: "joanna-avatar.jpg"
  },
  {
  id: 2,
  name: "Kim",
  avatar: "kim-avatar.jpg"
  },
  {
  id: 3,
  name: "Sarah",
  avatar: "sarah-avatar.jpg"
  },
  {
  id: 4,
  name: "Tricia",
  avatar: "tricia-avatar.jpg"
  }
  ];
   
  }
   
   
  // ---
  // PUBLIC METHODS.
  // ---
   
   
  // I clear the best-friend selection.
  public clearSelection() : void {
   
  this.bestFriend = null;
   
  }
   
  }
view rawapp.component.ts hosted with ❤ by GitHub

Notice that we are passing in the [value] and [items] input properties to the HtmlDropdownComponent; but, that we are also providing a TemplateRef as the definition of how to render each item - in this case, with an avatar and a name. From a component consumption standpoint, this is really close to where we want to be, at least for a simple HTML dropdown menu.

Once the HtmlDropdownComponent has a reference to this item TemplateRef, it can use it to render both the options in the item collection as well as the selected option in the dropdown menu root. The fact that it can use the TemplateRef to render both of these means that we no longer have to worry about cloning HTML, which is a huge win!

  // Import the core angular services.
  import { Component } from "@angular/core";
  import { ContentChild } from "@angular/core";
  import { EventEmitter } from "@angular/core";
  import { TemplateRef } from "@angular/core";
   
  @Component({
  selector: "html-dropdown",
  inputs: [ "items", "value", "placeholder" ],
  outputs: [ "valueChange" ],
   
  // Query for the template being provided by the calling context.
  queries: {
  itemTemplate: new ContentChild( TemplateRef )
  },
  host: {
  "[class.is-open]": "isShowingItems"
  },
  template:
  `
  <div (click)="toggleItems()" class="dropdown-root" [ngSwitch]="!! value">
  <div *ngSwitchCase="true" class="dropdown-item-content">
 
  <template
  [ngTemplateOutlet]="itemTemplate"
  [ngOutletContext]="{ item: value, index: -1 }">
  </template>
 
  </div>
  <div *ngSwitchCase="false" class="placeholder">
 
  {{ placeholder || "Nothing Selected" }}
 
  </div>
  </div>
 
  <ul *ngIf="isShowingItems" class="dropdown-items">
  <li
  *ngFor="let item of items ; let index = index ;"
  (click)="selectItem( item )"
  class="dropdown-item">
 
  <div class="dropdown-item-content">
 
  <template
  [ngTemplateOutlet]="itemTemplate"
  [ngOutletContext]="{ item: item, index: index }">
  </template>
 
  </div>
 
  </li>
  </ul>
  `
  })
  export class HtmlDropdownComponent {
   
  // I determine if the dropdown items are being shown.
  public isShowingItems: boolean;
   
  // INPUT: I am the collection of items used to render the dropdown items.
  public items: any[];
   
  // INPUT: I am the text to show when no item is selected.
  public placeholder: string;
   
  // INPUT: I am the currently selected value.
  public value: any;
   
  // OUTPUT: I am the output event stream that emits the item selected by the user.
  public valueChange: EventEmitter<any>;
   
   
  // I initialize the component.
  constructor() {
   
  this.isShowingItems = false;
  this.valueChange = new EventEmitter();
   
  }
   
   
  // ---
  // PUBLIC METHODS.
  // ---
   
   
  // I hide the dropdown items.
  public hideItems() : void {
   
  this.isShowingItems = false;
   
  }
   
   
  // I select the given item.
  // --
  // NOTE: Since this is a one-way data flow, the selection is being emitted rather
  // than applied directly to the value.
  public selectItem( item: any ) : void {
   
  this.hideItems();
  this.valueChange.emit( item );
   
  }
   
   
  // I show the dropdown items.
  public showItems() : void {
   
  this.isShowingItems = true;
   
  }
   
   
  // I show or hide the dropdown items depending on their current visibility.
  public toggleItems() : void {
   
  this.isShowingItems
  ? this.hideItems()
  : this.showItems()
  ;
   
  }
   
  }

This isn't a fully-featured dropdown menu; but, for the brevity of the code, it actually accomplishes quite a bit. Notice that we are gathering the TemplateRef through a ChildContent query. Then, we use that TemplateRef to render both the items and the menu root. This makes the entire view declarative - no manually manipulating the HTML; no cloning of LI item content into the root. The entire state of the view is managed directly by Angular.

There's a lot of dropdown menu functionality that we're not implementing for the sake of simplicity. But, when we run this code, you can see the items being rendered based on the template:

         
   Using a TemplateRef to provide custom item rendering in an HTML dropdown menu component in Angular 2 RC 3.  
     

As you can see, both the dropdown menu root as well as the dropdown menu items are being rendered by the TemplateRef that was supplied by the calling context.

I'm rather enamored with this idea of being able to pass TemplateRefs around in Angular 2. I think it can really simplify some solutions. Plus, the TemplateRefs have the added benefit of not existing until they are rendered. In comparison, ng-content components exist regardless of whether or not they are being rendered in the DOM (Document Object Model). So, to some degree, TemplateRef may have less overhead. Definitely a topic worth noodling on.

原文链接: http://www.bennadel.com/blog/3116-using-an-item-template-with-an-html-dropdown-menu-component-in-angular-2-rc-3.htm

相关文章推荐

Embed an HTML control in your own window using plain C

Embed an HTML control in your own window using plain C By Jeff Glatt, 3 Aug 2006 Mandatory COM ...

how to use a SQLite database in a standalone program with an HTML interface and VBScript as the programming language

This article describes how to use a SQLite database in a standalone program with an HTML interface a...

Developing an Angular 2 Edge azw3

  • 2017-10-05 16:11
  • 537KB
  • 下载

[Coursera][Rice] An introduction to interactive Programming in Python Week 2 3

一个十一基本上毁掉了很多事,总想着再说再说,懒的动懒的动,事情就这样一天天拖下去,日子就这么一天天过去,那么再读研的这三年又能有什么意义 Week 2 和 Week 3 全部错过了,严重拖延失败症 ...

How to Make an HTTP Connection Using TCP/IP with RSocket

Reviewer Approved     The following code shows how to make an HTTP connection using a TCP/IP wit...

How to Send an Email Using UTL_SMTP with Authenticated Mail Server

In this Document   Goal   Solution   References ...

Adding Custom Actions to the List Item Menu in SharePoint 2010 Using SharePoint Designer 2010[Sharepoint中添加自定义菜单]

http://www.endusersharepoint.com/2010/02/02/adding-custom-actions-to-the-list-item-menu-in-sharepoin...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)