4

I am currently working on porting some code that I have written from Knockout to Angular2. I really like the construct of computed observables in knockout which means the results of that function are only computed when the obeservables it depends on change.

I know I can use a function in angular and it will only update the view when the results change (even though it will calculate the results many times), but my computed is doing sorting, which is why I only want the work to be done when the inputs actually change.

I've found the links below that explain how to do it with angularjs, but I'm not sure how to translate that to angular2 (typescript)

http://www.jomendez.com/2015/02/06/knockoutjs-computed-equivalent-angularjs/ KO.Computed equivalent in Angular / Breeze Initializer

Gabe O'Leary
  • 2,010
  • 4
  • 24
  • 41

2 Answers2

8

I would consider a getter if you are using TypeScript. You could also make your Component use ChangeDetectionStrategy.OnPush to make sure the binding is only evaluated if one of the properties changed. Here is a Plunker: https://plnkr.co/edit/PjcgmFCDj8UvBR7wRJs2?p=preview

import {Component,ChangeDetectionStrategy} from 'angular2/core'

@Component({
  selector:'person',
  template:`<div>
              <div>
                  First Name
                  <input [(ngModel)]="firstName">
              </div>
              <div>
                  Last Name
                  <input [(ngModel)]="lastName">
              </div>

              {{fullName}}
          </div>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class Person{
  firstName = '';
  lastName = '';

  get fullName(){
    return this.firstName + ' ' + this.lastName;
  } 
}
TGH
  • 38,769
  • 12
  • 102
  • 135
  • 2
    This doesn't ensure that the contents of the function are only computer when one of the input's change. If the getter function is doing some kind of computationally intensive work (like sorting a large list of objects) this will continue to be inefficient. - you can see this in the plunkr provided. – Gabe O'Leary Nov 14 '16 at 19:19
4

The closest analogue of ko.observable is Subject or even BehaviorSubject, and for ko.computed you may use Observable.combineLatest

Here kind of hello world example:

import 'rxjs/add/operator/map';
import {Component} from '@angular/core';
import {Observable, BehaviorSubject} from "rxjs";

@Component({
  selector: 'app-root',
  template: `<div>
    <button (click)="both()">both</button>
    <button (click)="first()">first</button>
    <button (click)="last()">last</button>
    <button (click)="ageNow()">age</button>
    <hr />
    fullName: {{fullName | async}}
    <hr />
    age: {{age | async}}
  </div>`
})
export class AppComponent {
  firstName = new BehaviorSubject(''); // aka this.firstName = ko.observable('')
  lastName = new BehaviorSubject('');
  age = new BehaviorSubject(0);
  fullName = Observable.combineLatest(this.firstName, this.lastName) // aka this.fullName = ko.computed(...)
    .do(values => console.log('computed fired'))
    .map(values => values.join(' ').trim());

  both() {
    this.first();
    this.last();
  }

  first() {
    this.firstName.next('foo ' + Date.now());
  }

  last() {
    this.lastName.next('bar ' + Date.now());
  }

  ageNow() {
    this.age.next(Date.now());
  }
}

And probably you will want to get it working with forms then example will be something like this one:

import 'rxjs/add/operator/map';
import {Component} from '@angular/core';
import {Observable, BehaviorSubject} from "rxjs";
import {FormGroup, FormControl, FormBuilder} from "@angular/forms";

@Component({
  selector: 'app-root',
  template: `<form [formGroup]="form">
      <input formControlName="firstName" />
      <input formControlName="lastName" />
      {{fullName | async}}
    </form>`
})
export class AppComponent {
  form:FormGroup;
  firstName = new FormControl('');
  lastName = new FormControl('');
  fullName = Observable.combineLatest(
    this.firstName.valueChanges.startWith(''),
    this.lastName.valueChanges.startWith('')
  ).map(values => values.join(' ').trim());

  constructor(private fb:FormBuilder) {
    this.form = fb.group({
      firstName: this.firstName,
      lastName: this.lastName
    });
  }
}

Note that in form example we are watching not for FormControl but for its build in valueChanges stream, also we define initial value for it.

If you wish not to deal with | async pipes in templates you always can subscribe to your streams and them component properties

mac
  • 1,119
  • 2
  • 14
  • 13