我首先学习了Vue.js,现在在Angular 4中有一个项目,所以我刚刚学习了Angular。我发现除了" Computed Property"之外,一切都与Vue没有什么不同。在Vue中,我可以创建一个计算属性来侦听其他属性的更改并自动运行计算。
例如(在Vue 2中):
computed: {
name(){
return this.firstname + ' ' + this.lastname;
}
}
name属性只会在firstname或lastname之一更改时重新计算。如何在Angular 2或4中处理这个问题?
答案 0 :(得分:26)
虽然这已经得到了解答,但我认为这不是一个很好的答案,用户不应该使用getter作为angular中的计算属性。为什么你会问? getter只是函数的糖语法,它将被编译为普通函数,这意味着它将在每次更改检测检查时执行。这对于表现来说很糟糕,因为任何改变都会重新计算数百次。
看一下这个例子:https://plnkr.co/edit/TQMQFb?p=preview
@Component({
selector: 'cities-page',
template: `
<label>Angular computed properties are bad</label>
<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
placeholder="Select city"
[(ngModel)]="selectedCityId">
</ng-select>
<p *ngIf="hasSelectedCity">
Selected city ID: {{selectedCityId}}
</p>
<p><b>hasSelectedCity</b> is recomputed <b [ngStyle]="{'font-size': calls + 'px'}">{{calls}}</b> times</p>
`
})
export class CitiesPageComponent {
cities: NgOption[] = [
{id: 1, name: 'Vilnius'},
{id: 2, name: 'Kaunas'},
{id: 3, name: 'Pabradė'}
];
selectedCityId: any;
calls = 0;
get hasSelectedCity() {
console.log('hasSelectedCity is called', this.calls);
this.calls++;
return !!this.selectedCityId;
}
}
如果你真的想拥有计算属性,可以使用像mobx
这样的状态容器class TodoList {
@observable todos = [];
@computed get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length;
}
}
mobx拥有@computed装饰器,因此只有在需要时才会缓存并重新计算getter属性
答案 1 :(得分:11)
我会尽力改进Andzej Maciusovic
希望得到规范的答案。实际上,VueJS有一个名为computed属性的功能,可以使用示例快速显示:
<template>
<div>
<p>A = <input type="number" v-model="a"/></p>
<p>B = <input type="number" v-model="b"/></p>
<p>C = <input type="number" v-model="c"/></p>
<p>Computed property result: {{ product }}</p>
<p>Function result: {{ productFunc() }}</p>
</div>
</template>
<script>
export default {
data () {
return {
a: 2,
b: 3,
c: 4
}
},
computed: {
product: function() {
console.log("Product called!");
return this.a * this.b;
}
},
methods: {
productFunc: function() {
console.log("ProductFunc called!");
return this.a * this.b;
}
}
}
</script>
每当用户更改a
或b
的输入值时,product
和productFunc
都会记录到控制台。如果用户更改c
,则仅调用productFunc
。
回到Angular,mobxjs确实有助于解决这个问题:
npm install --save mobx-angular mobx
observable
和computed
属性TS文件
import { observable, computed } from 'mobx-angular';
@Component({
selector: 'home',
templateUrl: './home.component.html',
animations: [slideInDownAnimation]
})
export class HomeComponent extends GenericAnimationContainer {
@observable a: number = 2;
@observable b: number = 3;
@observable c: number = 4;
getAB = () => {
console.log("getAB called");
return this.a * this.b;
}
@computed get AB() {
console.log("AB called");
return this.a * this.b;
}
}
<强>标记强>
<div *mobxAutorun>
<p>A = <input type="number" [(ngModel)]="a" /> </p>
<p>B = <input type="number" [(ngModel)]="b" /> </p>
<p>C = <input type="number" [(ngModel)]="c" /> </p>
<p> A * B = {{ getAB() }}</p>
<p> A * B (get) = {{ AB }}</p>
</div>
如果更改了a
或b
,则会多次调用AB
一次并getAB
。如果更改c
,则仅调用getAB
。因此,即使必须执行计算,此解决方案也更有效。
答案 2 :(得分:8)
在TS文件中:
export class MyComponent {
get name() {
return this.firstname + ' ' + this.lastname;
}
}
之后在html:
<div>{{name}}</div>
这是一个例子:
@Component({
selector: 'my-app',
template: `{{name}}`,
})
export class App {
i = 0;
firstN;
secondN;
constructor() {
setInterval(()=> {
this.firstN = this.i++;
this.secondN = this.i++;
}, 2000);
}
get name() {
return this.firstN + ' ' + this.secondN;
}
}
答案 3 :(得分:3)
在某些情况下,使用Pure Pipe可能是一个合理的选择,显然会带来一些限制,但它至少可以避免在任何事件上执行该功能的成本。
@Pipe({ name: 'join' })
export class JoinPipe implements PipeTransform {
transform(separator: string, ...strings: string[]) {
return strings.join(separator);
}
}
在您的模板而不是全名属性中,您可以改为使用' ' | join:firstname:lastname
。很遗憾,计算属性仍然不存在角度。
答案 4 :(得分:0)
computed
属性有2个巨大的好处:反应性和备忘录化。
在Vue中,我可以创建一个可计算的属性,以侦听其他属性的变化并自动运行计算。
您在这里专门询问有关Angular中的反应系统。似乎人们已经忘记了什么是Angular基石:Observables。
const {
BehaviorSubject,
operators: {
withLatestFrom,
map
}
} = rxjs;
const firstName$ = new BehaviorSubject('Larry');
const lastName$ = new BehaviorSubject('Wachowski');
const fullName$ = firstName$.pipe(
withLatestFrom(lastName$),
map(([firstName, lastName]) => `Fullname: ${firstName} ${lastName}`)
);
const subscription = fullName$.subscribe((fullName) => console.log(fullName))
setTimeout(() => {
firstName$.next('Lana');
subscription.unsubscribe();
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.js"></script>
我的代码不能解决备注部分,只能解决反应性。
人们给出了一些有趣的答案。尽管mobX computed
属性可能是(我从未使用过mobX)属性,但更接近Vue中的computed
,但这也意味着您依赖的是您可能不希望使用的另一个库。对于您所解释的特定用例,管道替代在这里是最有趣的,因为它既解决了反应性又影响了记录,但并非在所有情况下都有效。
答案 5 :(得分:0)
我想再添加一个选项(TypeScript 4),因为上述方法不能100%满足所有需求。它没有反应性,但仍然足够好。想法是显式声明一个检测更改的函数和一个计算属性值的函数。
export class ComputedProperty<TInputs extends any[], TResult> {
private readonly _changes: (previous: TInputs) => TInputs;
private readonly _result: (current: TInputs) => TResult;
private _cache: TResult;
private _inputs: TInputs;
constructor(changes: (previous: TInputs) => TInputs, result: (current: TInputs) => TResult) {
this._changes = changes;
this._result = result;
}
public get value(): TResult {
const inputs = this._changes(this._inputs);
if (inputs !== this._inputs) {
this._inputs = inputs;
this._cache = this._result(this._inputs);
}
return this._cache;
}
}
声明:
// readonly property
this.computed = new ComputedProperty<[number], number>(
(previous) => {
return previous?.[0] === this.otherNumber ? previous : [this.otherNumber];
},
(current) => {
return current[0] + 1;
}
);
使用:
<label>Angular computed property: {{computed.value}}</label>
答案 6 :(得分:0)
您可以从 Vue 组合 API 中导入两个函数,并且效果很好。这可能是个坏主意,但很有趣。只需从 Vue 导入 def menu_is_boring(meals):
counter=0
for i in range(0, len(meals) - 1):
if meals[i]==meals[i+1]:
counter=counter+1
if counter!=0:
return True
else:
return False
和 ref
,您就可以在 Angular 中计算属性。
我有一个 PR 示例,我将 Vue 添加到 Angular 项目中:https://github.com/kevin-castify/vue-in-angular/pull/1/files
computed