54

Maybe I'm missing something. I can't find a simple tutorial for Observable and its syntax. I'm working with Angular, I need to call a function (defined in a component) from a service. I read this solution. But I can't figure out how to change the value in the Observable created in the service (maybe the creation is not the best method).

I have a component like in the solution:

@Component({
  selector: 'my-component',
  ...
)}
export class MyComponent {
   constructor(myService:MyService) {
   myService.condition.subscribe(value => doSomething(value));
}

doSomething(value) {
  if (value) // do stuff
  else // other stuff
}

}

and this is my service:

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

@Injectable()

export class MyService {
    private condition: Observable<boolean>;
    constructor() { 
       this.condition= new Observable(ob => {ob.next(false); })
       // maybe ob.next is not the best solution to assign the value?
    }

    change() {// how can i change the value of condition to 'true', to call
              // the doSomething function in the component?? 
    }

}
Community
  • 1
  • 1
Johannes
  • 675
  • 1
  • 7
  • 12
  • I'm having trouble extracting exactly what you want to do. Can you give a bit more context to your problem? Why do you (think) you want to call a Component function from a Service? An Observable is kind of like a promise: usually used in a Component, you ask the Service to return an Observable when some async operation is complete, and act on it, all from within a Component (or another Service). – msanford Feb 28 '17 at 15:28
  • The other question you link related to using Services as intermediaries between two Components. Is this what you want to do? – msanford Feb 28 '17 at 15:29
  • Would `this.condition = Observable.of(false)` work for you? – msanford Feb 28 '17 at 15:38
  • 1
    Example: I have a component 'A' that show the log-state (logged in/out), and another component 'B' for manage the login (username/password form and a button to login). The service 'S' is where I store some session information, like log state. So, when i login with the button of 'B', i store this information in the variable/observable 'condition'. The 'A' component is subscribed to that observable, so 'A' component "knows" when the log state is changed and can change refresh some html/css – Johannes Feb 28 '17 at 15:46
  • No, Observable.of doesn't work – Johannes Feb 28 '17 at 16:01

4 Answers4

29

From the comments on my other answer (preserved since it may be helpful to someone), you seem to want to leverage the power of something to emit values over time.

As DOMZE proposed, use a Subject, but here's a (trivial) example showing how you could do that. Though there are evidently some pitfalls to avoid in using Subject directly, I'll leave that up to you.

import { Component, NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { Observable, Subject } from 'rxjs/Rx';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Open the console.</h2>
    </div>
  `,
})
export class App {

  constructor() {}

  let subject = new Subject();

  // Subscribe in Component
  subject.subscribe(next => {
    console.log(next);
  });

  setInterval(() => {
    // Make your auth call and export this from Service
    subject.next(new Date())
  }, 1000)
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

Plunker

In my humble opinion, for this scenario, I don't see why a simple Service/Observable doesn't suffice, but that's none of my business.

Further reading: Angular 2 - Behavior Subject vs Observable?

Community
  • 1
  • 1
msanford
  • 11,803
  • 11
  • 66
  • 93
  • 3
    Thanks! Probably the explanation of my problem was not so good (i'm not english), so thank you for the patience. I tried with the Subject and it seems perfect for my purpose ( and BehaviorSubject ). – Johannes Mar 01 '17 at 10:35
  • 3
    @Johannes It was an interesting problem also for me, so you are welcome! (And your English is perfectly fine, I am also from another linguistic background ;)) – msanford Mar 01 '17 at 13:43
10

I would like to explain how to use Observables in case you want to update the value but I will not use your example. I will just show a few lines of code written in ES5 with explanations.

var updateObservable; // declare here in order to have access to future function

var observable = rx.Observable.create(function (observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);

  // attach a function to the variable which was created in the outer scope
  // by doing this, you now have your 'updateObservable' as a function and the reference to it as well
  // You will be able to call this function when want to update the value
  updateObservable = function (newValue) {
    observer.next(newValue);
    observer.complete();
  };
});

// Suppose you want to update on click. Here actually can be whatever (event) you want
vm.click = function () {
  // you can call this function because you have a reference to it
  // and pass a new value to your observable
  updateObservable(4);
};

// your subscription to changes
observable.subscribe(function(data) {
  // after 'click' event will be performed, subscribe will be fired
  // and the '4' value will be printed
  console.log(data);
});

The main idea here is that if you want to update the value of an Observable you have to do this inside the 'create' function. This will be possible if you declare a function inside this 'create' function.

EA0906
  • 472
  • 6
  • 14
  • it works for me, in Angular 8 will look like this: ` import { Observable } from 'rxjs'; // Inside de Service declaration up to constructor() public schoolObservable = new Observable(observer => { observer.next(this.schoolID); this.updateObservable = (newValue) => { observer.next(newValue); observer.complete(); }; }); // and then inside my function, i call the method when i have the value if (selectOp[0]) { this.schoolID = Number(selectOp[0].id); this.updateObservable(this.schoolID); } // and in the other component ` – Mariel Quezada Feb 05 '20 at 14:33
3

Managing login state

For this implementation, you only need one Service. In it, you would make your backend request to see if the user has a session, and then you can save that in a class variable in the Service. Then, return that variable if it's set, or return the result of a REST call directly.

For example:

export class AuthenticationService {

 private loggedIn: boolean = null;

 constructor(private http: Http) { }

 getUserSession(credentials): Observable<boolean> {

  if (this.loggedIn !== null) {

    return Observable.of(this.loggedIn);

  } else {

    return this.http.get('/authenticate?' + credentials)
      .map((session: Response) => session.json())
      .catch(e => {
        // If a server-side login gate returns an HTML page...
        return Observable.of(false);
      });

  }
}

And then in the Component, just subscribe to the Observable as usual and act on it on-demand.

There are other methods of achieving this with Observable.share() and Observable.replay()

Observable Syntax

To answer part of the question regarding the syntax of an Rx Observable in Angular2 (should someone Google it), the generic form is:

In a Service:

return this.http.get("/people", null)
  .map(res.json())
  .catch(console.error("Error in service")

And in a Component, by way of example:

this.someService.getPeople()
  .subscribe(
    people => this.people,
    error => console.warn('Problem getting people: ' + error),
    () => this.doneHandler();
  );

Formally:

interface Observer<T> {
  onNext(value: T) : void
  onError(error: Error) : void
  onCompleted() : void
}

The first function is called when the "next" value is received. In the case of REST calls (most common case) this contains the entire result.

The second function is an error handler (in the case Observable.trow() was called in the service).

The last is called when the result set was has, and takes no parameters. This is where you can call your doSomething() function.

msanford
  • 11,803
  • 11
  • 66
  • 93
  • Thanks, i'm trying this method. But generally, i would like to understand a method to "tell" a component X that in the service a variable value is changed; so, in a component, i'd like to subscribe to that variable. – Johannes Feb 28 '17 at 18:14
  • @Johannes I see what you mean, so that's why you were looking at using `Observable.next()`, so you could use it almost like an ES6 _*generator_? Or keep the spirit of two-way data binding, but from Service to Component, instead of Component to View. A few ideas come to mind which are not very idiomatic ng2, but it's an interesting question so I'll check it out myself and post another answer if I find something. – msanford Feb 28 '17 at 21:33
1

I suggest you change your condition to be a Subject. The subject is both an observer and an observable. You then will be able to emit a value.

See https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/subjects.md

DOMZE
  • 1,369
  • 10
  • 27