https://toddmotto.com/angular-ngfor-template-element#lttemplategt-element



Angular ngFor,<template> and the compiler

90% Unlimited Downloads Choose from Over 300,000 Vectors,Graphics & Photos.ads viaCarbon

Angular ngFor is a built-in Directive that allows us to iterate over a collection. This collection is typically an array,however can be “array-like”. To demonstrate this,we’ll be usingRx.Observable.of() to initialise our collection with an Observable instead of a static array.

We’ll also be exploring some other under-the-hood properties of ngFor,as well as looking at how Angular expands our ngFor to a <template> element and composes our view.

Table of contents

  • Using the ngFor directive
    • NgModule import
    • Iterating collections
    • Using ngFor
    • Using trackBy for keys
    • Capturing “index” and “count”
    • Accessing first,last,odd,even
  • <template> element
    • <template> and Web Components
    • ngFor and <template>
    • ngFor and embedded view templates
    • Context and passing variables

Using the ngFor directive

In this section,we’ll be taking a look at the featureset ngFor provides,as well as some use case examples.

NgModule import

First of all,to use ngFor,you need to import theCommonModule. However,if you’re using thebrowserModule in the root module,you won’t need to import it,as thebrowserModule exposes the CommonModule for us - so you only need to import the CommonModule when creating further modules with @NgModule.

Our @NgModule will look like this:

import { NgModule } from '@angular/core';
import { browserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [browserModule],
  bootstrap: [AppComponent]
})
export class AppModule {}

For this article,we’ll be including a further ContactCardComponent component in our @NgModule:

// ...
import { ContactCardComponent } from './contact-card.component';

@NgModule({
  declarations: [
    AppComponent,
    ContactCardComponent
  ],
  // ...
})
export class AppModule {}

Our ContactCardComponent simply looks like this,taking a single@Input of contact:

import { Component, Input } from '@angular/core';

import { Contact } from './models/contact.interface';

@Component({
  selector: 'contact-card',
  template: `
    <div class="contact-card">
      <p>{{ contact.name }} ( {{ contact.age }} )</p>       <p>{{ contact.email }}</p>     </div>   `
})
export class ContactCardComponent {
  
  @Input()
  contact: Contact;
  
}

So Now we’re all setup,what’s next?

Iterating collections

Now that our ContactCardComponent is included in our module,we can setup ourAppComponent to use this dataset:

@Component({...})
export class AppComponent implements OnInit {
  contacts: Observable<Contact[]>;
  ngOnInit() {
    this.contacts = Observable.of([
      {
        "id": 1,
        "name": "Laura",
        "email": "lbutler0@latimes.com",
        "age": 47
      },
      {
        "id": 2,
        "name": "Walter",
        "email": "wkelley1@goodreads.com",
        "age": 37
      },
      {
        "id": 3,
        "email": "wgutierrez2@smugmug.com",
        "age": 49
      },
      {
        "id": 4,
        "name": "Jesse",
        "email": "jarnold3@com.com",
      {
        "id": 5,
        "name": "Irene",
        "email": "iduncan4@oakley.com",
        "age": 33
      }
    ]);
  }
}

As mentioned in the introduction,I’m using Observable.of here from RxJS to give me an Observable stream from the results,this is a nice way to mimic an Observable response,such as when using the AngularHttp module to return data from your API.

Using ngFor

Now we’re setup,we can look into our AppComponent template:

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <ul>
        <li>
          <contact-card></contact-card>         </li>       </ul>     </div>   `
})

You can see I’m declaring <contact-card> inside of here,as we want to iterate our dataset and populate each contact via the@Input setup inside our ContactCardComponent.

One way we Could do this is using ngFor on the component itself,however for simplicity we’ll use the unordered list. Let’s addngFor:

<ul>
  <li *ngFor="let contact of contacts">
    <contact-card></contact-card>
  </li>
</ul>

There are a few things happening here,the first you’ll notice a * character at the beginning of the ngFor,we’ll come onto what this means in the next section when we look at the<template> element. Secondly,we’re creating a context calledcontact,using a “for of” loop - just like in ES6.

The ngFor Directive will clone the <li> and the child nodes. In this case,the <contact-card> is a child node,and a card will be “stamped out” in the DOM for each particular item inside ourcontacts collection.

So,Now we have contact available as an individual Object,we can pass the individualcontact into the <contact-card>:

<ul>
  <li *ngFor="let contact of contacts">
    <contact-card [contact]="contact"></contact-card>
  </li>
</ul>

If you’re using a static array,or binding the result of an Observable to the template,you can leave the template as it currently is. However,we can optionally bind the Observable directly to the template,which means we’ll need theasync pipe here to finish things off:

<ul>
  <li *ngFor="let contact of contacts | async">
    <contact-card [contact]="contact"></contact-card>
  </li>
</ul>

You can check a live output of what we’ve covered so far here:

Using trackBy for keys

If you’re coming from an Angular 1.x background,you’ll have likely seen “track by” when using anng-repeat,and similarly in React land,usingkey on a collection item.

So what do these do? They associate the objects,or keys,with the particular DOM nodes,so should anything change or need to be re-rendered,the framework can do this much more efficiently. Angular’sngFor defaults to using object identity checking for you,which is fast,but can befaster!

This is where trackBy comes into play,let’s add some more code then explain:

<ul>
  <li *ngFor="let contact of contacts | async; trackBy: trackById;">
    <contact-card [contact]="contact"></contact-card>
  </li>
</ul>

Here we’ve added trackBy,then given it a value oftrackById. This is a function that we’ll add in the component class:

trackById(index, contact) {
  return contact.id;
}

All this function does is use a custom tracking solution for our collection. Instead of using object identity,we’re telling Angular here to use the uniqueid property that each contact object contains. Optionally,we can use the index (which is the index in the collection of each item,i.e. 0,1,2,3,4).

If your API returns unique data,then using that would be a preferable solution overindex - as the index may be subject to change if you reorder your collection. Using a unique identifier allows Angular to locate that DOM node associated with the object much faster,and it will reuse the component in the DOM should it need to be updated - instead of destroying it and rebuilding it.

You can check a live output of what we’ve covered here:

Capturing “index” and “count”

The ngFor directive doesn’t just stop at iteration,it also provides us a few other niceties. Let’s exploreindex and count,two public properties exposed to us on each ngFor iteration.

Let’s create another variable called i,which we’ll assign the value ofindex to. Angular exposes these values under-the-hood for us,and when we look at the next section with the<template> element,we can see how they are composed.

To log out the index,we can simply interpolate i:

<ul>
  <li *ngFor="let contact of contacts | async; let i = index;">
    Index: {{ i }}
    <contact-card [contact]="contact"></contact-card>
  </li>
</ul>

This will give us every index,starting from 0,for each item in our collection. Let’s also exposecount:

<ul>
  <li *ngFor="let contact of contacts | async; let i = index; let c = count;">
    Index: {{ i }}
    Count: {{ c }}
    <contact-card [contact]="contact"></contact-card>
  </li>
</ul>

The count will return a live collection length,equivalent ofcontacts.length. These can optionally be bound and passed into each<contact-card>,for example you may wish to log out the total length of your collection somewhere,and also pass theindex of the particular contact into a function@Output:

<ul>
  <li *ngFor="let contact of contacts | async; let i = index; let c = count;">
    <contact-card 
      [contact]="contact"
      [collectionLength]="c"
      (update)="onUpdate($event, i)">
    </contact-card>
  </li>
</ul>

You can check a live output of what we’ve covered here:

Accessing first,even

Four more properties exposed by ngFor (well,actually underneath it usesNgForRow,a class which generates each ngFor item internally). Let’s quickly look at the source code for this:

export class NgForRow {
  constructor(public $implicit: any, public index: number, public count: number) {}
  get first(): boolean { return this.index === 0; }
  get last(): boolean { return this.index === this.count - 1; }
  get even(): boolean { return this.index % 2 === 0; }
  get odd(): boolean { return !this.even; }
}

As I mentioned above,the NgForRow is what constructs ourngFor items,and you can see in the constructor we’ve already taken a look at index and count! The last things we need to look at are the getters,which we can explain from the source code above:

  • first: returns true for the first item in the collection,matches the index with zero

  • last: returns true for the last item in the collection,matches the index with the total count,minus one to shift the “count” down one to cater for zero-based indexes

  • even: returns true for even items (e.g. 2,4) in the collection,uses% modulus operator to calculate based off index

  • odd: returns true for odd items (e.g. 1,3),simply invertsthis.even result

Using this,we can add conditionally apply things such as styling,or hook into thelast property to kNow when the collection has finished rendering.

For this quick demo,we’ll use ngClass to add some styles to each<li> (note how we create more variables,just likeindex):

<ul>
  <li
    *ngFor="let contact of contacts | async; let o = odd; let e = even;"
    [ngClass]="{
      'odd-active': o,
      'even-active': e
    }">
    <contact-card 
      [contact]="contact"
      (update)="onUpdate($event, index)">
    </contact-card>
  </li>
</ul>

And some styles:

@Component({
  selector: 'app-root',
  styles: [`
    .odd-active { background: purple; color: #fff; }
    .even-active { background: red; color: #fff; }
  `],
  template: `
    <div class="app">
      <ul>
        <li
          *ngFor="let contact of contacts | async; let o = odd; let e = even;"
          [ngClass]="{ 'odd-active': o,'even-active': e }">
          <contact-card 
            [contact]="contact"
            (update)="onUpdate($event,index)">
          </contact-card>         </li>       </ul>     </div>   `
})

We won’t demonstrate first and last,as it’s fairly obvIoUs from the above how we can hook those up!

You can check a live output of what we’ve covered here:

<template> element

We mentioned earlier in this article that we’d look at understanding what the * meant in our templates. This also shares the same Syntax as *ngIf,which you’ve likely also seen before.

So in this next section,we’ll take a deeper dive on ngFor,* and the <template> element to explain in more detail what’s really happening here.

When using an asterisk (*) in our templates,we are informing Angular we’re using a structural directive,which is also sugar Syntax (a nice short hand) for using the<template> element.

<template> and Web Components

So,what is the <template> element? First,let’s take a step back. We’ll roll back to showing some Angular 1.x code here,perhaps you’ve done this before or done something similar in another framework/library:

<script id="myTemplate" type="text/ng-template">
  <div>
    My awesome template!
  </div> </script>

This overrides the type on the <script> tag,which prevents the JavaScript engine from parsing the contents of the<script> tag. This allows us,or a framework such as Angular 1.x,to fetch the contents of the script tag and use it as some form of HTML template.

Web Components introduced something similar to this idea,called the <template>:

<template id="myTemplate">
  <div>
    My awesome template!
  </div>
</template>

To grab our above template and instantiate it,we’d do this in plain JavaScript:

<div id="host"></div> <script>
  let template = document.querySelector('#myTemplate');
  let clone = document.importNode(template.content, true);
  let host = document.querySelector('#host');
  host.appendChild(clone);
</script> 

Note how we have id=host,which is our “host” Node for the template to be injected into.

You may have seen this term floating around Angular in a few ways,such as _nghost prefixes on Nodes (ng-host) or the host property in directives.

ngFor and <template>

So how does the above <template> explanation tell us more aboutngFor and the *? The asterisk is shorthand Syntax for using the <template> element.

Let’s start from the basic ngFor example:

<ul>
  <li *ngFor="let contact of contacts | async">
    <contact-card [contact]="contact"></contact-card>
  </li>
</ul>

And demonstrate the <template> equivalent:

<template ngFor let-contact [ngForOf]="contacts | async">
  <li>
    <contact-card [contact]="contact"></contact-card>
  </li>
</template>

That’s a lot different! What’s happening here?

When we use *ngFor,we’re telling Angular to essentially treat the element the* is bound to as a template.

Note: Angular’s <template> element is not a true Web Component,it merely mirrors the concepts behind it to allow you to use<template> as it’s intended in the spec. When we Ahead-of-Time compile,we will see no<template> elements. However,this doesn’t mean we can’t use things like Shadow DOM,as they are stillcompletely possible.

Let’s continue,and understand what ngFor,let-contact and ngForOf are doing above.

ngFor and embedded view templates

First thing’s first,ngFor is a directive! Let’s check some of the source code:

@Directive({selector: '[ngFor][ngForOf]'})
export class NgFor implements DoCheck, OnChanges {...}

Here,Angular is using attribute selectors as the value of selector to tell the @Directive decorator what attributes to look for (don’t confuse this with property binding such as<input [value]="foo">).

The directive uses [ngFor][ngForOf],which implies there are two attributes as a chained selector. So,how doesngFor work if we’re not using ngForOf?

Angular’s compiler transforms any <template> elements and directives used with a asterisk (*) into views that are separate from the root component view. This is so each view can be created multiple times.

During the compile phase,it will take let contact of contacts and capitalise theof,and create a custom key to create ngForOf.

In our case,Angular will construct a view that creates everything from the <li> tag inwards:

<!-- view -->
<li>
  <contact-card [contact]="contact"></contact-card>
</li>
<!-- /view -->

It also creates an invisible view container to contain all instances of the template,acting as a placeholder for content. The view container Angular has created essentially wraps the “views”,in our case this is just inside the<ul> tags. This houses all the templates that are created byngFor (one for each row).

A pseudo-output might look like this:

<ul>
<!-- view container -->
  <!-- view -->
  <li>
    <contact-card [contact]="contact"></contact-card>
  </li>
  <!-- /view -->
  <!-- view -->
  <li>
    <contact-card [contact]="contact"></contact-card>
  </li>
  <!-- /view -->
  <!-- view -->
  <li>
    <contact-card [contact]="contact"></contact-card>
  </li>
  <!-- /view -->
<!-- /view container -->
</ul>

ngFor creates an “embedded view” for each row,passing through the view it has created and the context of the row (the index and the row data). This embedded view is then inserted into the view container. When the data changes,it tracks the items to see if they have moved. If they’ve moved,instead of recreating the embedded views,it moves them around to be in the correct position,or destroys them if they no longer exist.

Context and passing variables

The next step is understanding how Angular passes the context to each <template>:

<template ngFor let-contact [ngForOf]="contacts | async">
  <li>
    <contact-card [contact]="contact"></contact-card>
  </li>
</template>

So Now we’ve understood ngFor and ngForOf,how does Angular associate let-contact with the individualcontact that we then property bind to?

Because let-contact has no value,it’s merely an attribute,this is where Angular provides an “implied” value,or$implicit as it’s called under-the-hood.

Whilst Angular is creating each ngFor item,it uses anNgForRow class alongside an EmbeddedViewRef,and passes these properties in dynamically. Here’s a small snippet from the source code:

changes.forEachIdentityChange((record: any) => {
  const viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
  viewRef.context.$implicit = record.item;
});

Alongside this section of code,we can also see how our aforementioned index and count properties are kept updated:

for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
  const viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
  viewRef.context.index = i;
  viewRef.context.count = ilen;
}

You can dig through the directive source code in more detail here.

This is how we can then access the index andcontext like this:

<ul>
  <template ngFor let-i="index" let-c="count" let-contact [ngForOf]="contacts | async">
    <li>
      <contact-card [contact]="contact"></contact-card>
    </li>
  </template>
</ul>

Note how we’re supplying let-i and let-c values which are exposed from the NgForRow instance,unlike let-contact.

You can check out the <template> version of the things we’ve covered here:

I'm Todd,I teach the world Angular through @UltimateAngular.Conference speaker and Developer Expert at Google.

Award-winning Angular 1.x and Angular 2 courses

Become an expert with my comprehensive Angular 1.5+ and Angular 2 courses

Angular2 ngFor, <template> 的用法的更多相关文章

  1. 浅析HTML5页面元素及属性

    这篇文章主要介绍了浅析HTML5页面元素及属性,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. Android Studio是否支持用于Android UI设计的AngularJS?

    我对AndroidStudio有疑问:AS在设计XML文件时是否支持AngularJS代码,例如:对于小动画或效果?

  3. android – 如何使用ClientID和ClientSecret在Phonegap中使用Angularjs登录Google OAuth2

    我正尝试使用Angularjs(使用IonicFramework)通过GoogleOAuth2从我的Phonegap应用程序登录.目前我正在使用http://phonegap-tips.com/articles/google-api-oauth-with-phonegaps-inappbrowser.html进行登录.但是当我使用Angular-UI-RouterforIonic时,它正在创建非常

  4. 在Android Chrome浏览器中的移动Web应用程序菜单中未检测到HTML unicode

    我在Android移动Chrome浏览器的网站菜单中有一个问题,无法显示unicode.但如果我在iPhone或其他Android浏览器中检查我的Web应用程序,它正在呈现或正常工作.我在这个结构中使用了这个图标但它不是在移动Chrome浏览器中显示如何解决它!解决方法另一种选择是使用≡相反:它看起来非常相似:≡而不是

  5. 利用require.js与angular搭建spa应用的方法实例

    这篇文章主要给大家介绍了关于利用require.js与angular搭建spa应用的方法实例,文中通过示例代码给大家介绍的非常详细,对大家的理解和学习具有一定的参考学习价值,需要的朋友们下面跟着小编来一起看看吧。

  6. 详解Angular动态组件

    本文主要介绍了Angular动态组件,对此感兴趣的同学,可以亲自实验一下。

  7. 详解如何使用webpack+es6开发angular1.x

    本篇文章主要介绍了详解如何使用webpack+es6开发angular1.x,具有一定的参考价值,有兴趣的可以了解一下

  8. angular2系列之路由转场动画的示例代码

    本篇文章主要介绍了angular2系列之路由转场动画的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. 一种angular的方法级的缓存注解(装饰器)

    本篇文章主要介绍了一种angular的方法级的缓存注解(装饰器),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. 动手写一个angular版本的Message组件的方法

    本篇文章主要介绍了动手写一个angular版本的Message组件的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部