У angular есть функция "вычисленного свойства", например, в vue.js?

Сначала я узнал Vue.js, и теперь у меня есть проект в Angular 4, поэтому я только что узнал Angular. Я считаю, что все не отличается от Vue, кроме "Вычисленного свойства". В Vue я могу создать вычисляемое свойство, которое прослушивает изменения других свойств и автоматически запускает вычисления.

Например (в Vue 2):

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

Свойство name будет только пересчитываться при изменении одного из имени или фамилии. Как справиться с этим в Angular 2 или 4?

Ответ 1

да, вы можете.

В файле 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;
  }
}

Ответ 2

Хотя на этот вопрос уже дан ответ, но я думаю, что это не очень хороший ответ, и пользователям не следует использовать геттеры в качестве вычисляемых свойств в 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 decorator, поэтому свойство getter будет кэшироваться и пересчитываться только при необходимости

Ответ 3

Я постараюсь улучшить Andzej Maciusovic в надежде получить канонический ответ. Действительно, в VueJS есть функция, называемая вычисляемым свойством, которую можно быстро показать на примере:

<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 действительно помогает с этой проблемой:

  1. Установите его, используя npm install --save mobx-angular mobx
  2. Используйте атрибуты 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;
       }
    }

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>

Если a или b изменены, AB вызывается один раз, а getAB - несколько раз. Если c изменяется, вызывается только getAB. Таким образом, это решение более эффективно, даже если необходимо выполнить вычисления.

Ответ 4

В некоторых случаях использование Pure Pipe может быть разумной альтернативой, очевидно, что это связано с некоторыми ограничениями, но, по крайней мере, позволяет избежать дороговизны выполнения функции в любом случае.

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

В вашем шаблоне вместо свойства полного имени вы можете вместо этого просто использовать ' ' | join:firstname:lastname. Довольно грустно, что вычисляемые свойства по-прежнему не существуют для angular.

Ответ 5

Свойства computed в Vue имеют 2 огромных преимущества: реактивность и запоминание.

В Vue я могу создать вычисляемое свойство, которое прослушивает изменения других свойств и автоматически выполняет вычисления.

Вы спрашиваете здесь конкретно о системе реактивности в Angular. Кажется, что люди забыли, что является крае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>