3

When we're looking to share data between routes, the only viable solution seems to use a service. Since we want data to be rerendered in the view when they are updated, we must use BehaviorSubject.

So, correct me if i'm wrong but a simple component will look like this:

@Component({
    selector: 'user-info',
    template: `
        {{($user | async).firstName}}
        <button (click)="updateName()"></button>
    `
})
export class UserInfoComponent {

    private $user;
    private subscribe;

    constructor(private service: UserService) {
        this.user$ = this.service.get();
    }

    updateName() {
        this.subscribe = this.service.get().subscribe(user => 
            this.service.update({...user, firstName: 'new-name'})
        );
    }

    ngOnDestroy() {
        this.subscribe.unsubscribe();
    }
}

This sounds really frustrating because if we did not use the router we could just call the component with <user-info [user]="user | async"></user-info> and it would be so much cleaner:

@Component({
    selector: 'user-info',
    template: `
        {{user.lastName}}
        <button (click)="updateName()"></button>
    `
})
export class UserInfo2Component {

    @Input() user: User;

    constructor(private service: UserService) {}

    updateName() {
        this.service.update({...user, lastName: 'new-name'});
    }
}

So my question is: Why is it so difficult to share data between routes ? Is there a better solution that I could have missed ?

Alexandre Annic
  • 9,942
  • 5
  • 36
  • 50
  • You can also add some parameter of a user to your route for example Id and make a request when the component is initialized and get the user from the backend based on userId – Stefan Apr 19 '18 at 09:31

2 Answers2

1

You can use ngrx to avoid using observables. It's based on the redux concept.

This way, you setup data to the store, pass some id in your route, and use that id in next route to get data from store. Now since, store is the single source of data here, you do not need to keep passing data between routes, every route can set data to the store and other routes can access it from the store.

Read more about it here: Why use NGRX instead of constructor injected services with observables?

ashfaq.p
  • 5,379
  • 21
  • 35
  • Yes I think about `ngrx`. But finally, access to a store variable is always via an observable. So if I have to acess to `user` in many functions of my component I still have to subscribe to the store in each of them and unsbuscribe on destroy. – Alexandre Annic Apr 19 '18 at 12:00
  • Yes, but the pain of creating observables is gone. Let ngrx handle it for you. About subscribing, yes you will have to do that. Unsubscribe is handled automatically btw. You dont need to unsubscribe everytime. – ashfaq.p Apr 19 '18 at 12:04
  • Imagine that I have 2 functions in my component which need to access to `user`. Do I have to subscribe to the store in each of them ? Or do I subscribe in the constructor and bind `user` in a component variable then assume it's defined in my functions ? – Alexandre Annic Apr 19 '18 at 12:12
  • Subscribe in constructor and assign to variable. And yes, it will be then available everywhere else. You can even use `async` pipe. – ashfaq.p Apr 19 '18 at 12:14
  • Ok thanks, I will mark your answer as accepted because it seems there is no real solution. It is unfortunate that we can't use `@Input()` because of the router. – Alexandre Annic Apr 19 '18 at 12:21
1

Your mistake is handling the update logic in the components, and not in the service.

Try something like this:

user.service

import { Component, Input } from '@angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

export class UserService {
  private user = new BehaviorSubject({
    firstName: 'Tomasz'
  })

  user$ = this.user.asObservable();

  update(user: any) {
    this.user.next({
      ...this.user.getValue(),
      ...user
    })
  }
}

app.component.ts

import { Component } from '@angular/core';
import {UserService} from './user.service';

@Component({
  selector: 'my-app',
  template: `
  <input (keydown.enter)="updateUser(input.value); input.value=''" #input>

  {{ user$ | async | json }}
  `,
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  user$;

  constructor(private userService: UserService) {
    this.user$ = this.userService.user$;
  }

  updateUser(firstName: string) {
    this.userService.update({ firstName });
  }
}

Live demo

Tomasz Kula
  • 16,199
  • 2
  • 68
  • 79