-->

Angular Observable dont show me values in real tim

2020-07-27 03:30发布

问题:

I have 2 components (Menu and Header) and one Service. What I want to do is that when they select an item from the menu, the menu sends the text to the service and the service sends it to the header and changes it. I already managed to send it to the service, but I do not know why in the header with the Observable it does not update.

Service:

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

@Injectable()
export class passVarHelper{

    currentIndex: string;
    constructor(

    ){
        this.currentIndex = "Title";
    }


    getIndex(): Observable <string> {
        return of(this.currentIndex);
    }

    changeIndex(index:string){
        this.currentIndex = index;
        console.log(this.currentIndex);
    }



}

Header:

import { Component, OnInit } from '@angular/core';
import { passVarHelper } from 'src/app/helpers/passVar.helper';

@Component({
  selector: 'app-headerpage',
  templateUrl: './headerpage.component.html',
  styleUrls: ['./headerpage.component.css'],
  providers: [passVarHelper]
})
export class HeaderpageComponent implements OnInit {
  index:string;
  constructor(private _passVarHelper: passVarHelper) {

   }

  ngOnInit() {
    this.loadIndex();
  }

  loadIndex(){
    this._passVarHelper.getIndex().subscribe(
      response  => {
        if(response){
          console.log(response);
          this.index = response;
        }
      },
      error => {
        console.log(<any>error);
      }
    );
  }

}

Menu:

import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { passVarHelper } from 'src/app/helpers/passVar.helper';

@Component({
  selector: 'app-asidenavbar',
  templateUrl: './asidenavbar.component.html',
  styleUrls: ['./asidenavbar.component.css'],
  providers: [passVarHelper]
})
export class AsidenavbarComponent implements OnInit {


constructor(private _passVarHelper: passVarHelper) { 
  }

  ngOnInit() {

  }


  menuClick(){
    var indexMenu = document.getElementById('menu').innerHTML;
    this._passVarHelper.changeIndex(indexMenu);

  }


}

Header HTML

<div class="content-header">
  <div class="container-fluid">
    <div class="row mb-2">
      <div class="col-sm-6">
        <h1 class="m-0 text-dark">{{index}}</h1>
      </div>
      <div class="col-sm-6">
        <ol class="breadcrumb float-sm-right">
          <li class="breadcrumb-item"><a href="#">Volver</a></li>
        </ol>
      </div>
    </div>
  </div>
</div>

回答1:

Observable.of is one time event, as per documentation:

Creates an Observable that emits some values you specify as arguments, immediately one after the other, and then emits a complete notification.

The last part about complete notification is important since once it's done (it's executed for the first time) it emits complete event and doesn't emit anymore. What you need to do is use Subject or BehaviorSubject in order to get constant stream of events.

Try something like this:

@Injectable()
  export class PassVarHelper {
  currentIndex: string;
  currentIndexObs: BehaviorSubject<string>;

  constructor() {
    this.currentIndex = "Title";
    this.currentIndexObs = new BehaviorSubject<string>(this.currentIndex);
  }

  changeIndex(index: string) {
    this.currentIndex = index;
    this.currentIndexObs.next(this.currentIndex);
  }
}

Then loadIndex method:

loadIndex() {
  this._passVarHelper.currentIndexObs.subscribe(
    response => {
      if (response) {
        console.log(response);
        this.index = response;
      }
    },
    error => {
      console.log(<any>error);
    }
  );
}

Here is stackblitz example demonstrating this behavior.

EDIT: Ok, I see where the problem is... It is in providers array, you cannot provide service in header nor menu components, you have to provide it in component that is above them (like app.module or app.component). If you provide services in both of these components you will get different instance of the service and what you want to achieve will not work.

Therefore, provide passVarHelper in either one of them (if another is child of the first one) or in app.module.ts providers array (check the stackblitz example I provided, I provided service in app.module.ts) and only in that one place (in order to get the same instance).

Hmm, Angular has something called provider scope. For example, take a look at this definition:

When you provide the service at the root level, Angular creates a single, shared instance of service and injects into any class that asks for it.

That means that when you provide service at the root level, you will get a single instance of that service wherever you inject it using DI (like when you inject it in constructor).

But, you can also provide services in any other component. By providing a service in the component, you are actually limiting the service only to that component and components inside that component. Essentially, by providing a service in component, you will get a new instance of that service when you inject it in that component (and all its children will have that one instance, unless they themselves provide the service).

So, single instance of the service is used for the component and all its children, but if provided in any child component, that child and all its children will get the new instance.

Hope this makes it a bit more clearer.

Interesting reading material:

  • Official documentation
  • Provider scope


回答2:

Create your observable as a class level variable in your service and assign values to it using .next()

Something like this (I haven't tested the code, just conceptually edited it):

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import 'rxjs/add/observable/from'; 
import { of } from 'rxjs';
import {  BehaviorSubject } from 'rxjs';

@Injectable()
export class passVarHelper{

    currentIndex:BehaviorSubject<string>; 
    constructor(){
        this.currentIndex =  new BehaviorSubject<string>('Title');    
     }

    getIndex(): BehaviorSubject<string> {
        return this.currentIndex;
    }

    changeIndex(index:string){
        this.currentIndex.next(index);

    }

}

In the header component

index = getIndex();

In the header template (html), bind the index using async pipe

{{index | async}}


回答3:

Hey I think you should look into this section for component interaction on the Angular docs: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

You update the currentIndex variable, but I don't believe that triggers the Observable to update properly. The currentIndex should be an Observable variable that you can subscribe to and listen for changes in the variable.