Bind a service property to a component property with proper change tracking

Robert picture Robert · May 20, 2016 · Viewed 17.1k times · Source

Consider the utterly simple Angular 2 service:

import { Injectable } from '@angular/core';
import {Category} from "../models/Category.model";

@Injectable()
export class CategoryService {
    activeCategory: Category|{} = {};
    constructor() {};
}

And then the component using this service:

import { Component, OnInit } from '@angular/core';
import {CategoryService} from "../shared/services/category.service";
import {Category} from "../shared/models/Category.model";

@Component({
    selector: 'my-selector',
    template: `
    {{categoryService.activeCategory.Name}}<br/>
    {{category.Name}}<br/>
`,
})
export class MySelectorComponent implements OnInit {
    category:Category|{} = {};

    constructor(public categoryService:CategoryService){};

    ngOnInit() {
        this.category = this.categoryService.activeCategory;
    };
}

Assume appropriately defined Category model and assume that another component somewhere sets the activeCategory on the service to a valid Category at some point. Assume that the categoryservice is set as provider at an appropriately higher level.

When that happens, the first line in the template will correctly display the category Name, but the second line will not. I've tried using getters and setters vs. raw access on the service; I've tried primitive types vs. objects vs. object properties; I can't believe that the first line is the appropriate paradigm for this type of access. Can someone tell me the simplest way to bind a service property to a component property that will properly do change tracking in angular two?

CLARIFICATION: I know I could use observables that I create and push to for myself. What I am asking is if there is any kind of already-baked into the framework way of doing this (that doesn't require me to write the huge amount of boilerplate for an observable) that just makes a variable track between the service and component.

Answer

G&#252;nter Z&#246;chbauer picture Günter Zöchbauer · May 20, 2016

Observables can be used without much boilerplate using Behaviors.

@Injectable() 
export class CategoryService {
  activeCategory:BehaviorSubject<{category:Category}> = new BehaviorSubject({category:null});
  // or just `Subject` depending on your requirements
}
@Component({
  selector: 'my-selector',
  template: `
  {{(categoryService.activeCategory | async)?.Name}}<br/>
`,
})
export class MySelectorComponent implements OnInit {
  constructor(public categoryService:CategoryService){};
}

You also can just bind to properties of your service

@Component({
  selector: 'my-selector',
  template: `
  {{categoryService?.activeCategory?.Name}}<br/>
`,
})
export class MySelectorComponent implements OnInit {
  constructor(public categoryService:CategoryService){};
}    

Using the Elvis (or safe-navigation) operator you don't get an error if the activeCategory only gets a value later, for example when an async call completes.