ChatGPT解决这个技术问题 Extra ChatGPT

does angular have the "computed property" feature like in vue.js?

I learnt Vue.js first, and now have a project in Angular 4 so I just learnt Angular. I find everything is not that different from Vue except the "Computed Property". In Vue, I can create a computed property that listens to changes of other properties and run calculations automatically.

For example(in Vue 2):

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

The name property will only recalculate when one of firstname or lastname change. How to handle this in Angular 2 or 4 ?

@Pano angularjs tag is for Angular 1 questions. Angular uses fundamentally different approach, it detects changes with zones. So basically a counterpart in Angular is basic get property accessor, get name() { return ... }.
Thanks @estus, how about set a property based on another property that might change over time?
Then it should be a setter then. A component property updated this way will be updated properly in view . You will find yourself cornered when sticking to component props, Angular makes heavy use of RxJS observables and subjects to provide fiexible patterns for data observation and manupulation.
@estus What is the common practice for this? Say when a component load, there will be an http request, after which there are some calculations before actually displaying on the page.
A common practice is to separate concerns, make http request in a service and return calculated observable from there. An observable can be unwrapped in a component with subscribe or bound to view directly with | async pipe. As you can see, there are no computed properties at all, data flow is performed via observables. If you have a particular case in mind, feel free to ask a question.

A
Andzej Maciusovic

While this is already answered but I think this is not very good answer and users should not use getters as computed properties in angular. Why you may ask? getter is just sugar syntax for function and it will be compiled to plain function, this means that it will be executed on every change detection check. This is terrible for performance because property is recomputed hundred of times on any change.

Take a look at this example: 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;
    }
}

If you really want to have computed properties you can use state container like mobx

class TodoList {
    @observable todos = [];
    @computed get unfinishedTodoCount() {
        return this.todos.filter(todo => !todo.finished).length;
    }
}

mobx has @computed decorator so getter property will be cached and recalculated only when needed


thanks for pointing out the performance hit of using a get property for this. really useful information.
This should be the accepted answer, since it is the only one pointing that the original answer does not emulate computed properties from VueJS. Thanks.
A
Alexei - check Codidact

I will try to improve upon Andzej Maciusovic's hoping to obtain a canonical answer. Indeed VueJS has a feature called computed property that can be quickly shown using an example:

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

Whenever the user changes input value for a or b, both product and productFunc are logging to console. If user changes c, only productFunc is called.

Coming back to Angular, mobxjs really helps with this issue:

Install it using npm install --save mobx-angular mobx Use observable and computed attributes for bound properties

TS file

    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;
       }
    }

Markup

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

If a or b is changed, AB is called once and getAB several times. If c is changed, only getAB is called. So, this solution is more efficient even when computation must be performed.


k
keyneom

In some cases using a Pure Pipe might be a reasonable alternative, obviously that comes with some restrictions but it at least avoids the costliness of executing the function on any event.

@Pipe({ name: 'join' })
export class JoinPipe implements PipeTransform {
  transform(separator: string, ...strings: string[]) {
    return strings.join(separator);
  }
}

In your template instead of a full name property you might be able to instead just use ' ' | join:firstname:lastname. Pretty sad that computed properties still don't exist for angular.


R
R3ctor

computed properties in Vue has 2 massive benefits: reactivity and memoization.

In Vue, I can create a computed property that listens to changes of other properties and run calculations automatically.

You are asking here specifically about reactivity system in Angular. It seems like people have forgotten what is Angular cornerstone: 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);

My code doesn't solve the memoization part, it tackles only the reactivity.

People gave some interesting answers. While mobX computed property might be (I've never used mobX) the one closer to what computed in Vue is, it also means that you are relying on another library you might not be interested to use. The pipe alternative is the most interesting here for the specific use case you explained as it addresses both reactivity and memoization but it is not valid for all cases.


J
Julia Passynkova

yes, you can.

In TS file:

export class MyComponent {

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

and after that in html:

<div>{{name}}</div>

here is an example:

@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;
  }
}

No, this is not what the OP was referring to.
why? name will be updated in view then either firstName or secondName got changed. This is not vue.js invention. Knockout.js had computed prop already 7 years ago.
This is not the computed property: just put console.log at the beginning of this get function, and put a couple of repeated {{name}} expressions, and you will see that the function is called for each additional {{name}} call. The whole point of computed property is to compute it only once (per change of the underlying values), and use it many times.
VueJS computed property feature is smarter than this in changes detection, it will evaluate the function only when underlying properties changes.
k
kemsky

I'd like to add one more option (TypeScript 4) because the mentioned ways do not 100% meet all needs. It is not reactive but still good enough. The idea is to explicitly declare a function that detects changes and a function that computes property value.

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;
    }
}

Declare:

// readonly property
this.computed = new ComputedProperty<[number], number>(
        (previous) => {
            return previous?.[0] === this.otherNumber ? previous : [this.otherNumber];
        },
        (current) => {
            return current[0] + 1;
        }
    );

Use:

 <label>Angular computed property: {{computed.value}}</label>

K
Kevin K

You could import two functions from the Vue composition API and it works quite nicely. This might be a bad idea, but it's fun. Just import ref and computed from Vue, and you can have computed properties in Angular.

I have an example PR where I added Vue to an Angular project: https://github.com/kevin-castify/vue-in-angular/pull/1/files

Add Vue to your project using at least version 3 Add to your component.ts like so:

import { Component, OnInit } from '@angular/core';
import { ref, watch } from 'vue';

...

export class FullNameComponent implements OnInit {
  firstName = ref('');
  lastName = ref('');
  fullName = computed(() => this.firstName.value + this.lastName.value;

  ngOnInit(): void {
    // Might need to seed the refs here to trigger the first computation
    firstName.value = "Jane";
    lastName.value = "Doe";
  }
}