Utilizing RxJS for Advanced Asynchronous Data Handling in Angular

Best practices for leveraging RxJS to manage complex asynchronous data streams and enhance application responsiveness.

0 likes
7 views

Rule Content

{
  "title": "Utilizing RxJS for Advanced Asynchronous Data Handling in Angular",
  "description": "Best practices for leveraging RxJS to manage complex asynchronous data streams and enhance application responsiveness.",
  "category": "Angular Cursor Rules",
  "rules": [
    {
      "id": "angular-rxjs-avoid-nested-subscriptions",
      "description": "Avoid nested subscriptions to maintain code readability and prevent potential memory leaks.",
      "severity": "error",
      "pattern": {
        "type": "call_expression",
        "callee": {
          "type": "member_expression",
          "object": {
            "type": "call_expression",
            "callee": {
              "type": "member_expression",
              "property": {
                "type": "identifier",
                "name": "subscribe"
              }
            }
          },
          "property": {
            "type": "identifier",
            "name": "subscribe"
          }
        }
      },
      "fix": {
        "description": "Refactor to use higher-order mapping operators like switchMap, mergeMap, or concatMap to flatten nested subscriptions.",
        "example": {
          "before": "this.service.getData().subscribe(data => { this.service.getMoreData(data.id).subscribe(moreData => { /* handle moreData */ }); });",
          "after": "this.service.getData().pipe(switchMap(data => this.service.getMoreData(data.id))).subscribe(moreData => { /* handle moreData */ });"
        }
      }
    },
    {
      "id": "angular-rxjs-use-async-pipe",
      "description": "Use the async pipe in templates to automatically subscribe and unsubscribe from observables, preventing memory leaks.",
      "severity": "warning",
      "pattern": {
        "type": "call_expression",
        "callee": {
          "type": "member_expression",
          "property": {
            "type": "identifier",
            "name": "subscribe"
          }
        }
      },
      "fix": {
        "description": "Replace manual subscription in the component with the async pipe in the template.",
        "example": {
          "before": "this.data$.subscribe(data => { this.data = data; });",
          "after": "<div *ngIf=\"data$ | async as data\">{{ data }}</div>"
        }
      }
    },
    {
      "id": "angular-rxjs-manage-subscriptions",
      "description": "Ensure proper management of subscriptions to prevent memory leaks by unsubscribing when the component is destroyed.",
      "severity": "error",
      "pattern": {
        "type": "class_declaration",
        "body": {
          "type": "method_definition",
          "key": {
            "type": "identifier",
            "name": "ngOnDestroy"
          },
          "value": {
            "type": "function_expression",
            "body": {
              "type": "block_statement",
              "body": []
            }
          }
        }
      }
    },
    {
      "id": "angular-rxjs-use-pipeable-operators",
      "description": "Use pipeable operators to enhance code readability and maintainability.",
      "severity": "warning",
      "pattern": {
        "type": "call_expression",
        "callee": {
          "type": "member_expression",
          "property": {
            "type": "identifier",
            "name": "map"
          }
        }
      },
      "fix": {
        "description": "Refactor to use pipeable operators within the pipe method.",
        "example": {
          "before": "this.data$.map(data => data * 2).subscribe(result => { /* handle result */ });",
          "after": "this.data$.pipe(map(data => data * 2)).subscribe(result => { /* handle result */ });"
        }
      }
    },
    {
      "id": "angular-rxjs-avoid-logic-in-subscribe",
      "description": "Avoid placing complex logic inside subscribe blocks to maintain code clarity and testability.",
      "severity": "warning",
      "pattern": {
        "type": "call_expression",
        "callee": {
          "type": "member_expression",
          "property": {
            "type": "identifier",
            "name": "subscribe"
          }
        },
        "arguments": [
          {
            "type": "function_expression",
            "body": {
              "type": "block_statement",
              "body": [
                {
                  "type": "expression_statement",
                  "expression": {
                    "type": "call_expression"
                  }
                }
              ]
            }
          }
        ]
      },
      "fix": {
        "description": "Move logic to RxJS operators before the subscribe method.",
        "example": {
          "before": "this.data$.subscribe(data => { const processedData = processData(data); this.result = processedData; });",
          "after": "this.data$.pipe(map(data => processData(data))).subscribe(processedData => { this.result = processedData; });"
        }
      }
    }
  ]
}