<li *ngFor="#user of users ">
{{ user.name }} is {{ user.age }} years old.
</li>
是否有可能颠倒 ngFor 的顺序,以使项目从底部添加?
<li *ngFor="#user of users ">
{{ user.name }} is {{ user.age }} years old.
</li>
是否有可能颠倒 ngFor 的顺序,以使项目从底部添加?
.reverse()
方法。不需要使用 Angular 特定的解决方案。<li *ngFor="#user of users.slice().reverse() ">
{{ user.name }} is {{ user.age }} years old.
</li>
你需要实现一个自定义管道,利用 JavaScript 数组的 reverse 方法:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'reverse' })
export class ReversePipe implements PipeTransform {
transform(value) {
return value.slice().reverse();
}
}
您可以这样使用:
<li *ngFor="let user of users | reverse">
{{ user.name }} is {{ user.age }} years old.
</li>
别忘了在你的组件的pipes属性中添加管道。
.slice().reverse()
。 - danielslice()
方法返回一个新的数组,因此@zoidbergi的评论确保了在组件中数据数组保持不变,只影响视图。 - Mark Rajcok.slice().reverse()
的次数远超合理范围。在这里,管道是正确的选择,即使不使用管道也可以正常工作。 - Avius这样应该就可以解决问题了
<li *ngFor="user of users?.reverse()">
{{ user.name }} is {{ user.age }} years old.
</li>
users.slice().reverse()
(参见上面的答案)。如果没有 slice
,则用户数组会在每次视图更新时反转,并且会出现意外行为! - bersling令人惊讶的是,经过这么多年后,仍然没有一个解决方案被发布在这里,它没有严重的缺陷。这就是为什么在我的回答中,我将解释所有当前解决方案存在的缺点,并提出两种我认为更好的解决此问题的新方法。
每个解决方案都有现场演示。请查看说明下的链接。
slice().reverse()
(目前被接受)——由@Thierry Templier和@rey_coder提出(自ngx-pipes
使用此方法以来)
@Pipe({ name: 'reverse' })
export class ReversePipe implements PipeTransform {
transform(value) {
return value.slice().reverse();
}
}
管道默认为纯净, 因此Angular希望它们在给定相同输入时产生相同的输出。这就是为什么transform()
方法不会在您更改数组时再次调用,除非您每次想要更新旧数组时都创建一个新数组,例如通过编写
this.users = this.users.concat([{ name, age }]);
替代
this.users.push({ name, age });
但仅因为您的管道需要它而这样做是一个坏主意。
因此,如果您不打算修改数组,则此解决方案仅适用。
在实时演示中,您会发现只有重置按钮起作用,因为单击它时会创建一个新的(空)数组,而添加按钮仅会改变现有数组。
slice().reverse()
— 由@André Góis, @Trilok Singh 和 @WapShivam提供
<li *ngFor="let user of users.slice().reverse()">
{{ user.name }} is {{ user.age }} years old.
</li>
— 由@Günter Zöchbauer提供
@Pipe({ name: 'reverse', pure: false })
export class ReversePipe implements PipeTransform {
constructor(private differs: IterableDiffers) {
this.differ = this.differs.find([]).create();
}
transform(value) {
if (this.differ.diff(value)) {
this.cached = value.slice().reverse();
}
return this.cached;
}
}
slice()
和reverse()
。如果它们没有改变,就会返回上次操作的缓存结果。IterableDiffer
对象。这样的对象在Angular内部的变化检测过程中也被使用。同样,您可以在实时演示中检查控制台以查看它实际上是如何工作的。slice()
和reverse()
一样糟糕。如果它已经改变,最坏情况下的时间复杂度仍然是线性的,因为在某些情况下,在检测到更改之前必须遍历整个数组。例如,当修改了数组的最后一个元素时就会发生这种情况。在这种情况下,整个数组将不得不被遍历两次:一次是调用区别函数,另一次是调用slice()
和reverse()
,这太糟糕了。
reverse()
<li *ngFor="let user of users.reverse()">
{{ user.name }} is {{ user.age }} years old.
</li>
reverse()
会改变数组本身,而不是创建一个新的数组,这可能是不期望的,因为您可能需要在其他地方使用原始数组。如果您不需要,请在应用程序中收到数组后立即反转它,就不必再担心它了。[1,2,3]
这样的数组将不断在[1,2,3]
和[3,2,1]
之间切换。你肯定不想要那样。现场演示 (生产模式)
// In the component:
userIdentity = i => this.users[this.users.length - 1 - i];
<li *ngFor="let _ of users; index as i; trackBy:userIdentity">
{{ users[users.length - 1 - i].name }} is {{ users[users.length - 1 - i].age }} years old.
</li>
我们不是获取用户,而是只获取索引i
并使用它来获取数组的第i个最后一个元素。(从技术上讲,我们也会获取用户,但我们使用变量名_
表示我们不打算使用该变量。这是一个非常常见的约定。)
多次编写users[users.length - 1 - i]
很麻烦。我们可以使用this trick来简化代码:
<li *ngFor="let _ of users; index as i; trackBy:userIdentity">
<ng-container *ngIf="users[users.length - 1 - i] as user">
{{ user.name }} is {{ user.age }} years old.
</ng-container>
</li>
<ng-container>
中使用。请注意,它只能正常工作,如果您的数组元素不是假值,因为我们在该变量中保存的实际上是*ngIf
指令的条件。TrackByFunction
,如果您不想花费大量时间进行调试,那么绝对是必需的,即使我们的简单示例似乎可以正常工作而无需它。
演示
@Pipe({ name: 'reverseIterable' })
export class ReverseIterablePipe implements PipeTransform {
transform<T>(value: T[]): Iterable<T> {
return {
*[Symbol.iterator]() {
for (let i = value.length - 1; i >= 0; i--) {
yield value[i];
}
}
};
}
}
*ngFor
循环内部使用 可迭代对象,所以我们只需使用管道构造一个满足我们需求的对象。我们对象的 [Symbol.iterator]
方法是一个 生成器函数,它返回迭代器,让我们能够以相反的顺序遍历数组。如果您第一次听到可迭代对象或者对星号语法和 yield
关键字不熟悉,请阅读 本指南。
slice()
和 reverse()
的解决方案中,这使得该解决方案更好。*ngFor
循环中使用数组索引。实际上,除了添加管道之外,我们不需要更改组件中的任何内容,因为整个功能都包含在其中。<li *ngFor="let user of users | reverseIterable">
{{ user.name }} is {{ user.age }} years old.
</li>
{{ users | reverseIterable }}
来打印反转的数组,因为可迭代对象没有实现toString()方法(尽管如果在您的用例中有意义,则可以实现它)。这是与具有slice()和reverse()的管道相比的劣势,但如果您想要在*ngFor循环中使用它,那么它绝对更好。可能可以通过一个有状态的纯管道进行优化。
在线演示
然而,Ivy采用了一种不同的方法,为每个出现的实例创建一个单独的管道实例,这意味着我们现在可以使纯管道具有状态。为什么我们要这样做呢?嗯,我们当前解决方案的问题是,虽然我们在修改数组时不必创建新的可迭代对象,但在创建新的可迭代对象时仍然必须这样做。我们可以通过将数组存储在管道实例的属性中,并使可迭代对象依赖于该属性而不是传递给transform()
方法的数组来解决这个问题。因此,每当调用transform()
时,我们只需要将新数组分配给现有属性,然后返回现有的可迭代对象即可。
@Pipe({ name: 'reverseIterable' })
export class ReverseIterablePipe implements PipeTransform {
private array: any[] = [];
private reverseIterable: Iterable<any>;
constructor() {
this.reverseIterable = {
[Symbol.iterator]: function*(this: ReverseIterablePipe) {
for (let i = this.array.length - 1; i >= 0; i--) {
yield this.array[i];
}
}.bind(this)
};
}
transform<T>(value: T[]): Iterable<T> {
this.array = value;
return this.reverseIterable;
}
}
this
,否则在其他上下文中this
可能具有不同的含义。通常我们可以使用箭头函数来解决这个问题,但是对于生成器函数并没有这样的语法,所以我们必须显式地绑定它们。tsconfig.json
文件中将angularCompilerOptions.enableIvy
设置为true
。
实时演示
所选答案存在两个问题:
首先,除非在管道的属性中添加pure: false
,否则管道将无法注意到源数组的修改。
其次,管道不支持双向绑定,因为它复制的是数组的副本而非数组本身。
最终代码如下:
import {Pipe} from 'angular2/core';
@Pipe({
name: 'reverse',
pure: false
})
export class ReversePipe {
transform (values) {
if (values) {
return values.reverse();
}
}
}
更新
@Pipe({
name: 'reverse',
pure: false
})
export class ReversePipe implements PipeTransform {
constructor(private differs: IterableDiffers) {
this.differ = this.differs.find([]).create();
}
transform(value) {
const changes = this.differ.diff(value);
if (changes) {
this.cached = value.slice().reverse();
}
return this.cached;
}
}
transform()
方法,因此当向数组添加或删除项目时,它不会被调用,这意味着对数组的更改不会反映在UI中。value
未定义。 - Ashok KoyiIterableDiffer
现在是泛型的了。需要类型参数。 - Ashok Koyislice()
一样的时间,这需要线性时间。如果数组已更改,则在最坏情况下必须遍历整个数组,然后才能检测到更改。最坏情况可能发生在数组的最后一个元素被更改时。在这种情况下,整个数组将不得不被遍历两次:一次在调用差异函数时,一次在调用slice()
时,这非常糟糕。 - aweebitimport {NgPipesModule} from 'ngx-pipes';
@NgModule({
// ...
imports: [
// ...
NgPipesModule
]
})
@Component({
// ..
providers: [ReversePipe]
})
export class AppComponent {
constructor(private reversePipe: ReversePipe) {
}
}
<div *ngFor="let user of users | reverse">
<ul>
<li *ngFor="let item of items.slice().reverse()">{{item}}</li>
</ul>
您可以使用JavaScript函数来实现。
<li *ngFor="user of users?.slice()?.reverse()">
{{ user.name }} is {{ user.age }} years old.
</li>
.slice().reverse()
方法解决方案简单而有效。然而,在使用迭代索引时,您应该注意。这是因为.slice()
方法会为视图返回一个新数组,以便我们的初始数组不会改变。问题在于反转后的数组也具有反转的索引。因此,如果要将其用作变量,则还应反转索引。例如:<ul>
<li *ngFor="let item of items.slice().reverse(); index as i" (click)="myFunction(items.length - 1 - i)">{{item}}</li>
</ul>
items.length - 1 - i
代替了i
,这样点击的项目就对应于我们最初数组中的相同项目。
slice()
和reverse()
方法,建议使用管道。 - Ploppy