{"id":13808997,"url":"https://github.com/nilsmehlhorn/ngx-operators","last_synced_at":"2025-10-07T02:21:54.752Z","repository":{"id":38327460,"uuid":"245622195","full_name":"nilsmehlhorn/ngx-operators","owner":"nilsmehlhorn","description":"RxJS operators for Angular","archived":false,"fork":false,"pushed_at":"2025-04-20T13:56:52.000Z","size":2345,"stargazers_count":139,"open_issues_count":6,"forks_count":16,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-09-13T04:46:05.896Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nilsmehlhorn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-03-07T11:42:18.000Z","updated_at":"2025-07-24T14:36:13.000Z","dependencies_parsed_at":"2024-01-15T20:49:54.626Z","dependency_job_id":"42d29080-c6ae-4daa-9758-5d158fcfdf44","html_url":"https://github.com/nilsmehlhorn/ngx-operators","commit_stats":{"total_commits":51,"total_committers":5,"mean_commits":10.2,"dds":"0.21568627450980393","last_synced_commit":"1a216d37960caee7a2007bc7cc9c4f80c4c8d27c"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/nilsmehlhorn/ngx-operators","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilsmehlhorn%2Fngx-operators","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilsmehlhorn%2Fngx-operators/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilsmehlhorn%2Fngx-operators/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilsmehlhorn%2Fngx-operators/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nilsmehlhorn","download_url":"https://codeload.github.com/nilsmehlhorn/ngx-operators/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilsmehlhorn%2Fngx-operators/sbom","scorecard":{"id":688069,"data":{"date":"2025-08-11","repo":{"name":"github.com/nilsmehlhorn/ngx-operators","commit":"36032bd760cb25163bd2bcdd838e27ef3887effb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 2/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":4,"reason":"dependency not pinned by hash detected -- score normalized to 4","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/nilsmehlhorn/ngx-operators/ci.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/nilsmehlhorn/ngx-operators/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/nilsmehlhorn/ngx-operators/ci.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 5 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"13 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-4www-5p9h-95mh","Warn: Project is vulnerable to: GHSA-9gqv-wp59-fq42","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-859w-5945-r5v3","Warn: Project is vulnerable to: GHSA-4v9v-hfq4-rm2v","Warn: Project is vulnerable to: GHSA-9jgg-88mc-972h"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T01:29:48.789Z","repository_id":38327460,"created_at":"2025-08-22T01:29:48.789Z","updated_at":"2025-08-22T01:29:48.789Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278708060,"owners_count":26031944,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T01:01:57.325Z","updated_at":"2025-10-07T02:21:54.735Z","avatar_url":"https://github.com/nilsmehlhorn.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Underlying Technologies"],"sub_categories":["RxJS"],"readme":"[![npm-badge](https://img.shields.io/npm/v/ngx-operators.svg?style=flat-square)](https://www.npmjs.com/package/ngx-operators)\n\u0026nbsp;\n[![codecov-badge](https://codecov.io/gh/nilsmehlhorn/ngx-operators/branch/master/graph/badge.svg)](https://codecov.io/gh/nilsmehlhorn/ngx-operators)\n\nngx-operators is a collection of helpful RxJS operators for Angular apps.\n\n## Installation\n\nInstall via\n\n```bash\nnpm i ngx-operators\n```\n\n## Operators\n\n### prepare\n\nReturns an Observable that mirrors the source Observable, but will call a specified function when it's being subscribed to.\n\n`prepare\u003cT\u003e(callback: () =\u003e void): (source: Observable\u003cT\u003e) =\u003e Observable\u003cT\u003e`\n\n**callback**: Function to be called when source is being subscribed to.\n\n**Example**\n\n```typescript\nconst source = of(\"value\").pipe(prepare(() =\u003e console.log(\"subscribed\")));\nsource.subscribe(); // 'subscribed'\n```\n\n[**read more**](https://nils-mehlhorn.de/posts/indicating-loading-the-right-way-in-angular)\n\n### indicate\n\nIndicates whether the observable is currently loading (meaning subscription is active and it hasn't completed or errored).\n\n`indicate\u003cT\u003e(indicator: Subject\u003cboolean\u003e): (source: Observable\u003cT\u003e) =\u003e Observable\u003cT\u003e`\n\n**indicator**: Subject as target for indication\n\n**Example**\n\n```typescript\n@Component({...})\nexport class UserComponent  {\n  loading$ = new Subject\u003cboolean\u003e()\n\n  constructor(private userService: UserService) {}\n\n  create(name = \"John Doe\"): void {\n    this.userService.create(new User(name))\n    .pipe(indicate(this.loading$))\n    .subscribe()\n  }\n}\n```\n\n```typescript\n\u003cbutton (click)=\"create()\"\u003eCreate User\u003c/button\u003e\n\u003cdiv *ngIf=\"loading$ | async\"\u003e\n  Creating, please wait \u003cloading-indicator\u003e\u003c/loading-indicator\u003e\n\u003c/div\u003e\n```\n\n[**read more**](https://nils-mehlhorn.de/posts/indicating-loading-the-right-way-in-angular)\n\n### throwForCodes\n\nMaps Angular HTTP status codes to more semantic errors.\n\n`throwForCodes\u003cT\u003e(codeErrors: {[status: number]: () =\u003e Error}): (source: Observable\u003cT\u003e) =\u003e Observable\u003cT\u003e`\n\n**codeErrors**: Object mapping HTTP codes to error providers\n\n**Example**\n\n```typescript\nthis.http.post(\"/users\", newUser).pipe(\n  throwForCodes({\n    409: () =\u003e new Error(\"User already exists\"),\n    400: () =\u003e new Error(\"Invalid user\")\n  })\n);\n```\n\n### download\n\nTransform HTTP events into an observable download for indicating progress.\n\n`download(saver?: (b: Blob) =\u003e void): (source: Observable\u003cHttpEvent\u003cBlob\u003e\u003e) =\u003e Observable\u003cDownload\u003e`\n\n**saver**: Function for saving download when it's done. This could be `saveAs` from [FileSaver.js](https://github.com/eligrey/FileSaver.js). When no `saver` is provided the download won't be saved by this operator.\n\n**Example**\n\n```typescript\n@Component({...})\nexport class AppComponent  {\n\n  download$: Observable\u003cDownload\u003e\n\n  constructor(private http: HttpClient) {}\n\n  download() {\n    this.download$ = this.http.get('/users/123/report', {\n                           reportProgress: true,\n                           observe: 'events',\n                           responseType: 'blob'\n                         }).pipe(download(() =\u003e saveAs('report.pdf')))\n  }\n}\n```\n\n```html\n\u003cbutton (click)=\"download()\"\u003eDownload\u003c/button\u003e\n\u003cmat-progress-bar\n  *ngIf=\"download$ | async as download\"\n  [mode]=\"download.state == 'PENDING' ? 'buffer' : 'determinate'\"\n  [value]=\"download.progress\"\n\u003e\n\u003c/mat-progress-bar\u003e\n```\n\n[**read more**](https://nils-mehlhorn.de/posts/angular-file-download-progress)\n\n### upload\n\nTransform HTTP events into an observable upload for indicating progress.\n\n`upload\u003cT = unknown\u003e(): (source: Observable\u003cHttpEvent\u003cT\u003e\u003e) =\u003e Observable\u003cUpload\u003cT\u003e\u003e`\n\n**Example**\n\n```typescript\n@Component()\nexport class AppComponent {\n  upload$: Observable\u003cUpload\u003e;\n\n  constructor(private http: HttpClient) {}\n\n  upload(files: FileList | null) {\n    const file = files?.item(0);\n    if (!file) {\n      return;\n    }\n    const data = new FormData();\n    data.append(\"file\", file);\n    this.upload$ = this.http\n      .post(\"/users/123/avatar\", data, {\n        reportProgress: true,\n        observe: \"events\"\n      })\n      .pipe(upload());\n  }\n}\n```\n\n```html\n\u003cinput type=\"file\" #fileInput (change)=\"upload(fileInput.files)\" /\u003e\n\u003cmat-progress-bar\n  *ngIf=\"upload$ | async as upload\"\n  [mode]=\"upload.state == 'PENDING' ? 'buffer' : 'determinate'\"\n  [value]=\"upload.progress\"\n\u003e\n\u003c/mat-progress-bar\u003e\n```\n\n[**read more**](https://nils-mehlhorn.de/posts/angular-file-upload-progress)\n\n### ignoreNotFound\n\nIgnores 404 error responses by instead completing the underlying observable.\n\nNote: You can use [defaultIfEmpty](https://rxjs-dev.firebaseapp.com/api/operators/defaultIfEmpty) to provide a fallback value.\n\n`ignoreNotFound(): (source: Observable\u003cT\u003e) =\u003e Observable\u003cT\u003e`\n\n**Example**\n\n```typescript\n@Component({...})\nexport class AppComponent  {\n  user$: Observable\u003cUser\u003e\n\n  constructor(private http: HttpClient) {}\n\n  ngOnInit() {\n    this.user$ = this.http.get\u003cUser\u003e('/users/123').pipe(\n      ignoreNotFound()\n    );\n  }\n}\n```\n\n### noZoneRunner\n\nRuns an observable sequence outside of the [Angular zone](https://angular.io/guide/zone) so that change detection won't be triggered for intermediate, possibly async, operations. This way change detection can only be run when you actually change your view model. Initialize the runner by passing [NgZone](https://angular.io/api/core/NgZone) which you inject into your service or component. Then wrap your observable with the runner.\n\n`noZoneRunner(zone: NgZone): (source: Observable\u003cT\u003e) =\u003e Observable\u003cT\u003e`\n\n**Example**\n\n```typescript\n@Component({...})\nexport class AppComponent  {\n\n  constructor(private zone: NgZone,\n              private el: ElementRef) {}\n\n  ngOnInit() {\n    const runner = noZoneRunner(this.zone)\n    runner(fromEvent(this.el.nativeElement, 'mousemove').pipe(\n      /* fromEvent \u0026 operations are outside zone, won't trigger change-detection */\n    )).subscribe(() =\u003e {\n      /* operations back inside zone, will trigger change-detection */\n    })\n  }\n}\n```\n\n### runOutsideZone / runInZone\n\nMoves observable execution in and out of [Angular zone](https://angular.io/guide/zone).\n\n`runOutsideZone(zone: NgZone): (source: Observable\u003cT\u003e) =\u003e Observable\u003cT\u003e`\n\n`runInZone(zone: NgZone): (source: Observable\u003cT\u003e) =\u003e Observable\u003cT\u003e`\n\n**Example**\n\n```typescript\nobs$\n  .pipe(\n    runOutsideZone(this.zone),\n    tap(() =\u003e console.log(NgZone.isInAngularZone())), // false\n    runInZone(this.zone)\n  )\n  .subscribe(() =\u003e console.log(NgZone.isInAngularZone())); // true\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilsmehlhorn%2Fngx-operators","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnilsmehlhorn%2Fngx-operators","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilsmehlhorn%2Fngx-operators/lists"}