Angular Developer Interview Questions For 5 Years Of Experience

1.
What is storybook in Angular
Please checkout the detailed explaination here
2.
What are the design patterns in Angular?
In Angular, which is a popular JavaScript framework for building web applications, several design patterns are commonly used to structure and organize code. These design patterns help developers create maintainable, scalable, and modular applications. Here are some of the design patterns frequently utilized in Angular:
1. Singleton Pattern: Angular services are often implemented using the Singleton pattern. A service is instantiated once and shared across multiple components, allowing them to communicate and share data.
To implement the Singleton pattern in Angular, you can follow these steps:
a. Create a service using the Angular CLI:
b. There are two ways to create a single service in angular that is by using -
-> providedIn property
-> NgModule providers arrays
c. Open the generated service file (`my-singleton-service.service.ts`) and modify it as follows:
d. The `providedIn: 'root'` property in the `@Injectable` decorator is key to implementing the Singleton pattern in Angular. This tells Angular to provide the service at the root level, making it accessible throughout the application.
e. You can now use the `MySingletonService` in your components by injecting it into their constructors:
By injecting `MySingletonService` into multiple components, you will be accessing the same instance of the service across the application, ensuring data consistency and sharing.
It's important to note that Angular itself manages the lifecycle of the singleton service. It creates and maintains a single instance of the service and shares it among components that request it.
In the case of NgModule providers array, a singleton service is created by passing the service as a value to the providers array and if the NgModule is root app module then the service will be available throughout the application as a singleton service.
That's how you can implement the Singleton pattern in Angular using a service. This allows you to share data, maintain state, and provide centralized functionality throughout your application.
2. Dependency Injection (DI) Pattern: Angular utilizes the DI pattern to manage the dependencies between components and services. With DI, the required dependencies are provided to a component or service through constructor injection or property injection, promoting loose coupling and testability.
3. Observer Pattern: Angular leverages the Observer pattern through the EventEmitter class and the RxJS library. Components can emit events using EventEmitters, and other components can subscribe to these events to react accordingly.
4. Strategy Pattern: The Strategy pattern enables you to dynamically select and switch between different strategies at runtime based on specific conditions or requirements. By encapsulating these behaviors in separate classes, components can switch between strategies based on specific conditions.
Here's an example of implementing the Strategy pattern in Angular:
a. Define an interface that represents the common behavior of the strategies. Let's assume we have a payment processing scenario:
b. Implement multiple strategies by creating separate classes that implement the `PaymentStrategy` interface. Each class will provide its own implementation of the `processPayment` method:
c. Create a context class that will use the strategies and provide a method to set the active strategy:
d. Now, you can utilize the strategies in your Angular components or services. For example:
e. In this example, the `PaymentComponent` uses the `PaymentContext` to switch between different payment strategies (`CreditCardStrategy` and `PaypalStrategy`) based on user actions or conditions. By setting the active strategy through `setStrategy`, you can dynamically change the behavior of the payment processing logic in `processPayment`.
This implementation allows for easy extensibility, as you can add new strategies by implementing the `PaymentStrategy` interface and use them interchangeably within the `PaymentComponent` or any other component that requires payment processing functionality.
The Strategy pattern provides flexibility and maintainability by separating the implementation of different algorithms or behaviors from the client code, allowing you to change or extend strategies without modifying existing code.
5. Decorator Pattern: Angular decorators, such as @Component and @Injectable, are based on the Decorator pattern. Decorators provide a way to enhance or modify the behavior of classes or class members without directly modifying the underlying code.
a. Create a base component that represents the core functionality:
b. Create a decorator component that extends the base component:
In this example, the `DecoratorComponent` is a child component that extends the functionality of the `BaseComponent`. It wraps the `BaseComponent` within itself and adds extra content using `<ng-content>`. This allows you to inject additional behavior or template content around the base component.
c. Use the decorator component in your application:
In the `AppComponent` template, the `BaseComponent` is wrapped within the `DecoratorComponent` using its selector `<app-decorator>`. You can inject other components, templates, or HTML content within the `DecoratorComponent` to extend or modify the behavior of the `BaseComponent`.
By using the Decorator pattern in Angular, you can dynamically extend or modify the functionality of existing components by wrapping them within decorator components. This approach provides flexibility, code reusability, and maintainability, as you can reuse the base components while adding specific behavior or content as needed.
6. Facade Pattern:The Facade pattern is a structural design pattern that provides a simplified interface to a complex subsystem, making it easier to use and understand. In Angular, you can apply the Facade pattern to create a simplified API or service that encapsulates the complexity of interacting with multiple components, services, or modules.
Here's an example of implementing the Facade pattern in Angular:
a. Identify a complex subsystem or set of related components/services that you want to simplify for client usage.
b. Create a Facade service that encapsulates the interactions with the complex subsystem. The Facade service will provide a simplified interface for clients to access the subsystem's functionality.
c. Implement the complex subsystem components/services that the Facade service interacts with. These components/services handle the actual complex logic.
d. Use the Facade service in your components to simplify the usage of the complex subsystem:
e. In this example, the `ClientComponent` utilizes the `FacadeService` to perform complex operations without needing to interact directly with the complex subsystem (`ComplexServiceA` and `ComplexServiceB`). The `FacadeService` encapsulates the complexity and provides a simplified interface for the client component to interact with.
By using the Facade pattern in Angular, you can simplify the usage of complex subsystems, hide their implementation details, and provide a straightforward and easy-to-use interface for clients. This promotes code maintainability, readability, and modularity by abstracting the complexity of interacting with multiple components or services behind a single facade.
7. Composite Pattern: The Composite Design Pattern is a structual design pattern that is used to compose objects into a tree-like structure. Components can be composed of other components, forming a tree-like structure. This pattern enables the creation of reusable and hierarchical UI components.
In Angular, you can apply the Composite pattern to represent hierarchical relationships between components or services.
Here's an example of implementing the Composite pattern in Angular:
a. Create an abstract class or interface that represents the common behavior for both individual objects and groups:
b. Implement the abstract class or interface for individual objects:
c. Implement the abstract class or interface for the composite object, which can contain both individual objects and other composite objects:
d. Use the composite object to create a tree-like structure of components:
e. In this example, we create a tree-like structure using the Composite pattern. The `CompositeComponent` can contain both individual `LeafComponent` objects and other `CompositeComponent` objects. Calling the `operation()` method on the top-level `CompositeComponent` will recursively invoke the operation on all its children, whether they are leaf components or other composite components.
By using the Composite pattern in Angular, you can represent complex hierarchical relationships between components or services in a uniform manner. It allows you to treat individual objects and groups of objects in a consistent way, simplifying the code and enabling recursive operations on the composite structure.
8.Factory Pattern: The Factory pattern is a creational design pattern that provides an interface for creating objects without specifying the exact class of the object that will be created. In Angular, you can apply the Factory pattern to encapsulate object creation logic and provide a centralized place for creating instances of different classes.
Here's an example of implementing the Factory pattern in Angular:
a. Define an abstract class or interface that represents the common behavior of the objects you want to create:
b. Implement multiple classes that conform to the `Product` interface:
c. Create a factory class that encapsulates the object creation logic:
d. Use the factory class to create instances of the desired products:
e. In this example, the `ExampleComponent` uses the `ProductFactory` to create instances of different products based on the provided type. By calling the `createProduct` method with the desired type ('A' or 'B'), it receives an instance of the corresponding product class and can invoke its `operation()` method.
Using the Factory pattern in Angular provides a centralized place for creating objects and decouples the client code from the concrete classes. It allows for flexible object creation and enables easy extensibility by adding new product classes and updating the factory logic accordingly.
These are some of the design patterns commonly used in Angular. However, it's worth noting that Angular itself follows the MVC (Model-View-Controller) architectural pattern, where components serve as the controllers, templates represent views, and services act as models.
ng generate service MySingletonService
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MySingletonService {
// Your service implementation goes here
}
import { Component } from '@angular/core';
import { MySingletonService } from './my-singleton-service.service';
@Component({
selector: 'app-my-component',
template: '...',
})
export class MyComponent {
constructor(private mySingletonService: MySingletonService) {
// Access the shared service instance here
}
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MySingletonService } from './my-singleton-service.service';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [MySingletonService], // Add the service here
bootstrap: [AppComponent]
})
export class AppModule { }
// Component using DI
constructor(private productService: ProductService) {
// Use the productService
}
// Component emitting an event
@Output() productSelected = new EventEmitter();
selectProduct(product: Product) {
this.productSelected.emit(product);
}
// Component subscribing to an event
// payment-strategy.interface.ts
export interface PaymentStrategy {
processPayment(amount: number): void;
}
// credit-card-strategy.ts
export class CreditCardStrategy implements PaymentStrategy {
processPayment(amount: number): void {
console.log(`Processing credit card payment of $${amount}`);
// Perform credit card payment processing logic here
}
}
// paypal-strategy.ts
export class PaypalStrategy implements PaymentStrategy {
processPayment(amount: number): void {
console.log(`Processing PayPal payment of $${amount}`);
// Perform PayPal payment processing logic here
}
}
// payment-context.ts
import { PaymentStrategy } from './payment-strategy.interface';
export class PaymentContext {
private strategy: PaymentStrategy;
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
processPayment(amount: number): void {
this.strategy.processPayment(amount);
}
}
import { Component } from '@angular/core';
import { PaymentContext } from './payment-context';
import { CreditCardStrategy } from './credit-card-strategy';
import { PaypalStrategy } from './paypal-strategy';
@Component({
selector: 'app-payment-component',
template: '...',
})
export class PaymentComponent {
constructor(private paymentContext: PaymentContext) {}
processCreditCardPayment(amount: number): void {
this.paymentContext.setStrategy(new CreditCardStrategy());
this.paymentContext.processPayment(amount);
}
processPaypalPayment(amount: number): void {
this.paymentContext.setStrategy(new PaypalStrategy());
this.paymentContext.processPayment(amount);
}
}
import { Component } from '@angular/core';
@Component({
selector: 'app-base-component',
template: 'Base Component',
})
export class BaseComponent {}
import { Component, ViewChild } from '@angular/core';
import { BaseComponent } from './base-component';
@Component({
selector: 'app-decorator',
template: `
<div>
<p>This is the decorator component</p>
<ng-content></ng-content>
</div>
`,
})
export class DecoratorComponent extends BaseComponent {}
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-decorator>
<app-base-component></app-base-component>
</app-decorator>
`,
})
export class AppComponent {}
import { Injectable } from '@angular/core';
import { ComplexServiceA } from './complex-service-a';
import { ComplexServiceB } from './complex-service-b';
@Injectable()
export class FacadeService {
constructor(private serviceA: ComplexServiceA, private serviceB: ComplexServiceB) {}
// Provide simplified methods that internally call the appropriate complex subsystem methods
performOperation(): void {
this.serviceA.complexOperationA();
this.serviceB.complexOperationB();
}
}
@Injectable()
export class ComplexServiceA {
complexOperationA(): void {
// Complex logic of service A
console.log('Performing complex operation A');
}
}
@Injectable()
export class ComplexServiceB {
complexOperationB(): void {
// Complex logic of service B
console.log('Performing complex operation B');
}
}
import { Component } from '@angular/core';
import { FacadeService } from './facade.service';
@Component({
selector: 'app-client-component',
template: '...',
})
export class ClientComponent {
constructor(private facadeService: FacadeService) {}
performFacadeOperation(): void {
this.facadeService.performOperation();
}
}
// component.interface.ts
export interface ComponentInterface {
operation(): void;
}
// leaf.component.ts
import { ComponentInterface } from './component.interface';
export class LeafComponent implements ComponentInterface {
operation(): void {
console.log('Performing operation on a leaf component.');
}
}
// composite.component.ts
import { ComponentInterface } from './component.interface';
export class CompositeComponent implements ComponentInterface {
private children: Component[] = [];
add(component: ComponentInterface): void {
this.children.push(component);
}
remove(component: ComponentInterface): void {
const index = this.children.indexOf(component);
if (index > -1) {
this.children.splice(index, 1);
}
}
operation(): void {
console.log('Performing operation on the composite component.');
for (const child of this.children) {
child.operation();
}
}
}
import { ComponentInterface } from './component.interface';
import { LeafComponent } from './leaf.component';
import { CompositeComponent } from './composite.component';
// Create leaf components
const leaf1: ComponentInterface = new LeafComponent();
const leaf2: ComponentInterface = new LeafComponent();
// Create composite component
const composite: ComponentInterface = new CompositeComponent();
composite.add(leaf1);
composite.add(leaf2);
// Create another composite component
const composite2: ComponentInterface = new CompositeComponent();
composite2.add(composite);
composite2.add(leaf1);
// Perform operation on the composite structure
composite2.operation();
// product.interface.ts
export interface Product {
operation(): void;
}
// product-a.ts
export class ProductA implements Product {
operation(): void {
console.log('Product A operation.');
}
}
// product-b.ts
export class ProductB implements Product {
operation(): void {
console.log('Product B operation.');
}
}
// product-factory.ts
import { Product } from './product.interface';
import { ProductA } from './product-a';
import { ProductB } from './product-b';
export class ProductFactory {
createProduct(type: string): Product {
if (type === 'A') {
return new ProductA();
} else if (type === 'B') {
return new ProductB();
}
throw new Error('Invalid product type');
}
}
import { Component } from '@angular/core';
import { ProductFactory } from './product-factory';
import { Product } from './product.interface';
@Component({
selector: 'app-example',
template: '...',
})
export class ExampleComponent {
constructor(private productFactory: ProductFactory) {}
createProduct(type: string): void {
const product: Product = this.productFactory.createProduct(type);
product.operation();
}
}
3.
How to create a custom decorator in Angular or JavaScript
Decorators are a design pattern that is used to separate modification or decoration of a class without modifying the original source code. In Angular, decorators are functions that allow a service, directive or filter to be modified prior to its usage.
Create decorator
Usage of decorator
function log(target,name,descriptor) {
const original=descriptor.value;
descriptor.value=function(...args) {
console.log('this function is hacked')
const result=original.apply(this,args)
console.log("the result of the function is ", result);
return result;
}
original();
return descriptor;
}
@log
sum(a,b) {
return a+b;
}
//function overridden by decorator called
sum(2,3)
//output will be
this function is hacked
the result of the function is 5
4.
How to make multiple http calls in parallel in Angular
This can be achieved by using forkJoin, this operator takes an array of observables and waits for all the source observables to complete. Once they all complete, it emits an array of the last emitted values from each observable.
Example:
In this example, `forkJoin` takes an array of observables, including two observables that emit 'A' and 'B' respectively after a delay, and an observable that throws an error after a delay. `forkJoin` waits for all the observables to complete, and once they complete, it emits an array of the last emitted values from each observable. However, if any of the observables in `forkJoin` throws an error, the error will be propagated to the error callback of the `subscribe` method.
import { forkJoin, of, throwError } from 'rxjs';
const observables = [
of(1,2,3).pipe(delay(500)),
from([10,11,12])
]
const $forkJoin = forkJoin(observables);
$forkJoin.subscribe(data=>{
console.log('forkjoin data', data); // [3,12] as forkJoin will return last emitted values of each observable
})
5.
What is a module in TypeScript, and how can you use it?
In TypeScript, a module is a way to organize code into reusable, self-contained units of code that can be imported and exported between different parts of an application. Modules can contain classes, functions, interfaces, and other code, and can be either internal to a project or external libraries.
To use a module in TypeScript, you need to define it using the `export` keyword, which makes its members available to other parts of the application. You can then import the module using the `import` keyword, which allows you to use its members in your code.
Here is an example of how to define and use a module in TypeScript:
In this example, we define a module called `myModule` that exports a function called `myFunction` and a class called `MyClass`. The `export` keyword makes these members available outside of the module.
To use the members of the `myModule` module in another file, you can import them using the `import` keyword:
In this example, we import the `myFunction` function and `MyClass` class from the `myModule` module using destructuring. We can then call the `myFunction` function and create an instance of the `MyClass` class, both using the imported names.
There are different ways to import and export modules in TypeScript, such as importing all members using the `* as` syntax, importing default exports, or using aliases for imported members. It is important to understand the different syntaxes and their implications, depending on the size and complexity of the project.
Using modules can help you write more modular and maintainable code in TypeScript, by isolating functionality and reducing naming conflicts. However, it is important to use them judiciously and not to create too many small modules, which can increase the complexity of the codebase.
// myModule.ts
export function myFunction() {
// code here
}
export class MyClass {
// code here
}
// main.ts
import { myFunction, MyClass } from "./myModule";
myFunction();
const myInstance = new MyClass();
6.
How to use a service only for a specific component instead of the whole module ?
7.
Which compiler does angular use ?
Visit the following link : How the Angular Compiler Works
8.
Explain switchMap , mergeMap , forkjoin, combineLatest, concatMap, exhaustMap . what would happen if any of the requests fails in switchMap , mergeMap or forkjoin ?
Let's go through each operator and explain them along with examples. We'll also discuss what happens if any of the requests fail when using `switchMap`, `mergeMap`, or `forkJoin`.
1. switchMap: This operator is used to map each source value to an inner observable, and it only emits the values from the most recent inner observable. If a new source value arrives before the previous inner observable completes, it will switch to the new inner observable and unsubscribe from the previous one. Example:
import { of, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';
const $switchMap = from([1,2,3,4]).pipe(switchMap(data=>{
return of(data).pipe(delay(500))
}));
$switchMap.subscribe(data=>{
console.log('switch map data', data); // 4 as switchMap cancels all previous observables when new observable is emitted
})
import { of } from 'rxjs';
import { mergeMap, delay } from 'rxjs/operators';
// Create an observable that emits three values
const sourceObservable = of(1, 2, 3);
// Use mergeMap to merge the values from the inner observables
const resultObservable = sourceObservable.pipe(
mergeMap((value) => {
// Create an inner observable that emits the value after a delay
return of(value).pipe(delay(1000));
})
);
// Subscribe to the result observable
resultObservable.subscribe((value) => {
console.log(value); // 1 2 3
});
import { forkJoin, of, throwError } from 'rxjs';
const observables = [
of(1,2,3).pipe(delay(500)),
from([10,11,12])
]
const $forkJoin = forkJoin(observables);
$forkJoin.subscribe(data=>{
console.log('forkjoin data', data); // [3,12] as forkJoin will return last emitted values of each observable
})
import { combineLatest, interval } from 'rxjs';
const observables = [
of(1,2,3,4),
from([10,11,12])
]
const $combineLatest = combineLatest(observables);
$combineLatest.subscribe(data=> {
console.log('combineLatest data', data);
})
/* output */
// [4, 10]
// [4,11]
// [4,12]
const observables = [
of(1,2,3,4).pipe(delay(500)),
from([10,11,12])
]
then output will be as follows
// [12,1]
// [12,2]
// [12,3]
// [12,4]
import { of } from 'rxjs';
import { concatMap, delay } from 'rxjs/operators';
// Create an observable that emits three values
const sourceObservable = of(1, 2, 3);
// Use concatMap to concatenate the values from the inner observables
const resultObservable = sourceObservable.pipe(
concatMap((value) => {
// Create an inner observable that emits the value after a delay
return of(value).pipe(delay(1000));
})
);
// Subscribe to the result observable
resultObservable.subscribe((value) => {
console.log(value); // 1 2 3
});
const $exhaustMap = from([1,2,3,4]).pipe(
exhaustMap(data=>{
return of(data).pipe(delay(500));
})
)
$exhaustMap.subscribe(data=> {
console.log('exhaustMap data', data); //1
})
9.
How do you handle errors in RxJS observables?
RxJS provides several operators for handling errors in Observables. The two main operators for error handling are `catchError` and `retry`.
1. catchError: The `catchError` operator is used to catch errors that may occur in an Observable and handle them in a graceful way. It takes a function as an argument that returns another Observable or throws an error. If the function returns an Observable, the source Observable will be replaced with the returned Observable. If the function throws an error, the error will be propagated to the subscriber. Here is an example:
In this example, the `map` operator throws an error when it encounters the number 2. The `catchError` operator catches the error and logs the error message to the console. It then replaces the source Observable with a new Observable that emits the numbers 4, 5, and 6.
2. retry: The `retry` operator is used to automatically retry an Observable when it encounters an error. It takes an optional argument that specifies the maximum number of retries. Here is an example:
In this example, the `map` operator throws an error when it encounters the number 2. The `retry` operator retries the Observable up to 2 times before propagating the error to the subscriber.
import { of } from 'rxjs';
import { catchError } from 'rxjs/operators';
of(1, 2, 3).pipe(
map(num => {
if (num === 2) {
throw new Error('Oops!');
}
return num;
}),
catchError(err => {
console.error(err.message);
return of(4, 5, 6);
})
).subscribe(
num => console.log(num),
err => console.error(err),
() => console.log('Complete')
);
import { of } from 'rxjs';
import { map, retry } from 'rxjs/operators';
of(1, 2, 3).pipe(
map(num => {
if (num === 2) {
throw new Error('Oops!');
}
return num;
}),
retry(2)
).subscribe(
num => console.log(num),
err => console.error(err),
() => console.log('Complete')
);
10.
How do you implement backpressure in RxJS?
Backpressure is a mechanism used in reactive programming to handle situations where an Observable is emitting data at a faster rate than it can be consumed. This can lead to issues such as high memory usage, slow processing, and even crashes. RxJS provides several operators for implementing backpressure, including `buffer`, `throttle`, `debounce`, `sample`, and `switchMap`.
1. buffer: The `buffer` operator collects emitted values from the source Observable into an array and emits the array when it reaches a specified size. It can be used to temporarily store emitted values until they can be processed. Here is an example:
In this example, the `interval` Observable emits a value every 100 milliseconds. The `bufferTime` operator collects the emitted values into an array and emits the array every 1000 milliseconds.
2. throttle: The `throttle` operator throttles the emissions of the source Observable by discarding emissions that occur within a specified time window. It can be used to limit the rate of emissions from the source Observable. Here is an example:
In this example, the `interval` Observable emits a value every 100 milliseconds. The `throttleTime` operator discards emissions that occur within 1000 milliseconds of the previous emission.
3. debounce: The `debounce` operator delays emissions from the source Observable until a specified time has elapsed since the last emission. It can be used to filter out rapid emissions and emit only the last value. Here is an example:
In this example, the `fromEvent` Observable emits a value every time a key is released on the document. The `debounceTime` operator delays emissions until 1000 milliseconds have elapsed since the last emission.
4. sample: The `sample` operator emits the most recent value from the source Observable at a specified time interval. It can be used to emit the most recent value at a regular interval, regardless of how many values are emitted. Here is an example:
In this example, the `interval` Observable emits a value every 100 milliseconds. The `sampleTime` operator emits the most recent value at 1000 millisecond intervals.
5. switchMap: The `switchMap` operator can be used to limit the number of concurrent emissions from the source Observable.
Here's an example of using `switchMap` to implement backpressure:
In this example, the `source$` Observable emits a value every 100 milliseconds. The `processValue` function simulates processing time by returning a Promise that resolves after 1 second. The `switchMap` operator limits the number of concurrent emissions to 2, so only 2 values will be processed at a time. The `limitedSource$` Observable is subscribed to and emits the processed values.
Using `switchMap` in this way ensures that the processing of values is limited to a specific number at a time, preventing the system from being overwhelmed with too many values to process at once.
import { interval } from 'rxjs';
import { bufferTime } from 'rxjs/operators';
interval(100).pipe(
bufferTime(1000)
).subscribe(
values => console.log(values),
err => console.error(err),
() => console.log('Complete')
);
import { interval } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
interval(100).pipe(
throttleTime(1000)
).subscribe(
num => console.log(num),
err => console.error(err),
() => console.log('Complete')
);
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
fromEvent(document, 'keyup').pipe(
debounceTime(1000)
).subscribe(
event => console.log(event.target.value),
err => console.error(err),
() => console.log('Complete')
);
import { interval } from 'rxjs';
import { sampleTime } from 'rxjs/operators';
interval(100).pipe(
sampleTime(1000)
).subscribe(
num => console.log(num),
err => console.error(err),
() => console.log('Complete')
);
import { from, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';
// An Observable that emits a value every 100ms
const source$ = interval(100);
// An Observable that processes values
const processValue = value => {
return from(new Promise(resolve => {
// Simulate processing time
setTimeout(() => {
console.log(`Processed value: ${value}`);
resolve();
}, 1000);
}));
};
// Use switchMap to limit the number of concurrent emissions
const limitedSource$ = source$.pipe(
switchMap(value => processValue(value), 2) // Only allow 2 concurrent emissions
);
limitedSource$.subscribe(
value => console.log(`Received value: ${value}`),
err => console.error(err),
() => console.log('Complete')
);