Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 version of knockout's computed observable

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

like image 779
Gabe O'Leary Avatar asked Dec 15 '22 07:12

Gabe O'Leary


2 Answers

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;
  } 
}
like image 122
TGH Avatar answered Dec 18 '22 11:12

TGH


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

like image 27
mac Avatar answered Dec 18 '22 12:12

mac