反转角度2*ngFor

dan*_*iel 47 angular

<li *ngFor="#user of users ">
    {{ user.name }} is {{ user.age }} years old.
</li>
Run Code Online (Sandbox Code Playgroud)

是否可以反转ngFor项目是自下而上添加的?

小智 68

您可以.reverse()在阵列上使用JavaScript .不需要角度特定的解决方案.

<li *ngFor="#user of users.slice().reverse() ">
   {{ user.name }} is {{ user.age }} years old.
</li>
Run Code Online (Sandbox Code Playgroud)

在此处查看更多信息:https: //www.w3schools.com/jsref/jsref_reverse.asp

  • 使用Angular v4.1编译时存在一个错误,其中每次检查组件时都会恢复该数组.请按照建议使用管道. (5认同)
  • 管道也会发生错误,你必须使用.slice().reverse()来不影响原始数组.(在模板管道中) (4认同)
  • 这应该标记为已接受的答案! (3认同)
  • 为了避免这种情况,将在每个角度变化检测周期调用 `slice()` 和 `reverse()` 方法。建议使用管道。 (2认同)

Thi*_*ier 63

您需要实现一个利用JavaScript数组的反向方法的自定义管道:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'reverse' })

export class ReversePipe implements PipeTransform {
  transform(value) {
    return value.slice().reverse();
  }
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

<li *ngFor="let user of users | reverse">
    {{ user.name }} is {{ user.age }} years old.
</li>
Run Code Online (Sandbox Code Playgroud)

不要忘记在组件的pipes属性中添加管道.

  • 谢谢,不需要管道.我只是在我的for语句中使用了`.slice().reverse()` (38认同)
  • 我以为你只想反转显示和保持你的数据数据...这就是为什么我这样回答;-) (4认同)
  • `slice()`返回一个新数组,因此@ zoidbergi的注释确实将数据数组保持在组件中,并且仅影响视图. (4认同)
  • @daniel 方法调用应该在模板中避免,因为这对于更改检测来说效果不佳。换句话说,Angular 最终可能会以超出合理范围的方式调用“.slice().reverse()”。管道是正确的方法,尽管“无管道”也可行。 (4认同)

小智 18

这应该可以解决问题

<li *ngFor="user of users?.reverse()">
  {{ user.name }} is {{ user.age }} years old.
</li>
Run Code Online (Sandbox Code Playgroud)

  • 相反,您应该执行“ users.slice()。reverse()”(请参见上面的答案)。没有`slice`,在每次视图更新时,users数组都会反转,您将获得意想不到的行为! (2认同)

awe*_*bit 17

令人惊讶的是,这么多年过去了,这里仍然没有发布没有严重缺陷的解决方案。这就是为什么在我的回答中,我将解释所有当前解决方案的缺点,并针对这个问题提出两种我认为更好的新方法。

\n
\n

当前解决方案的缺点

\n

每个解决方案都提供现场演示。请查看解释下的链接。

\n

1.纯管带slice().reverse() (目前已接受)

\n

\xe2\x80\x94 by @Thierry Templier@rey_coder(因为ngx-pipes 使用了这个方法)

\n
@Pipe({ name: \'reverse\' })\nexport class ReversePipe implements PipeTransform {\n  transform(value) {\n    return value.slice().reverse();\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

默认情况下,管道是纯管道,因此 Angular 希望它们在给定相同输入的情况下产生相同的输出。这就是为什么transform()当您对数组进行更改时,不会再次调用该方法,除非您每次想要更新旧数组时都创建一个新数组,例如通过编写

\n
this.users = this.users.concat([{ name, age }]);\n
Run Code Online (Sandbox Code Playgroud)\n

代替

\n
this.users.push({ name, age });\n
Run Code Online (Sandbox Code Playgroud)\n

但仅仅因为你的管道需要就这样做是一个坏主意。

\n

因此,只有当您不打算修改数组时,此解决方案才有效。

\n

在现场演示中,您将看到只有“重置”按钮起作用,因为单击时它会创建一个新的(空)数组,而“添加”按钮只会改变现有数组。

\n

现场演示

\n

2.slice().reverse()不带管道

\n

\xe2\x80\x94 作者:@Andr\xc3\xa9 G\xc3\xb3is@Trilok Singh@WapShivam

\n
<li *ngFor="let user of users.slice().reverse()">\n   {{ user.name }} is {{ user.age }} years old.\n</li>\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,我们使用相同的数组方法,但没有管道,这意味着 Angular 将在每个更改检测周期调用它们。对数组的更改现在将反映在 UI 中,但这里的问题是slice()reverse()可能会被调用得太频繁,因为数组更改并不是唯一可以导致更改检测运行的事情。

\n

请注意,如果我们修改先前解决方案的代码以使管道不纯,我们会得到相同的行为。我们可以通过设置pure: false传递给装饰器的对象来做到这一点Pipe。我在现场演示中这样做是为了能够在任何时候被调用时在控制台中记录一些slice()内容reverse()。我还添加了一个按钮,可以让您在不更改数组的情况下触发更改检测。单击它后,您将在控制台中看到slice()reverse()被调用,尽管数组尚未修改,这是我们在理想解决方案中不希望出现的情况。尽管如此,该解决方案是当前所有解决方案中最好的。

\n

现场演示

\n

3.尝试通过缓存反转数组来提高性能

\n

\xe2\x80\x94 by @G\xc3\xbcnter Z\xc3\xb6chbauer

\n
@Pipe({ name: \'reverse\', pure: false })\nexport class ReversePipe implements PipeTransform {\n  constructor(private differs: IterableDiffers) {\n    this.differ = this.differs.find([]).create();\n  }\n\n  transform(value) {\n    if (this.differ.diff(value)) {\n      this.cached = value.slice().reverse();\n    }\n    return this.cached;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是解决我在上一节中描述的问题的巧妙尝试。在这里,仅当数组的内容实际发生更改时才调用 和slice()reverse()如果没有改变,则返回上次操作的缓存结果。

\n

IterableDiffer为了检查数组更改,使用Angular 对象。Angular 在更改检测期间也会在内部使用此类对象。同样,您可以在现场演示中检查控制台,看看它是否确实有效。

\n

但是,我不认为性能有任何改进。相反,我认为这个解决方案使情况变得更糟。原因是需要不同的线性时间才能发现数组没有改变,这与调用slice()and一样糟糕reverse()。如果它发生了变化,在最坏的情况下时间复杂度仍然是线性的,因为在某些情况下,在 diff 检测到变化之前必须遍历整个数组。例如,当数组的最后一个元素被修改时,就会发生这种情况。在这种情况下,整个数组必须遍历两次:调用 diff 时和调用slice()and时reverse(),这太糟糕了。

\n

现场演示

\n

4.仅使用reverse()

\n

\xe2\x80\x94 作者:@Prank100@Dan

\n
<li *ngFor="let user of users.reverse()">\n   {{ user.name }} is {{ user.age }} years old.\n</li>\n
Run Code Online (Sandbox Code Playgroud)\n

这个解决方案是绝对不行的。

\n

原因是reverse()改变数组而不是创建一个新数组,这很可能是不希望的,因为您可能需要在不同的位置使用原始数组。如果不这样做,您应该在数组到达应用程序后立即反转该数组,而不必再担心它。

\n

还有一个更严重的问题,特别危险,因为你在开发模式下看不到它。在生产模式下,您的数组将在每个更改检测周期内反转,这意味着类似的数组[1,2,3]将不断在[1,2,3]和 之间切换[3,2,1]。你绝对不希望这样。

\n

您在开发模式下看不到它,因为在该模式下,Angular在每次运行更改检测后都会执行额外的检查,这会导致反转数组再次反转,使其恢复到原来的顺序。第二次检查的副作用不会反映在 UI 中,这就是为什么一切看起来都工作正常,但实际上并非如此。

\n

现场演示 (生产模式)

\n
\n

替代解决方案

\n

1.仅使用数组索引

\n
// In the component:\nuserIdentity = i => this.users[this.users.length - 1 - i];\n
Run Code Online (Sandbox Code Playgroud)\n
<li *ngFor="let _ of users; index as i; trackBy:userIdentity">\n  {{ users[users.length - 1 - i].name }} is {{ users[users.length - 1 - i].age }} years old.\n</li>\n
Run Code Online (Sandbox Code Playgroud)\n

我们不获取用户,而是仅获取索引i并使用它来获取i数组的最后一个元素。(从技术上讲,我们也获取用户,但我们使用 _ 变量名称来表明我们不会使用该变量。这是一个非常常见的约定。)

\n

必须写users[users.length - 1 - i]多次很烦人。我们可以使用这个技巧来简化代码:

\n
<li *ngFor="let _ of users; index as i; trackBy:userIdentity">\n  <ng-container *ngIf="users[users.length - 1 - i] as user">\n    {{ user.name }} is {{ user.age }} years old.\n  </ng-container>\n</li>\n
Run Code Online (Sandbox Code Playgroud)\n

这样,我们创建了一个可以在<ng-container>. 请注意,只有当您的数组元素不是假值时它才能正常工作,因为我们在该变量中保存的内容实际上是指令的条件*ngIf

\n

另请注意,我们必须使用自定义TrackByFunction,如果您不想事后花费大量时间进行调试,那么这是绝对必要的,即使我们的简单示例在没有它的情况下似乎也可以正常工作。

\n

现场演示

\n

2.返回可迭代对象的纯管道

\n
@Pipe({ name: \'reverseIterable\' })\nexport class ReverseIterablePipe implements PipeTransform {\n  transform<T>(value: T[]): Iterable<T> {\n    return {\n      *[Symbol.iterator]() {\n        for (let i = value.length - 1; i >= 0; i--) {\n          yield value[i];\n        }\n      }\n    };\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

*ngFor循环在内部使用可迭代对象,因此我们只需使用管道构造一个满足我们需求的循环即可。[Symbol.iterator]我们对象的方法是一个生成器函数,它返回迭代器,让我们以相反的顺序遍历数组。如果您是第一次听说迭代,或者您不熟悉星号语法和关键字,请阅读本指南。yield

\n

这种方法的美妙之处在于,只需为我们的数组创建一个可迭代对象就足够了。如果我们对数组进行更改,应用程序仍然可以正常工作,因为生成器函数每次被调用时都会返回一个新的迭代器。这意味着我们只需要在创建新数组时创建一个新的迭代器,这就是为什么这里一个纯管道就足够了,这使得这个解决方案比使用 和 的解决方案slice()更好reverse()

\n

我认为这是比前一个更优雅的解决方案,因为我们不必在循环中使用数组索引*ngFor。事实上,除了添加管道之外,我们不需要更改组件中的任何内容,因为整个功能都包含在其中。

\n
<li *ngFor="let user of users | reverseIterable">\n  {{ user.name }} is {{ user.age }} years old.\n</li>\n
Run Code Online (Sandbox Code Playgroud)\n

另外,不要忘记在模块中导入并声明管道。

\n

请注意,这次我对管道进行了不同的命名,以表明它仅在可迭代就足够的上下文中工作。如果其他管道期望数组作为输入,则不能将其应用到其输出,并且不能{{ users | reverseIterable }}像使用普通数组一样通过在模板中写入来使用它来打印反转数组,因为可迭代对象没有实现该toString()方法(尽管没有任何停止)如果它对您的用例有意义,您就不要实现它)。slice()与带有and的管道相比,这是该管道的缺点reverse(),但如果您只想在*ngFor循环中使用它,那么它肯定会更好。

\n

现场演示

\n

使用有状态纯管道进行可能的优化

\n

\xe2\x80\x9c 有状态纯管道\xe2\x80\x9d 听起来可能有点矛盾,但如果我们使用相对较新的编译和渲染引擎Ivy ,它们实际上是 Angular允许我们创建的东西。

\n

在引入 Ivy 之前,Angular 只会创建一个纯管道实例,即使我们在模板中多次使用它也是如此。这意味着该transform()方法将始终在同一个对象上调用。因此,我们不能依赖该对象中存储的任何状态,因为它在模板中管道的所有出现之间共享。

\n

然而,ivy 采用了不同的方法,为每次出现的管道创建一个单独的实例,这意味着我们现在可以使纯管道有状态。我们为什么要这样做?好吧,我们当前解决方案的问题在于,虽然我们在修改数组时不必创建一个新的可迭代对象,但在创建新数组时仍然必须这样做。我们可以通过在管道实例的属性中存储数组并使可迭代依赖于该属性而不是传递给方法的数组来解决这个问题transform()。然后,我们只需在transform()调用时将新数组分配给现有属性,然后返回现有的可迭代对象即可。

\n
@Pipe({ name: \'reverseIterable\' })\nexport class ReverseIterablePipe implements PipeTransform {\n  private array: any[] = [];\n  private reverseIterable: Iterable<any>;\n\n  constructor() {\n    this.reverseIterable = {\n      [Symbol.iterator]: function*(this: ReverseIterablePipe) {\n        for (let i = this.array.length - 1; i >= 0; i--) {\n          yield this.array[i];\n        }\n      }.bind(this)\n    };\n  }\n\n  transform<T>(value: T[]): Iterable<T> {\n    this.array = value;\n    return this.reverseIterable;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,我们必须绑定生成器函数,this因为this如果不这样做,在其他上下文中可能会有不同的含义。我们通常会使用箭头函数来解决这个问题,但是生成器函数没有这样的语法,因此我们必须显式绑定它们。

\n

从 Angular 版本 9 开始,Ivy 默认启用,但我在现场演示中禁用了它,以便向您展示为什么我们无法在没有它的情况下使纯管道有状态。为了使应用程序正常工作,您必须在文件中设置angularCompilerOptions.enableIvy为。truetsconfig.json

\n

现场演示

\n


Dan*_*Dan 12

所选答案有两个问题:

首先,管道不会注意到源数组修改,除非您添加pure: false到管道的属性.

其次,管道不支持双向绑定,因为数组的副本是相反的,而不是数组本身.

最终代码如下:

import {Pipe} from 'angular2/core';

@Pipe({
  name: 'reverse',
  pure: false
})
export class ReversePipe {
  transform (values) {
    if (values) {
      return values.reverse();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

Plunker

http://plnkr.co/edit/8aYdcv9QZJk6ZB8LZePC?p=preview


Gün*_*uer 7

更新

@Pipe({
  name: 'reverse',
  pure: false
})
export class ReversePipe {
  constructor(private differs:IterableDiffers) {
    this.differ = this._differs.find([]).create(null);
  }

  transform(value) {
    const changes = this.differ.diff(value);
    if(changes) {
      this.cached = value.slice().reverse();
    }
    return this.cached;    
  }
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,Angular在向数据添加项目或从数组中删除项目时不会调用管道,因为默认情况下Angular更改检测仅比较数组实例更改.

为了在每次调用更改检测时调用管道,我使管道不纯.不经常会调用不纯的管道,因此高效工作非常重要.创建一个数组的副本(甚至可能是一个大型数组),然后颠倒它的顺序是非常昂贵的.

因此,不同之处仅在于,如果识别出某些更改,则仅执行实际工作,否则返回先前调用的缓存结果.

原版的

您可以创建一个自定义管道,以相反的顺序返回该数组,或者只是以相反的顺序提供数据.

也可以看看


小智 7

尝试这个:

<ul>
  <li *ngFor="let item of items.slice().reverse()">{{item}}</li>
</ul>
Run Code Online (Sandbox Code Playgroud)


rey*_*der 6

可以使用ngx-pipes

npm install ngx-pipes --save

模块

import {NgPipesModule} from 'ngx-pipes';

@NgModule({
 // ...
 imports: [
   // ...
   NgPipesModule
 ]
})
Run Code Online (Sandbox Code Playgroud)

成分

@Component({
  // ..
  providers: [ReversePipe]
})
export class AppComponent {
  constructor(private reversePipe: ReversePipe) {
  }
}
Run Code Online (Sandbox Code Playgroud)

然后在模板中使用 <div *ngFor="let user of users | reverse">