Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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.

like image 993
Robert Avatar asked May 20 '16 15:05

Robert


2 Answers

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.

like image 85
Günter Zöchbauer Avatar answered Oct 21 '22 15:10

Günter Zöchbauer


You may try to substiture ngOnInit() with ngDoCheck(). I am not sure (actually I doubt) this is the right thing to do, in any case you can try.

This method is run at every change detection cycle (instead of the standard Angular algorithm I guess, and here is the potential issue) and therefore you shoud have the category property of MySelectorComponent up to date with the changes in the service.

Again need to be carefull of side effects (which are not clear to me).

like image 33
Picci Avatar answered Oct 21 '22 15:10

Picci