Thomas David Kehoe
Thomas David Kehoe

Reputation: 10930

Angular: how to subscribe a component to a data service?

I've read all the Angular documentation and 89 million blog posts and I just get more confused. I have a button in my header for the user to select their preferred language:

  <mat-menu>
    <button (click)="change_L1_Language('en-US')">English<img width="50" alt="Flag of the United States" src="../assets/images/flags/us-flag.svg"></button>
    <button (click)="change_L1_Language('es')">Español<img width="50" alt="Flag of Spain" src="../assets/images/flags/es-flag.svg"></button>
    <button (click)="change_L1_Language('zh')">中文<img width="50" alt="Flag of China" src="../assets/images/flags/ch-flag.svg">&nbsp; 中文</button>
    <button (click)="change_L1_Language('jp')">日本人<img width="50" alt="Flag of Japan" src="../assets/images/flags/jp-flag.svg" class="flag-border">&nbsp; 日本人</button>
  </mat-menu>

The button works great, the language in the header changes when the user clicks the button.

The component works fine:

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { L1LanguageService } from '../l1-language.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css'],
})

export class HeaderComponent implements OnInit {
  public L1_Language: string = 'en-US';

  constructor(
    private l1LanguageService: L1LanguageService,
  ) {}

  change_L1_Language(language: string) {
    this.l1LanguageService.changeL1Language(language);
  }
}

I made a service l1-language.service.ts that receives the data from the button:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class L1LanguageService {
  changeL1Language(language): Observable<any>{
    console.log(language);
    return of(language)
  }
}

The service works too. When I click the button, the new language appears on the console:

es                     l1-language.service.ts:13

The only bit that I don't understand is what's in the angle brackets: Observable<any>. Any what?

Now I want to send the data to a component. Here's my component:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { L1LanguageService } from '../../l1-language.service';

@Component({
  selector: 'app-landing-computer',
  templateUrl: './landing-computer.component.html',
  styleUrls: ['./landing-computer.component.css']
})
export class LandingComputerComponent implements OnInit{
  L1_Language: string = 'en-US'; // English is the initial or default language
  observeLanguage: Observable<L1LanguageService>; // the service to be observed

  constructor(
    private l1LanguageService: L1LanguageService,
  ) {
    this.observeLanguage = l1LanguageService.changeL1Language(this.L1_Language) as Observable<L1LanguageService>;
  }

ngOnInit() {
    this.observeLanguage.subscribe(language => console.log(language));  // does nothing
    this.observeLanguage.changeL1Language.subscribe(language => console.log(language));  // "Cannot read property 'subscribe' of undefined"
    this.l1LanguageService.subscribe(language => console.log(language)); // "not a function"
    this.l1LanguageService.changeL1Language.subscribe(language => console.log(language)); // "not a function"
  }
}

This logs the default value en-US when the app loads, but nothing logs from the component when I click the button.

This looks right to me:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { L1LanguageService } from '../../l1-language.service';

Nothing wrong with this:

@Component({
  selector: 'app-landing-computer',
  templateUrl: './landing-computer.component.html',
  styleUrls: ['./landing-computer.component.css']
})

The third line I'm not so sure about:

export class LandingComputerComponent implements OnInit{
  L1_Language: string = 'en-US'; // English is the initial or default language
  observeLanguage: Observable<L1LanguageService>; // the service to be observed

Is the purpose of the third line to create an instance named observeLanguage of the class Observable that observes the class L1LanguageService?

The constructor completely baffles me:

 constructor(
    private l1LanguageService: L1LanguageService,
  ) {
    this.observeLanguage = l1LanguageService.changeL1Language(this.L1_Language) as Observable<L1LanguageService>;
  }

Huh? What's the smelly dumpster fire in the curly brackets?

Every blog post has a different way to subscribe to a service so I put in four, none of which work:

ngOnInit() {
    this.observeLanguage.subscribe(language => console.log(language));  // does nothing
    this.observeLanguage.changeL1Language.subscribe(language => console.log(language));  // "Cannot read property 'subscribe' of undefined"
    this.l1LanguageService.subscribe(language => console.log(language)); // "not a function"
    this.l1LanguageService.changeL1Language.subscribe(language => console.log(language)); // "not a function"
  }

The first line doesn't throw an error but it doesn't do anything.

The second line throws "Cannot read property 'subscribe' of undefined".

The third and fourth lines throw "not a function".

Why are there some many moving parts to a simple service subscription?

Upvotes: 0

Views: 295

Answers (3)

user11202780
user11202780

Reputation:

Observable is for any type of data what You wnt observeI haven't seen anyone watching the service yet try this:

enter image description here

This is result:

enter image description here

Upvotes: 0

b_it_s
b_it_s

Reputation: 714

The only bit that I don't understand is what's in the angle brackets: Observable. Any what?

This defines which type is 'returned' in the observable. So basically, in this Observable any value is accepted... To describe explicitly what type is in the observable, you could say Observable, cause your languages are sent as a string.

Code problems

  1. <button (click)="change_L1_Language('en-US')"> ==> you're using the method change_L1_Language which isn't defined in your component
  2. this.observeLanguage = l1LanguageService.changeL1Language(this.L1_Language) as Observable<L1LanguageService>; doesn't make any sense... You already have an observable in your service method which you can access via this.l1LanguageService.changeL1Language
  3. this.l1LanguageService.changeL1Language.subscribe(language => console.log(language)); ==> This one was very close, but you're subscribing to the definition of your method... you should call your function and subscribe to it (cause it returns an observabel)

Something like below code should work for you to console log the set value for language:

ngOnInit() {
    this.l1LanguageService.changeL1Language(this.L1_Language).subscribe(language => {
        console.log(language)
    });
}

UPDATE: BUT above code will only subscribe to the initially set value, because you're subscribing to a method which is called only once...

A complete solution for you would be to extend your L1LanguageService to something like below code (some untested pseudo code, so check Observable documentation for more info if needed). This way you can subscribe to the L1Language subject and you will be notified on every change of the subject

export class L1LanguageService {
  public L1Language : Subject;
  
  changeL1Language(language) : void {
    console.log(language);
    this.L1Language.next(language);
  }
}

In your ngOnInit you can subscribe to the subject like this this.L1LanguageService.L1Language.subscribe(...)

More info on https://rxjs-dev.firebaseapp.com/guide/subject

Upvotes: 1

abney317
abney317

Reputation: 8492

This should work for you:

export class LandingComputerComponent implements OnInit{
  L1_Language: string = 'en-US'; // English is the initial or default language

  constructor(
    private l1LanguageService: L1LanguageService
  ) { }

  ngOnInit() {
    this.l1LanguageService.changeL1Language(this.L1_Language).subscribe(language => {
      console.log(language);
    });
  }
}

Upvotes: 0

Related Questions