angular有"计算属性"功能如vue.js?

时间:2017-04-30 19:47:49

标签: javascript angular vue.js

我首先学习了Vue.js,现在在Angular 4中有一个项目,所以我刚刚学习了Angular。我发现除了" Computed Property"之外,一切都与Vue没有什么不同。在Vue中,我可以创建一个计算属性来侦听其他属性的更改并自动运行计算。

例如(在Vue 2中):

computed: {
    name(){
        return this.firstname + ' ' + this.lastname;
    }
}

name属性只会在firstname或lastname之一更改时重新计算。如何在Angular 2或4中处理这个问题?

7 个答案:

答案 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>

每当用户更改ab的输入值时,productproductFunc都会记录到控制台。如果用户更改c,则仅调用productFunc

回到Angular,mobxjs确实有助于解决这个问题:

  1. 使用npm install --save mobx-angular mobx
  2. 进行安装
  3. 对绑定属性使用observablecomputed属性
  4. 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>
    

    如果更改了ab,则会多次调用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)

Vue中的

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

  1. 至少使用版本 3 将 Vue 添加到您的项目
  2. 像这样添加到你的 component.ts:
computed