Observables - aakash14goplani/FullStack GitHub Wiki

Topics Covered


What is RxJS

  • RxJS is short for Reactive Extensions for JavaScript. RxJS is a library for composing asynchronous and event-based programs by using observable sequences.

  • Think of RxJS as a way to manage data as it flows through time, similar to how apples flow on a conveyor belt.

    • First, we collect. Apples are picked, hauled, and loaded onto a conveyor, creating a stream of apples.
    • Similarly, we use RxJS to send an HTTP request and receive the response, creating a stream of data. The stream is observable. We can watch it as items are added or emitted to the stream. We pipe the stream of apples through a set of operations.
    • As each apple flows through, we transform the apple by cleaning it, filter it by quality or size, and process it, affixing a label, for example.
    • Likewise, we pipe our data through a set of operations to transform, filter, or process our data using query-like operators.
    • We combine streams to merge the results. In our apple example, we combine the green apple stream with a red apple stream to create a mixed bag of apples.
    • Correspondingly, we combined a stream of customer data with a stream of invoice data to display a customer with their overdue invoices.
    • And we can cache the stream. This one doesn't fit as well into our apple metaphor, but when working with data, we may want to cache it in a service to reuse the data without another HTTP request.
  • Going back to the definition, we restate this as RxJS is a library for composing observable streams and optionally processing each item in the stream using an extensive set of operators. RxJS helps us better manage and manipulate the data needed in our application.

  • There are other techniques for managing asynchronous and event-based data.

    • A callback is a function that can be called back after an async operation is complete, but callbacks can be difficult to manage when working with nested async operations.
    • A promise is an object that may produce a single value sometime in the future. It can only handle a single emission and is not cancelable.
    • Async/await is a special syntax that allows writing asynchronous code that looks synchronous. It also can only handle a single emission and is not cancelable.
  • Why use RxJS and not one of above approaches? Using RxJS has several advantages.

    • RxJS provides a single technique for working with any type of data.
    • We often work with multiple sources of data, events from the keyboard/mouser routes, and data from arrays, files, databases, or third-party APIs. With RxJS, we work with different data sources using the same techniques and operators.
    • Our views often require data combined from several sources. We easily compost data with RxJS. RxJS can produce multiple values over time and uses a push model to notify our code when specific actions occur, making it easy for us to watch and react to user interactions or data changes.
    • RxJS is lazy. Evaluation does not start until subscription so we can create recipes that only execute when we need the result.
    • For the best user experience, we want to handle errors gracefully. RxJS has built-in error handling.
    • And with RxJS, we can cancel asynchronous actions. For example, if a user clicks on Product A and then quickly clicks on Product B, we can cancel the request for Product A and only return Product B.
  • So many great advantages to RxJS that Angular built it in to several key features. That's why Angular installs RxJS as part of its core set of libraries. For example, Angular uses RxJS in

    • routing to watch for changes to route parameters, route data, or router events,
    • reactive forms to watch for value changes as the user modifies input elements on a form
    • when communicating with a back-end server, sending requests and receiving responses using HttpClient.

^ Back to Top ^


Observers and Subscribers

  • As an observer in our apple factory, you observe the apples as they are emitted onto the conveyor. You are notified as the next apple comes to you so you can process it, you are notified of an error condition so you can handle it, and you are notified when the stream is complete, letting you know you are done for the day.
  • In RxJS, we define an observer as an object that monitors a stream, the observer response to notifications specified as methods,

    • next to handle the next item emitted in the stream,
    • error to handle an error condition,
    • complete to do any final processing or clean up when the stream is complete.
  • Here are some other ways you may hear an observer described.

    • The first is from the RxJS documentation. An observer is a collection of callbacks that knows how to listen to values delivered by the observable. Okay, that makes sense. We can think of our next, error and complete methods as callbacks that respond as each item is emitted to the stream, an error occurs, or the stream is complete.
    • This one is from the Angular documentation. An observer is a JavaScript object that defines the handlers for the notifications you receive. Here, handlers refers to our next, error, and complete methods.
    • Internally in RxJS, an observer is also defined as an interface with next, error, and complete methods. That ensures that any class created as an observer implements the three methods.
  • One such class that implements the observer interface is a subscriber. While observer is the class we use to observe our stream inside RxJS, each observer is converted to a subscriber. A subscriber is basically an observer with additional features to unsubscribe from an observable.

  • Let's examine what an observer looks like in code.

const observer = {
   next: (value_to_emit) => { // emit values here },
   error: (error_object_to_handle) => { // handle errors here },
   complete: () => { // perform final steps here }
};

What are Observables

  • Observables provide support for passing messages between publishers and subscribers in your application. Observables offer significant benefits over other techniques for event handling, asynchronous programming, and handling multiple values.

  • Observables are declarative - that is, you define a function for publishing values, but it is not executed until a consumer subscribes to it. The subscribed consumer then receives notifications until the function completes, or until they unsubscribe.

  • An observable can deliver multiple values of any type - literals, messages, or events, depending on the context. The API for receiving values is the same whether the values are delivered synchronously or asynchronously. Because setup and teardown logic are both handled by the observable, your application code only needs to worry about subscribing to consume values, and when done, unsubscribing. Whether the stream was keystrokes, an HTTP response, or an interval timer, the interface for listening to values and stopping listening is the same.

  • More Reading.

^ Back to Top ^


Data Streams in Observables

  • When you hear the phrase data stream, you may picture something like continuous water stream, but often data is not continuous like water stream. It's more discreet, like items on a conveyor. Let's take that metaphor a bit further. Imagine you work at an apple factory processing apples.

    • You turn on the conveyor to start the stream.
    • Apples are collected and dumped or emitted into the stream.
    • Each apple passes through a set of operations, such as cleaning, filtering, or adding a label before it reaches you.
    • As an observer of this stream, you'll receive three types of notifications.
      • You're notified each time the next apple comes to you so you can process it.
      • If the machine jams, you're notified of the error, and the conveyor will stop so you can handle it.
      • When there are no more apples to process, you'll receive a notification that you're done for the day.
      • And if you need to leave before all the apples are processed, shut down the conveyor before you go; otherwise, the stream will continue after you're gone, making quite a mess.
  • Let's review this process again, but this time with RxJS terms.

    • Before you receive any apples to process, you must first start the stream. In RxJS, we start the stream by subscribing, which starts sending, or emitting, data items along the stream.
    • The apples pass through a set of operations. In RxJS, we pipe the data in our stream through a set of operators.
    • The apples are observable, and you are the observer.
    • As the observer, you receive three types of notifications: next, error, and complete. In RxJS, an observer is an object with three basic methods.
      • A next method specifies the operation to perform when each item is emitted
      • the error method handles an error condition
      • the complete method specifies the action to take when the stream is complete.
    • And you can stop the apples at any time by shutting down the stream. In RxJS, we unsubscribe.
  • We omit any type of data into an observable stream, numbers, strings, or events, such as mouse events, key events, value changes, or routing events.

    • We can emit objects into an observable stream, such as customers or products.
    • we can emit the response returned from an HTTP request, which is often an array of objects.
    • We can even emit other observable streams.
  • An observable stream is also called an observable sequence, just an observable, or sometimes just a stream. An observable is the core RxJS type.

  • Observables can be synchronous, meaning the items are emitted to the stream immediately, or asynchronous, meaning the items are emitted at some future point in time.

  • Observables can emit a finite number of items, such as the numbers in an array, or an infinite number of items, such as a count when a timer goes off every second forever.

const appleStream = new Observable(appleObserver => {
   appleObserver.next('Apple 1');
   appleObserver.next('Apple 2');
   appleObserver.complete();
});

^ Back to Top ^


Starting Observable Streams

  • In our apple factory, you start the stream of apples by turning on the conveyor. Then apples are collected and dumped, or emitted into the stream. With RxJS, we start the stream by calling the subscribe() method of the observable.

  • We must subscribe to an observable to start the stream; otherwise, no values are emitted to the stream, and we have nothing to observe. To subscribe, we call the subscribe method on the observable, by passing in an observer to monitor that subscription and react each time an item is emitted, when an error occurs, or when the observable completes.

    const sub = appleStream.subscribe(observer);
  • To say this another way, as part of the subscription process, we tell the observable how we will observe it. The subscribe method returns a subscription, which represents the execution of the observable.

  • Now that we have subscribed, this code in the constructor is executed. As per our example above, it first calls next on the observer, displaying the next method message in the console. It calls next again, displaying a second message to the console. Lastly, it calls the complete method, stopping our stream and displaying our complete message.

  • When you hear the term observable or stream, think sequence of emitted items, and remember that a stream won't start until we subscribe.

^ Back to Top ^


Stopping Observable Stream

  • In our apple factory, we turn off the conveyor before we leave to ensure we don't end up with apples leaking everywhere. With RxJS, properly stopping each observable stream helps avoid potential memory leaks in our application.

  • There are several ways to stop an observable.

    • Calling the complete() method of the observer automatically unsubscribes and executes the observer's complete method.
    • Some creation functions and operators, such as of(), from(), and take(), also automatically unsubscribe and execute the observer's complete method.
    • Any uncaught error executes the observer's error method and unsubscribes without calling the observer's complete method.
    • Calling the unsubscribe() method on the subscription also stops the stream. Unsubscribing does not call the observer's complete method. It simply lets the stream know that we are no longer interested in observing it, so it shouldn't emit any more items.
      sub.unsubscribe();

^ Back to Top ^


Creating Observables

Using of() function

  • Of creates an observable using a set of defined values, emitting each value and then completing the stream.
    const appleStream = Observable.of('Apple 1', 'Apple 2');
  • In this example, the observable emits two strings and completes.

Using from() function

  • From creates an observable from an array or other data structure, emitting each individual value from that structure and then completing the stream.

    const appleStream = Observable.from(['Apple 1', 'Apple 2']);
  • In this example, the observable emits two strings and completes.

  • Notice that these creation functions are static functions, meaning we call them directly without an object. So there is no something dot of or something dot from. It's just of and from.

  • Let's clarify the difference between the of and from creation functions. Say we have an array of apples. If we passed that array to the from creation function, we'll get two apples emitted to the stream because the from creation function takes each element in a structure, in this case our array, and emits them.

    const apples = ['Apple 1', 'Apple 2'];
    from(apples); // Apple 1, Apple 2
  • If we pass the same array to the of function, only one item is emitted, the array of apples, not the individual apples. That's because the of function emits each of its arguments into the stream, and in this case, our first argument is the array of apples.

    const apples = ['Apple 1', 'Apple 2'];
    of(apples); // [Apple 1, Apple 2]
  • To achieve the same result as the from creation function with the of creation function, we use the JavaScript spread operator.

    const apples = ['Apple 1', 'Apple 2'];
    of(...apples); // Apple 1, Apple 2

Using fromEvent()

  • fromEvent() creates an observable from any document object model, or DOM event.
    @ViewChild('para') paragraph: ElementRef;
    const paragraphStream = fromEvent(this.paragraph.nativeElemet, 'click').subscribe(...);
  • In this example, we create an observable from click events on a paragraph element.
  • This technique is useful if you want to combine this stream with others that want notification of the click events.

Using interval()

  • Interval is another creation function. It creates an observable that emits a sequential number at a defined interval.
  • This is often used to generate an asynchronous stream for test or sample code.
    const num = interval(1000).subscribe(...);
  • This example emits a sequential number every 1000 ms until the stream is stopped.

^ Back to Top ^


Operators

  • An RxJS operator is a function that transforms and manipulates items in an observable stream.

  • We apply operators in sequence using the observable's pipe() method. Here is an observable stream of three numbers. We call the pipe() method of the observable and pass in operators, separated by commas. In this example, we use map(), tap(), and take().

    of(2, 4, 6).
    pipe(
       map(item => item * 2),
       tap(item => console.log(item)),
       take(2)
    ).subscribe(console.log);
  • Many of the operators require arguments to define the details for the operation. Our original observable is the source observable. We use the pipe() method on this observable. When we subscribe, the source observable stream starts emitting items. Each item is piped through the series of operators in sequence. In this example, the 2 is emitted and processed through each operator. Then the 4 is emitted and processed through each operator. Then the 6. The value output by the last operator is the value emitted to the result observable and processed by the observer's next method, which we pass to the subscribe.

^ Back to Top ^


Handling Errors

  • In real-world applications, we need to handle errors. When working with observables, it's important to catch errors. Any error stops the observable stream, it won't emit any more items.

  • There are two basic strategies for handling errors with observables:

    • catch and replace
    • catch and rethrow.
  • In both cases, the first step is the catch the error. That brings us to our next operator. The catchError() operator catches any errors that occur on an observable. Note that the catchError() operator must be sequentially after any operator that could generate an error. Use catchError() for catching errors and rethrowing and error or replacing the errored observable with a new observable so we can continue through our pipeline after an error occurs.

  • Let's examine the catch and replace strategy first.

    • The catch and replace strategy involves catching the error and replacing the errored observable with a new observable.
    • What do we replace the errored observable with?
      • Depending on the error and the observable, we could return an observable created from hard-coded data or data stored locally for the user. For example, if the application uses local storage to store a list of items, the stored list could be used if an error occurs retrieving the current list of items.
      • We could return an observable that emits an empty value or empty array. Or we could return EMPTY, which is an RxJS constant defining an observable that emits no items and immediately completes.
    • In this example, we catch the error and log it to the console. If the observable has an error, it stops and won't emit anymore items, so we create a new observable and return it, effectively replacing the errored observable.
    • In this example, we use the of() creation function to emit a default array of products. In a real application, if a retrieve is unsuccessful, we may fall back to a locally safe set of items or return an empty set.
      return this.http.get<Product[]>(this.productsUrl)
      .pipe(
        catchError(err => {
          console.log(err);
          return of([{id: 1, name: 'test'}, {id: 2, name: 'demo'}]);
        });
      );
    • The observer does not get notified of the error because the catchError() caught and handled it. Instead, the observer's next method is called with the emitted array from the new observable.
      this.sub = this.productService.getProducts()
      .subscribe(
        products => this.products = products,
        error => this.errorMessage = error
      );
    • Here our source observable generates an error. The catchError() operator catches the error and creates a new observable, which emits the array of two products. That array is emitted to the result stream, and processing continues as if there were no error.
    • More formally, catchError() is an error handling operator. Just like every other operator, it takes an observable stream as an input, subscribes to it, and creates an output stream.
    • When an item is emitted from the input stream, it's emitted to the output stream. If there is no error, catchError() outputs a stream identical to its input stream. If an error occurs, catchError() catches the error, unsubscribes from the input stream, and returns a replacement observable.
    • It optionally rethrows the error, propagating it further up the chain. We've just talked through the replacement strategy, now let's look at how to rethrow the error.
  • catch and rethrow

    • In this example, we again catch the error with the catchError() operator and log it. Once an observable has an error, it stops and won't emit any more items. We use throwError() to return a replacement observable and rethrow the error, propagating it further up the chain.
      return this.http.get<Product[]>(this.productsUrl)
      .pipe(
        catchError(this.handleError)
      );
      private handleError(err: any) {
        // ...
        return throwError(err);
      }
    • throwError() is actually a creation function that create an observable that emits no items and immediately emits an error notification. Here we throw the specified error. Use throwError() to propagate an error to other parts of the application.
    • throwError() is a creation function that creates and returns a replacement observable that emits no items. Its returned value is represented as Observable of never because it emits no values and never completes.
    • After creating the observable, throwError() immediately emits an error notification, stopping the newly created observable.
    • In our existing service code, the handleError() function executes some code to log the error and build a user-friendly error message.
    • The throwError() replaces the original observable with Observable of never and then emits an error, essentially rethrowing.
    • Since the service rethrows the error, it's up to the component to catch that error. Our original component code used the subscribe to provide an error method.
      this.sub = this.productService.getProducts()
      .subscribe(
        products => this.products = products,
        error => this.errorMessage = error
      );
    • We set the local errorMessage property there. But now that we've changed to a more reactive development approach, we aren't subscribing. We instead catch the error, propagate it from the service by piping the emitted item through the catchError() operator, like this. In the catchError, we set the local errorMessage property to the error message propagated by the service.
  • We don't want to rethrow from our component because the error will propagate to our template. We'll instead catch and replace. If, due to the error, we don't get data from our service, our component may want to replace the errored observable with an empty observable.

       return this.http.get<Product[]>(this.productsUrl)
       .pipe(
         catchError(err => {
           this.errorMessage = err;
           return EMPTY;
         });
       );
  • EMPTY is an RxJS constant that returns an observable that emits no items and immediately emits a complete notification. Here we return EMPTY. Use EMPTY to return an empty observable. It is especially useful when replacing an errored observable when you have no default value or to catch and ignore the error. In our error handling then, we replace the errored observable by returning EMPTY.

^ Back to Top ^


Build-in Observables

  1. router.params.subscribe()

    • Observables are constructs to which you subscribe to be informed about the changes in data because remember, observables are that stream of data and whenever a new data piece is emitted, our subscription will know about it.

      this.route.params.subscribe(
         (param: Params) => {
           this.server.id = +param.id;
         }
       );
    • In this case, params is the observable, it's a stream of route parameters and that stream gives us a new route parameter whenever we go to a new page, whenever that parameter in the URL changes and then here in this function we pass to subscribe, we get the new params and we can extract our relevant param, in this case the ID param from that.

  2. interval.subscribe()

    • I want to build my own observable here in ngOnInit() and for that, we can import from RxJS package. RxJS package gives us different ways of creating a new observable and one of them is the interval() method.

    • You can just import interval from RxJS, you can call interval and pass in a number and that is a bit like the set interval where you pass a number in milliseconds that will fire an event every x milliseconds. So if I pass 1000 here, then every second, a new event will be emitted.

      import { interval, Subscription } from 'rxjs';
      ...
      incrementerSubscription: Subscription;
      ...
      ngOnInit(): void {
         this.incrementerSubscription = interval(1000).subscribe(
            (count) => {
               console.log('count: ', count);
            }
         );
      }
    • Now this gives us an observable and therefore, we can subscribe to the observable, I'm passing an anonymous function here to subscribe, that's the first argument we can pass to subscribe, it's the handler for all the data values that are emitted.

    • So here, we have interval which will fire a new value every second and in this function here, I will simply console log count.

    • Now when I navigate away to other component, you see that this keeps on counting and that is something you have to be aware of, observables don't necessarily stop emitting values just because you're not interested in them anymore.

    • There are certain observable that emit a value once and they're done, like for example an HTTP request where you get back a response, but there are other observables that keep on emitting values and to stop that and therefore to prevent memory leaks, which you would otherwise introduce if that keeps on emitting whilst you're not interested in it anymore, you should unsubscribe from any observable in which values you are no longer interested.

    • That's really important because before we do that, watch what happens if I go back to the main page. I'm on component 2, I was on main component before, now I go back to main page and what you now see is a new observable started. So we have the old one going on and a new one and of course we can repeat that. We get more and more of observables counting and that of course is very bad. If that happens behind the scenes, you quickly run out of resources and you slow down your app, you introduce a memory leak here because your memory gets occupied a lot by data you don't need.

      ngOnDestroy(): void {
         this.incrementerSubscription.unsubscribe();
      }
    • We clear that subscription and therefore we prevent memory leaks because we're not keeping old subscriptions around. Now if you save that and reload the app, you see our observable starts again but if I navigate away, we get no new values. If I go back, it starts at zero again but the old observable is dead because we unsubscribed and that's an important step.

    • Common Question - We unsubscribe here, why don't we unsubscribe in the params observable, here where we also set up a subscription? - Angular does that for you. For the observables provided by Angular, you don't need to unsubscribe mnually here,

^ Back to Top ^


Custom Observables

  • Our task is to replicate interval observable.

  • You can create a new observable using new keyword. Observable takes a function and I'll pass in an anonymous arrow function here, RxJS will pass in that argument for us and that argument is a so-called observer.

  • Now what's an observer? - The observer is a part that is interested in being informed about new data, about errors or about the observable being completed.

  • Our job here is to tell the observer about new data, about an error or about the observable being completed. Here, we're not responsible for listening because the observer is the listener, here we get that listening part as an argument and we need to tell it once we're done, once new data is there and so on.

  • So in this anonymous function, we can now use the regular set interval method and set an interval of one second let's say as before and we can now call console log or do whatever we want or simply use our observer and there, we now got methods like next(). We can call next() here to emit a new value.

  • The observer has a couple of important methods, next() is one of them, error() is another of them that would be the one you use to throw an error and complete() is one to let the observer know that you're done.

  • Here we want to decrement number at every interval, so we pass our count variable to next() method. We want to add a condition that if given number is less than -10, throw error using error() method. Finally we want our observable to get completed if number value is equal to 10. Here is how we do it:

    const customObservable = new Observable(
       (observer) => {
         let count = 0;
         setInterval(
           () => {
             observer.next(count);
    
             if (Math.ceil(Math.random() * 10) === 10) {
               observer.complete();
             }
    
             if (count < -10) {
               observer.error(new Error('stack limit exceeded!'));
             }
             count--;
           },
         1000);
       }
     );
  • Now we can also subscribe to our custom observable in ngOnInit(), of course you could do this in other places too... To subscribe, you pass the same thing as before, a function that simply accepts that data we're emitting. In this case, we could name it count again or to avoid naming confusion, simply name it data, whatever you want and here I will now console log that data, just as before.

    this.customDecrementerSubscription = customObservable.subscribe(
       (data: number) => {
         console.log('decrementer: ', data);
       },
       (error) => {
         console.log('Error occurred: ', error.message);
       },
       () => {
         console.log('Custom Observable completed successfully!');
       }
     );
  • Handling Error in Observable

    • Whenever an observable throws an error, it cancels, it's done, it will not emit any other values and therefore in that case, you also don't need to unsubscribe.

    • Thus far, we passed the first argument which is this data function, the second argument would be our function that gets called when an error occurs and we'll get the error as an argument there.

    • Now of course the simplest thing we can do here is that we log that error to the console but obviously you could do way more than that, you could send it to your own back-end and store it in a database there, you can show an error message and alert to the user.

  • Completing Observable

    • We simply call observer complete and there, you don't need to pass any arguments.

    • It's a function that gets no arguments because completing doesn't pass any arguments and it's simply a function where you can do some cleanup work or whatever you need to do and here I'll just log completed.

  • Here too, we should unsubscribe our custom observable, once we no longer need it!

    ngOnDestroy(): void {
      this.customDecrementerSubscription.unsubscribe();
    }
  • Note on ERROR and COMPLETE

    • Throwing an error actually cancels the observable and lets it die but completing it is something different, completing can be a normal process in an observable.

    • Whenever an observable completes, it really is done, there are no other values emitted thereafter, which kind of makes sense because it completed.

    • You might think that complete, so that complete function here also fires when you're getting an error, after all the observable does finish after throwing an error - Well that's not the case. When it cancels due to an error, then that's a different thing than when it completes. An error cancels the observable, it does not complete it, technically in both cases, no new values are emitted but regarding the functions that get called here, there is a difference.

  • NOTE: Whenever you subscribe and you set up your different handler functions here, RxJS in the end merges them all together into one object and passes that object, the observer to the observable and inside of the observable, it will then interact with the observer and let the observer know about new data and errors and so on and that is how your functions get called.

^ Back to Top ^


Subjects

  • I want to communicate or pass data between two components, what methods do I have? - Well with a service and an event emitter for example, this works but this is the old approach better one, a more recommended one and that new approach, the better approach uses a subject.

  • Subject is something we import from RxJS, instead of EventEmitter, you now create a subject here. Other than that, it's pretty similar though, it's a generic type where you define which data will eventually be emitted.

    import { Subject } from 'rxjs';
    ...
    // userStatus = new EventEmitter<boolean>();
    userStatus = new Subject<boolean>();
  • We also use it very similarly, in the component we don't call emit(), you call next(). We know observables, we can subscribe but they're rather passive. A subject is different, you can subscribe to it but it's more active because you can actively call next() on it from outside. In the last section, in observable, we also called next() but that was from inside the observable when we created it.

    // this.userService.userStatus.emit(this.userActive);
    this.userService.userStatus.next(this.userActive);
    ...
    subjectSubscription: Subscription;
    ngOnInit() {
      this.subjectSubscription = this.userService.userStatus.subscribe(
        (userStatus: boolean) => {
          this.activateUser = userStatus;
        }
      );
    }
  • A subject is recommended way for component communication. Don't use event emitter, use subjects they are in the end a bit more efficient behind the scenes.

  • You can also now incorporate all these operators because a subject in the end also is kind of an observable.

  • One important note, just as with your own observables, you should unsubscribe to your subjects though whenever you don't need them.

    ngOnDestroy(): void {
      this.subjectSubscription.unsubscribe();
    }
  • Now one important note about subjects as a replacement for event emitters, this only counts if you're using them as cross component event emitters, where you manually call next or previously emit. You don't use subjects instead of event emitter when you're using @Output. So in a component if you're using @Output here with your own event, you still use the Angular event emitter, you're not using subject there because the subject is not suitable for that, there you need the Angular event emitter.

  • Subject vs EventEmitter

^ Back to Top ^


Commonly used Observables


Reference

⚠️ **GitHub.com Fallback** ⚠️