├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── angular.json ├── config ├── externals.js ├── rollup-esm2015.conf.js ├── rollup-esm5.conf.js ├── rollup-umd.conf.js ├── rollup.conf.js ├── tsconfig-esm2015.json └── tsconfig-esm5.json ├── demo ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.ts │ └── app.module.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── tsconfig.app.json └── typings.d.ts ├── package-lock.json ├── package.json ├── projects └── ngx-restangular │ ├── karma.conf.js │ ├── ng-package.json │ ├── ng-package.prod.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── index.ts │ │ ├── ngx-restangular-config.factory.ts │ │ ├── ngx-restangular-helper.ts │ │ ├── ngx-restangular-http.ts │ │ ├── ngx-restangular.config.ts │ │ ├── ngx-restangular.module.ts │ │ └── ngx-restangular.ts │ ├── public_api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | node_modules 4 | coverage 5 | npm-debug.log 6 | dist 7 | tmp 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [0.1.7](https://github.com/2muchcoffeecom/ng2-restangular/compare/v0.1.6...v0.1.7) (2016-11-08) 7 | 8 | 9 | 10 | 11 | ## [0.1.6](https://github.com/2muchcoffeecom/ng2-restangular/compare/v0.1.5...v0.1.6) (2016-11-08) 12 | 13 | 14 | 15 | 16 | ## 0.1.5 (2016-11-08) 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Developing 2 | 3 | ### Setup 4 | 5 | ``` 6 | npm install 7 | ``` 8 | 9 | ### Testing 10 | 11 | ``` 12 | npm test 13 | ``` 14 | 15 | ### Start Demo 16 | 17 | ``` 18 | npm start 19 | ``` 20 | 21 | ## Submitting Pull Requests 22 | 23 | **Please follow these basic steps to simplify pull request reviews.** 24 | 25 | * Please rebase your branch against the current master 26 | * Run ```npm install``` to make sure your development dependencies are up-to-date 27 | * Make reference to possible [issues](https://github.com/2muchcoffeecom/ngx-restangular/issues) on PR comment 28 | 29 | ## Submitting bug reports 30 | 31 | * Please detail the affected browser(s) and operating system(s) 32 | * Please be sure to state which version of node **and** npm you're using -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Matt Lewis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ngx-restangular. Maintained by [2muchcoffee](https://2muchcoffee.com/). 2 | 3 | This project is the follow-up of the [Restangular](https://github.com/mgonto/restangular/). Ngx-restangular is an Angular 2+ service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. 4 | It's a perfect fit for any WebApp that consumes data from a RESTful API. 5 | 6 | # Demo 7 | 8 | You can check post about using ngx-restangular in [RESTful API Angular Solution](https://2muchcoffee.com/blog/ngx-restangular-restful-api-angular-solution/) 9 | 10 | # Current stage 11 | 12 | Ngx-restangular almost all functionality was transferred from the Restangular. 13 | We are open to any cooperation in terms of its further development. 14 | 15 | # Renaming project from ng2-restangular to ngx-restangular 16 | 17 | This project was renamed from **ng2-restangular** to **ngx-restangular** due to implementation of Semantic Versioning by Angular Core Team. NPM name has also changed, you can install actual version of the project with ``npm install ngx-restangular``. 18 | 19 | # Table of contents 20 | 21 | - [How do I add this to my project in angular 5+?](#how-do-i-add-this-to-my-project-in-angular-5) 22 | - [How do I add this to my project in angular 4?](#how-do-i-add-this-to-my-project-in-angular-4) 23 | - [How do I add this to my project in angular 2?](#how-do-i-add-this-to-my-project-in-angular-2) 24 | 25 | - [Dependencies](#dependencies) 26 | - [Starter Guide](#starter-guide) 27 | - [Quick configuration for Lazy Readers](#quick-configuration-for-lazy-readers) 28 | - [Using Restangular](#using-restangular) 29 | - [Creating Main Restangular object](#creating-main-restangular-object) 30 | - [Lets Code with Observables!](#lets-code-with-observables) 31 | - [Here is Example of code with using promises!](#here-is-example-of-code-with-using-promises) 32 | - [Configuring Restangular](#configuring-restangular) 33 | - [Properties](#properties) 34 | - [withConfig](#withconfig) 35 | - [setBaseUrl](#setbaseurl) 36 | - [setExtraFields](#setextrafields) 37 | - [setParentless](#setparentless) 38 | - [addElementTransformer](#addelementtransformer) 39 | - [setTransformOnlyServerElements](#settransformonlyserverelements) 40 | - [setOnElemRestangularized](#setonelemrestangularized) 41 | - [addResponseInterceptor](#addresponseinterceptor) 42 | - [addFullRequestInterceptor](#addfullrequestinterceptor) 43 | - [addErrorInterceptor](#adderrorinterceptor) 44 | - [setRestangularFields](#setrestangularfields) 45 | - [setMethodOverriders](#setmethodoverriders) 46 | - [setDefaultRequestParams](#setdefaultrequestparams) 47 | - [setFullResponse](#setfullresponse) 48 | - [setDefaultHeaders](#setdefaultheaders) 49 | - [setRequestSuffix](#setrequestsuffix) 50 | - [setUseCannonicalId](#setusecannonicalid) 51 | - [setPlainByDefault](#setplainbydefault) 52 | - [setEncodeIds](#setencodeids) 53 | - [Accessing configuration](#accessing-configuration) 54 | - [How to configure them globally](#how-to-configure-them-globally) 55 | - [Configuring in the AppModule](#configuring-in-the-appmodule) 56 | - [Configuring in the AppModule with Dependency Injection applied](#configuring-in-the-appmodule-with-dependency-injection-applied) 57 | - [How to create a Restangular service with a different configuration from the global one](#how-to-create-a-restangular-service-with-a-different-configuration-from-the-global-one) 58 | - [Decoupled Restangular Service](#decoupled-restangular-service) 59 | - [Methods description](#methods-description) 60 | - [Restangular methods](#restangular-methods) 61 | - [Element methods](#element-methods) 62 | - [Collection methods](#collection-methods) 63 | - [Custom methods](#custom-methods) 64 | - [Copying elements](#copying-elements) 65 | - [Using values directly in templates with Observables](#using-values-directly-in-templates-with-observables) 66 | - [URL Building](#url-building) 67 | - [Creating new Restangular Methods](#creating-new-restangular-methods) 68 | - [Adding Custom Methods to Collections](#adding-custom-methods-to-collections) 69 | - [Example:](#example) 70 | - [Adding Custom Methods to Models](#adding-custom-methods-to-models) 71 | - [Example:](#example-1) 72 | - [FAQ](#faq) 73 | - [How can I handle errors?](#how-can-i-handle-errors) 74 | - [I need to send Authorization token in EVERY Restangular request, how can I do this?](#i-need-to-send-authorization-token-in-every-restangular-request-how-can-i-do-this) 75 | - [I need to send one header in EVERY Restangular request, how can I do this?](#i-need-to-send-one-header-in-every-restangular-request-how-can-i-do-this) 76 | - [How can I send a delete WITHOUT a body?](#how-can-i-send-a-delete-without-a-body) 77 | - [I use Mongo and the ID of the elements is _id not id as the default. Therefore requests are sent to undefined routes](#i-use-mongo-and-the-id-of-the-elements-is-_id-not-id-as-the-default-therefore-requests-are-sent-to-undefined-routes) 78 | - [What if each of my models has a different ID name like CustomerID for Customer](#what-if-each-of-my-models-has-a-different-id-name-like-customerid-for-customer) 79 | - [How can I send files in my request using Restangular?](#how-can-i-send-files-in-my-request-using-restangular) 80 | - [How do I handle CRUD operations in a List returned by Restangular?](#how-do-i-handle-crud-operations-in-a-list-returned-by-restangular) 81 | - [Removing an element from a collection, keeping the collection restangularized](#removing-an-element-from-a-collection-keeping-the-collection-restangularized) 82 | - [How can I access the unrestangularized element as well as the restangularized one?](#how-can-i-access-the-unrestangularized-element-as-well-as-the-restangularized-one) 83 | - [How can add withCredentials params to requests?](#how-can-add-withcredentials-params-to-requests) 84 | - [Server Frameworks](#server-frameworks) 85 | - [Contributing](#contributing) 86 | - [License](#license) 87 | 88 | **[Back to top](#table-of-contents)** 89 | 90 | 91 | # How do I add this to my project in angular 5+? 92 | 93 | You can download this by npm and running `npm install ngx-restangular`. This will install latest version of ngx-restangular (v.2.0.0). 94 | 95 | **[Back to top](#table-of-contents)** 96 | 97 | 98 | # How do I add this to my project in angular 4? 99 | 100 | You can download this by npm and running `npm install --save ngx-restangular@1.0.13` 101 | 102 | Versions from 1.0.14 to 1.1.4 are deprecated. Npm warns you after their installation. Those versions would be removed. 103 | 104 | **[Back to top](#table-of-contents)** 105 | 106 | 107 | # How do I add this to my project in angular 2? 108 | 109 | You can download this by npm and running `npm install ng2-restangular` 110 | 111 | **[Back to top](#table-of-contents)** 112 | 113 | # Dependencies 114 | 115 | Restangular depends on Angular 2+ and Lodash. 116 | 117 | **[Back to top](#table-of-contents)** 118 | 119 | # Starter Guide 120 | 121 | ## Quick Configuration (For Lazy Readers) 122 | This is all you need to start using all the basic Restangular features. 123 | 124 | ````javascript 125 | import { NgModule } from '@angular/core'; 126 | import { AppComponent } from './app.component'; 127 | import { RestangularModule, Restangular } from 'ngx-restangular'; 128 | 129 | // Function for setting the default restangular configuration 130 | export function RestangularConfigFactory (RestangularProvider) { 131 | RestangularProvider.setBaseUrl('http://api.restngx.local/v1'); 132 | RestangularProvider.setDefaultHeaders({'Authorization': 'Bearer UDXPx-Xko0w4BRKajozCVy20X11MRZs1'}); 133 | } 134 | 135 | // AppModule is the main entry point into Angular2 bootstraping process 136 | @NgModule({ 137 | bootstrap: [ AppComponent ], 138 | declarations: [ 139 | AppComponent, 140 | ], 141 | imports: [ 142 | // Importing RestangularModule and making default configs for restanglar 143 | RestangularModule.forRoot(RestangularConfigFactory), 144 | ] 145 | }) 146 | export class AppModule { 147 | } 148 | 149 | // later in code ... 150 | 151 | @Component({ 152 | ... 153 | }) 154 | export class OtherComponent { 155 | constructor(private restangular: Restangular) { 156 | } 157 | 158 | ngOnInit() { 159 | // GET http://api.test.local/v1/users/2/accounts 160 | this.restangular.one('users', 2).all('accounts').getList(); 161 | } 162 | ```` 163 | **[Back to top](#table-of-contents)** 164 | 165 | ## Using Restangular 166 | 167 | ### Creating Main Restangular object 168 | 169 | There are 3 ways of creating a main Restangular object. 170 | The first one and most common one is by stating the main route of all requests. 171 | The second one is by stating the main route and object of all requests. 172 | 173 | ````javascript 174 | // Only stating main route 175 | Restangular.all('accounts') 176 | 177 | // Stating main object 178 | Restangular.one('accounts', 1234) 179 | 180 | // Gets a list of all of those accounts 181 | Restangular.several('accounts', 1234, 123, 12345); 182 | ```` 183 | 184 | **[Back to top](#table-of-contents)** 185 | 186 | ### Lets Code with Observables! 187 | 188 | Now that we have our main Object let's start playing with it. 189 | 190 | ````javascript 191 | // AppModule is the main entry point into Angular2 bootstraping process 192 | @NgModule({ 193 | bootstrap: [ AppComponent ], 194 | declarations: [ 195 | AppComponent, 196 | ], 197 | imports: [ 198 | // Importing RestangularModule 199 | RestangularModule, 200 | ] 201 | }) 202 | export class AppModule { 203 | } 204 | 205 | @Component({ 206 | ... 207 | }) 208 | export class OtherComponent { 209 | allAccounts; 210 | accounts; 211 | account; 212 | 213 | constructor(private restangular: Restangular) { 214 | } 215 | 216 | ngOnInit() { 217 | // First way of creating a this.restangular object. Just saying the base URL 218 | let baseAccounts = this.restangular.all('accounts'); 219 | 220 | // This will query /accounts and return a observable. 221 | baseAccounts.getList().subscribe(accounts => { 222 | this.allAccounts = accounts; 223 | }); 224 | 225 | 226 | let newAccount = {name: "Gonto's account"}; 227 | 228 | // POST /accounts 229 | baseAccounts.post(newAccount); 230 | 231 | // GET to http://www.google.com/ You set the URL in this case 232 | this.restangular.allUrl('googlers', 'http://www.google.com/').getList(); 233 | 234 | // GET to http://www.google.com/1 You set the URL in this case 235 | this.restangular.oneUrl('googlers', 'http://www.google.com/1').get(); 236 | 237 | // You can do RequestLess "connections" if you need as well 238 | 239 | // Just ONE GET to /accounts/123/buildings/456 240 | this.restangular.one('accounts', 123).one('buildings', 456).get(); 241 | 242 | // Just ONE GET to /accounts/123/buildings 243 | this.restangular.one('accounts', 123).getList('buildings'); 244 | 245 | // Here we use Observables 246 | // GET /accounts 247 | let baseAccounts$ = baseAccounts.getList().subscribe(accounts => { 248 | // Here we can continue fetching the tree :). 249 | 250 | let firstAccount = accounts[0]; 251 | // This will query /accounts/123/buildings considering 123 is the id of the firstAccount 252 | let buildings = firstAccount.getList("buildings"); 253 | 254 | // GET /accounts/123/places?query=param with request header: x-user:mgonto 255 | let loggedInPlaces = firstAccount.getList("places", {query: 'param'}, {'x-user': 'mgonto'}); 256 | 257 | // This is a regular JS object, we can change anything we want :) 258 | firstAccount.name = "Gonto"; 259 | 260 | // If we wanted to keep the original as it is, we can copy it to a new element 261 | let editFirstAccount = this.restangular.copy(firstAccount); 262 | editFirstAccount.name = "New Name"; 263 | 264 | 265 | // PUT /accounts/123. The name of this account will be changed from now on 266 | firstAccount.put(); 267 | editFirstAccount.put(); 268 | 269 | // PUT /accounts/123. Save will do POST or PUT accordingly 270 | firstAccount.save(); 271 | 272 | // DELETE /accounts/123 We don't have first account anymore :( 273 | firstAccount.remove(); 274 | 275 | }, () => { 276 | alert("Oops error from server :("); 277 | }); 278 | 279 | 280 | // Get first account 281 | let firstAccount$ = baseAccounts$.map(accounts => accounts[0]); 282 | 283 | 284 | // POST /accounts/123/buildings with MyBuilding information 285 | firstAccount$.switchMap(firstAccount => { 286 | var myBuilding = { 287 | name: "Gonto's Building", 288 | place: "Argentina" 289 | }; 290 | 291 | return firstAccount.post("Buildings", myBuilding) 292 | }) 293 | .subscribe(() => { 294 | console.log("Object saved OK"); 295 | }, () => { 296 | console.log("There was an error saving"); 297 | }); 298 | 299 | 300 | // GET /accounts/123/users?query=params 301 | firstAccount$.switchMap(firstAccount => { 302 | var myBuilding = { 303 | name: "Gonto's Building", 304 | place: "Argentina" 305 | }; 306 | 307 | return firstAccount.getList("users", {query: 'params'}); 308 | }) 309 | .subscribe((users) => { 310 | // Instead of posting nested element, a collection can post to itself 311 | // POST /accounts/123/users 312 | users.post({userName: 'unknown'}); 313 | 314 | // Custom methods are available now :). 315 | // GET /accounts/123/users/messages?param=myParam 316 | users.customGET("messages", {param: "myParam"}); 317 | 318 | var firstUser = users[0]; 319 | 320 | // GET /accounts/123/users/456. Just in case we want to update one user :) 321 | let userFromServer = firstUser.get(); 322 | 323 | // ALL http methods are available :) 324 | // HEAD /accounts/123/users/456 325 | firstUser.head() 326 | }, () => { 327 | console.log("There was an error saving"); 328 | }); 329 | 330 | 331 | // Second way of creating this.restangular object. URL and ID :) 332 | var account = this.restangular.one("accounts", 123); 333 | 334 | // GET /accounts/123?single=true 335 | this.account = account.get({single: true}); 336 | 337 | // POST /accounts/123/messages?param=myParam with the body of name: "My Message" 338 | account.customPOST({name: "My Message"}, "messages", {param: "myParam"}, {}) 339 | } 340 | } 341 | ```` 342 | 343 | **[Back to top](#table-of-contents)** 344 | 345 | ### Here is Example of code with using promises! 346 | 347 | 348 | ````javascript 349 | @Component({ 350 | ... 351 | }) 352 | export class OtherComponent { 353 | allAccounts; 354 | accounts; 355 | account; 356 | 357 | constructor(private restangular: Restangular) { 358 | } 359 | 360 | ngOnInit() { 361 | 362 | // First way of creating a this.restangular object. Just saying the base URL 363 | let baseAccounts = this.restangular.all('accounts'); 364 | 365 | // This will query /accounts and return a promise. 366 | baseAccounts.getList().toPromise().then(function(accounts) { 367 | this.allAccounts = accounts; 368 | }); 369 | 370 | var newAccount = {name: "Gonto's account"}; 371 | 372 | // POST /accounts 373 | baseAccounts.post(newAccount); 374 | 375 | // GET to http://www.google.com/ You set the URL in this case 376 | this.restangular.allUrl('googlers', 'http://www.google.com/').getList(); 377 | 378 | // GET to http://www.google.com/1 You set the URL in this case 379 | this.restangular.oneUrl('googlers', 'http://www.google.com/1').get(); 380 | 381 | // You can do RequestLess "connections" if you need as well 382 | 383 | // Just ONE GET to /accounts/123/buildings/456 384 | this.restangular.one('accounts', 123).one('buildings', 456).get(); 385 | 386 | // Just ONE GET to /accounts/123/buildings 387 | this.restangular.one('accounts', 123).getList('buildings'); 388 | 389 | // Here we use Promises then 390 | // GET /accounts 391 | baseAccounts.getList().toPromise().then(function (accounts) { 392 | // Here we can continue fetching the tree :). 393 | 394 | var firstAccount = accounts[0]; 395 | // This will query /accounts/123/buildings considering 123 is the id of the firstAccount 396 | this.buildings = firstAccount.getList("buildings"); 397 | 398 | // GET /accounts/123/places?query=param with request header: x-user:mgonto 399 | this.loggedInPlaces = firstAccount.getList("places", {query: 'param'}, {'x-user': 'mgonto'}); 400 | 401 | // This is a regular JS object, we can change anything we want :) 402 | firstAccount.name = "Gonto"; 403 | 404 | // If we wanted to keep the original as it is, we can copy it to a new element 405 | var editFirstAccount = this.restangular.copy(firstAccount); 406 | editFirstAccount.name = "New Name"; 407 | 408 | 409 | // PUT /accounts/123. The name of this account will be changed from now on 410 | firstAccount.put(); 411 | editFirstAccount.put(); 412 | 413 | // PUT /accounts/123. Save will do POST or PUT accordingly 414 | firstAccount.save(); 415 | 416 | // DELETE /accounts/123 We don't have first account anymore :( 417 | firstAccount.remove(); 418 | 419 | var myBuilding = { 420 | name: "Gonto's Building", 421 | place: "Argentina" 422 | }; 423 | 424 | // POST /accounts/123/buildings with MyBuilding information 425 | firstAccount.post("Buildings", myBuilding).toPromise().then(function() { 426 | console.log("Object saved OK"); 427 | }, function() { 428 | console.log("There was an error saving"); 429 | }); 430 | 431 | // GET /accounts/123/users?query=params 432 | firstAccount.getList("users", {query: 'params'}).toPromise().then(function(users) { 433 | // Instead of posting nested element, a collection can post to itself 434 | // POST /accounts/123/users 435 | users.post({userName: 'unknown'}); 436 | 437 | // Custom methods are available now :). 438 | // GET /accounts/123/users/messages?param=myParam 439 | users.customGET("messages", {param: "myParam"}); 440 | 441 | var firstUser = users[0]; 442 | 443 | // GET /accounts/123/users/456. Just in case we want to update one user :) 444 | this.userFromServer = firstUser.get(); 445 | 446 | // ALL http methods are available :) 447 | // HEAD /accounts/123/users/456 448 | firstUser.head() 449 | 450 | }); 451 | 452 | }, function errorCallback() { 453 | alert("Oops error from server :("); 454 | }); 455 | 456 | // Second way of creating this.restangular object. URL and ID :) 457 | var account = this.restangular.one("accounts", 123); 458 | 459 | // GET /accounts/123?single=true 460 | this.account = account.get({single: true}); 461 | 462 | // POST /accounts/123/messages?param=myParam with the body of name: "My Message" 463 | account.customPOST({name: "My Message"}, "messages", {param: "myParam"}, {}) 464 | } 465 | } 466 | ```` 467 | 468 | **[Back to top](#table-of-contents)** 469 | 470 | 471 | ## Configuring Restangular 472 | 473 | ### Properties 474 | Restangular comes with defaults for all of its properties but you can configure them. **So, if you don't need to configure something, there's no need to add the configuration.** 475 | You can set all these configurations in **[RestangularModule](#how-to-configure-them-globally) to change the global configuration**, you can also **use the [withConfig](#how-to-create-a-restangular-service-with-a-different-configuration-from-the-global-one) method in Restangular service to create a new Restangular service with some scoped configuration** or **use [withConfig](#withconfig) in component to make specified Restangular** 476 | 477 | #### withConfig 478 | You can configure Restangular "withConfig" like in example below, you can also configure them globally [RestangularModule](#how-to-configure-them-globally) or in service with [withConfig](#how-to-create-a-restangular-service-with-a-different-configuration-from-the-global-one) 479 | 480 | ````javascript 481 | // Function for settting the default restangular configuration 482 | export function RestangularConfigFactory (RestangularProvider) { 483 | RestangularProvider.setBaseUrl('http://www.google.com'); 484 | } 485 | 486 | @NgModule({ 487 | bootstrap: [ AppComponent ], 488 | declarations: [ 489 | AppComponent, 490 | ], 491 | imports: [ 492 | // Global configuration 493 | RestangularModule.forRoot(RestangularConfigFactory), 494 | ] 495 | }) 496 | export class AppModule {} 497 | // Let's use it in the component 498 | @Component({ 499 | ... 500 | }) 501 | export class OtherComponent { 502 | constructor(private restangular: Restangular) {} 503 | 504 | ngOnInit() { 505 | restangular.withConfig((RestangularConfigurer) => { 506 | RestangularConfigurer.setBaseUrl('http://www.bing.com'); 507 | }).all('users').getList() 508 | } 509 | }; 510 | ```` 511 | 512 | #### setBaseUrl 513 | The base URL for all calls to your API. For example if your URL for fetching accounts is http://example.com/api/v1/accounts, then your baseUrl is `/api/v1`. The default baseUrl is an empty string which resolves to the same url that Angular2 is running, but you can also set an absolute url like `http://api.example.com/api/v1` if you need to set another domain. 514 | 515 | #### setExtraFields 516 | These are the fields that you want to save from your parent resources if you need to display them. By default this is an Empty Array which will suit most cases 517 | 518 | #### setParentless 519 | Use this property to control whether Restangularized elements to have a parent or not. So, for example if you get an account and then get a nested list of buildings, you may want the buildings URL to be simple `/buildings/123` instead of `/accounts/123/buildings/123`. This property lets you do that. 520 | 521 | This method accepts 1 parameter, it could be: 522 | 523 | * Boolean: Specifies if all elements should be parentless or not 524 | * Array: Specifies the routes (types) of all elements that should be parentless. For example `['buildings']` 525 | 526 | #### addElementTransformer 527 | This is a hook. After each element has been "restangularized" (Added the new methods from Restangular), the corresponding transformer will be called if it fits. 528 | 529 | This should be used to add your own methods / functions to entities of certain types. 530 | 531 | You can add as many element transformers as you want. The signature of this method can be one of the following: 532 | 533 | * **addElementTransformer(route, transformer)**: Transformer is called with all elements that have been restangularized, no matter if they're collections or not. 534 | 535 | * **addElementTransformer(route, isCollection, transformer)**: Transformer is called with all elements that have been restangularized and match the specification regarding if it's a collection or not (true | false) 536 | 537 | #### setTransformOnlyServerElements 538 | This sets whether transformers will be run for local objects and not by objects returned by the server. This is by default true but can be changed to false if needed (Most people won't need this). 539 | 540 | 541 | #### setOnElemRestangularized 542 | This is a hook. After each element has been "restangularized" (Added the new methods from Restangular), this will be called. It means that if you receive a list of objects in one call, this method will be called first for the collection and then for each element of the collection. 543 | 544 | **I favor the usage of `addElementTransformer` instead of `onElemRestangularized` whenever possible as the implementation is much cleaner.** 545 | 546 | 547 | This callback is a function that has 4 parameters: 548 | 549 | * **elem**: The element that has just been restangularized. Can be a collection or a single element. 550 | * **isCollection**: Boolean indicating if this is a collection or a single element. 551 | * **what**: The model that is being modified. This is the "path" of this resource. For example `buildings` 552 | * **Restangular**: The instanced service to use any of its methods 553 | 554 | This can be used together with `addRestangularMethod` (Explained later) to add custom methods to an element 555 | 556 | ````javascript 557 | service.setOnElemRestangularized((element, isCollection, what, Restangular) => { 558 | element.newField = "newField"; 559 | return element; 560 | }); 561 | ```` 562 | 563 | #### addResponseInterceptor 564 | The responseInterceptor is called after we get each response from the server. It's a function that receives this arguments: 565 | 566 | * **data**: The data received got from the server 567 | * **operation**: The operation made. It'll be the HTTP method used except for a `GET` which returns a list of element which will return `getList` so that you can distinguish them. 568 | * **what**: The model that's being requested. It can be for example: `accounts`, `buildings`, etc. 569 | * **url**: The relative URL being requested. For example: `/api/v1/accounts/123` 570 | * **response**: Full server response including headers 571 | 572 | Some of the use cases of the responseInterceptor are handling wrapped responses and enhancing response elements with more methods among others. 573 | 574 | The responseInterceptor must return the restangularized data element. 575 | 576 | ````javascript 577 | RestangularProvider.addResponseInterceptor((data, operation, what, url, response)=> { 578 | return data; 579 | }); 580 | }); 581 | ```` 582 | 583 | #### addFullRequestInterceptor 584 | This adds a new fullRequestInterceptor. The fullRequestInterceptor is similar to the `requestInterceptor` but more powerful. It lets you change the element, the request parameters and the headers as well. 585 | 586 | It's a function that receives the same as the `requestInterceptor` plus the headers and the query parameters (in that order). 587 | 588 | It can return an object with any (or all) of following properties: 589 | * **headers**: The headers to send 590 | * **params**: The request parameters to send 591 | * **element**: The element to send 592 | 593 | ````javascript 594 | RestangularProvider.addFullRequestInterceptor((element, operation, path, url, headers, params)=> { 595 | return { 596 | params: Object.assign({}, params, {sort:"name"}), 597 | headers: headers, 598 | element: element 599 | } 600 | }); 601 | ```` 602 | If a property isn't returned, the one sent is used. 603 | 604 | #### addErrorInterceptor 605 | The errorInterceptor is called whenever there's an error. It's a function that receives the response, subject and the Restangular-response handler as parameters. 606 | 607 | The errorInterceptor function, whenever it returns false, prevents the observable linked to a Restangular request to be executed. All other return values (besides false) are ignored and the observable follows the usual path, eventually reaching the success or error hooks. 608 | 609 | The refreshAccesstoken function must return observable. It`s function that will be done before repeating the request, there you can make some actions. In switchMap you might do some transformations to request. 610 | ````javascript 611 | // Function for settting the default restangular configuration 612 | export function RestangularConfigFactory (RestangularProvider, authService) { 613 | RestangularProvider.setBaseUrl('http://api.test.com/v1'); 614 | 615 | // This function must return observable 616 | var refreshAccesstoken = function () { 617 | // Here you can make action before repeated request 618 | return authService.functionForTokenUpdate(); 619 | }; 620 | 621 | RestangularProvider.addErrorInterceptor((response, subject, responseHandler) => { 622 | if (response.status === 403) { 623 | 624 | refreshAccesstoken() 625 | .switchMap(refreshAccesstokenResponse => { 626 | //If you want to change request or make with it some actions and give the request to the repeatRequest func. 627 | //Or you can live it empty and request will be the same. 628 | 629 | // update Authorization header 630 | const newHeaders = response.request.headers.set('Authorization', 'Bearer ' + refreshAccesstokenResponse); 631 | const newRequest = response.request.clone({headers: newHeaders}); 632 | 633 | return response.repeatRequest(newRequest); 634 | }) 635 | .subscribe( 636 | res => responseHandler(res), 637 | err => subject.error(err) 638 | ); 639 | 640 | return false; // error handled 641 | } 642 | return true; // error not handled 643 | }); 644 | } 645 | 646 | // AppModule is the main entry point into Angular2 bootstraping process 647 | @NgModule({ 648 | bootstrap: [ AppComponent ], 649 | imports: [ 650 | // Importing RestangularModule and making default configs for restanglar 651 | RestangularModule.forRoot([authService], RestangularConfigFactory), 652 | ], 653 | }) 654 | ```` 655 | 656 | #### setRestangularFields 657 | 658 | Restangular required 3 fields for every "Restangularized" element. These are: 659 | 660 | * id: Id of the element. Default: id 661 | * route: Name of the route of this element. Default: route 662 | * parentResource: The reference to the parent resource. Default: parentResource 663 | * restangularCollection: A boolean indicating if this is a collection or an element. Default: restangularCollection 664 | * cannonicalId: If available, the path to the cannonical ID to use. Useful for PK changes 665 | * etag: Where to save the ETag received from the server. Defaults to `restangularEtag` 666 | * selfLink: The path to the property that has the URL to this item. If your REST API doesn't return a URL to an item, you can just leave it blank. Defaults to `href` 667 | 668 | Also all of Restangular methods and functions are configurable through restangularFields property. 669 | All of these fields except for `id` and `selfLink` are handled by Restangular, so most of the time you won't change them. You can configure the name of the property that will be binded to all of this fields by setting restangularFields property. 670 | 671 | #### setMethodOverriders 672 | 673 | You can now Override HTTP Methods. You can set here the array of methods to override. All those methods will be sent as POST and Restangular will add an X-HTTP-Method-Override header with the real HTTP method we wanted to do. 674 | 675 | ````javascript 676 | RestangularProvider.setMethodOverriders(["Get","Put"]); 677 | ```` 678 | 679 | #### setDefaultRequestParams 680 | 681 | You can set default Query parameters to be sent with every request and every method. 682 | 683 | Additionally, if you want to configure request params per method, you can use `requestParams` configuration similar to `$http`. For example `RestangularProvider.requestParams.get = {single: true}`. 684 | 685 | Supported method to configure are: remove, get, post, put, common (all) 686 | 687 | ````javascript 688 | // set params for multiple methods at once 689 | RestangularProvider.setDefaultRequestParams(['remove', 'post'], {confirm: true}); 690 | 691 | // set only for get method 692 | RestangularProvider.setDefaultRequestParams('get', {limit: 10}); 693 | 694 | // or for all supported request methods 695 | RestangularProvider.setDefaultRequestParams({apikey: "secret key"}); 696 | ```` 697 | 698 | #### setFullResponse 699 | 700 | You can set fullResponse to true to get the whole response every time you do any request. The full response has the restangularized data in the `data` field, and also has the headers and config sent. By default, it's set to false. Please note that in order for Restangular to access custom HTTP headers, your server must respond having the `Access-Control-Expose-Headers:` set. 701 | 702 | ````javascript 703 | // set params for multiple methods at once 704 | RestangularProvider.setFullResponse(true); 705 | ```` 706 | 707 | Or set it per service 708 | ````javascript 709 | // Restangular factory that uses setFullResponse 710 | export const REST_FUL_RESPONSE = new InjectionToken('RestFulResponse'); 711 | export function RestFulResponseFactory(restangular: Restangular) { 712 | return restangular.withConfig((RestangularConfigurer) => { 713 | RestangularConfigurer.setFullResponse(true); 714 | }); 715 | } 716 | 717 | 718 | // Configure factory in AppModule module 719 | // AppModule is the main entry point into Angular2 bootstraping process 720 | @NgModule({ 721 | bootstrap: [ AppComponent ], 722 | declarations: [ 723 | AppComponent, 724 | ], 725 | imports: [RestangularModule], 726 | providers: [ 727 | { provide: REST_FUL_RESPONSE, useFactory: RestFulResponseFactory, deps: [Restangular] } 728 | ] 729 | }) 730 | export class AppModule {} 731 | 732 | 733 | // Let's use it in the component 734 | @Component({ 735 | ... 736 | }) 737 | export class OtherComponent { 738 | users; 739 | 740 | constructor(@Inject(REST_FUL_RESPONSE) public restFulResponse) { 741 | } 742 | 743 | ngOnInit() { 744 | this.restFulResponse.all('users').getList().subscribe( response => { 745 | this.users = response.data; 746 | console.log(response.headers); 747 | }); 748 | } 749 | } 750 | ```` 751 | 752 | #### setDefaultHeaders 753 | 754 | You can set default Headers to be sent with every request. Send format: {header_name: header_value} 755 | ````javascript 756 | import { NgModule } from '@angular/core'; 757 | import { RestangularModule, Restangular } from 'ngx-restangular'; 758 | 759 | // Function for settting the default restangular configuration 760 | export function RestangularConfigFactory (RestangularProvider) { 761 | RestangularProvider.setDefaultHeaders({'Authorization': 'Bearer UDXPx-Xko0w4BRKajozCVy20X11MRZs1'}); 762 | } 763 | 764 | // AppModule is the main entry point into Angular2 bootstraping process 765 | @NgModule({ 766 | ... 767 | imports: [ 768 | // Importing RestangularModule and making default configs for restanglar 769 | RestangularModule.forRoot(RestangularConfigFactory), 770 | ] 771 | }) 772 | export class AppModule { 773 | } 774 | ```` 775 | 776 | #### setRequestSuffix 777 | 778 | If all of your requests require to send some suffix to work, you can set it here. For example, if you need to send the format like `/users/123.json` you can add that `.json` to the suffix using the `setRequestSuffix` method 779 | 780 | #### setUseCannonicalId 781 | 782 | You can set this to either `true` or `false`. By default it's false. If set to true, then the cannonical ID from the element will be used for URL creation (in DELETE, PUT, POST, etc.). What this means is that if you change the ID of the element and then you do a put, if you set this to true, it'll use the "old" ID which was received from the server. If set to false, it'll use the new ID assigned to the element. 783 | 784 | #### setPlainByDefault 785 | 786 | You can set this to `true` or `false`. By default it's false. If set to true, data retrieved will be returned with no embed methods from restangular. 787 | 788 | #### setEncodeIds 789 | 790 | You can set here if you want to URL Encode IDs or not. By default, it's true. 791 | 792 | ### Accessing configuration 793 | 794 | You can also access the configuration via `RestangularModule` and `Restangular.provider` via the `configuration` property if you don't want to use the setters. Check it out: 795 | 796 | ````js 797 | RestangularProvider.configuration.requestSuffix = '/'; 798 | ```` 799 | 800 | **[Back to top](#table-of-contents)** 801 | 802 | ### How to configure them globally 803 | 804 | You can configure this in either the `AppModule`. 805 | 806 | #### Configuring in the `AppModule` 807 | 808 | ````javascript 809 | import { RestangularModule } from 'ngx-restangular'; 810 | 811 | // Function for settting the default restangular configuration 812 | export function RestangularConfigFactory (RestangularProvider) { 813 | RestangularProvider.setBaseUrl('http://api.restngx.local/v1'); 814 | RestangularProvider.setDefaultHeaders({'Authorization': 'Bearer UDXPx-Xko0w4BRKajozCVy20X11MRZs1'}); 815 | } 816 | 817 | // AppModule is the main entry point into Angular2 bootstraping process 818 | @NgModule({ 819 | bootstrap: [ AppComponent ], 820 | declarations: [ 821 | AppComponent, 822 | ], 823 | imports: [ 824 | RestangularModule.forRoot(RestangularConfigFactory), 825 | ] 826 | }) 827 | export class AppModule { 828 | } 829 | ```` 830 | 831 | **[Back to top](#table-of-contents)** 832 | 833 | 834 | #### Configuring in the `AppModule` with Dependency Injection applied 835 | 836 | ````javascript 837 | import { RestangularModule } from 'ngx-restangular'; 838 | 839 | // Function for settting the default restangular configuration 840 | export function RestangularConfigFactory (RestangularProvider, http) { 841 | RestangularProvider.setBaseUrl('http://api.restngx.local/v1'); 842 | RestangularProvider.setDefaultHeaders({'Authorization': 'Bearer UDXPx-Xko0w4BRKajozCVy20X11MRZs1'}); 843 | 844 | // Example of using Http service inside global config restangular 845 | RestangularProvider.addElementTransformer('me', true, ()=>{ 846 | return http.get('http://api.test.com/v1/users/2', {}); 847 | }); 848 | } 849 | 850 | // AppModule is the main entry point into Angular2 bootstraping process 851 | @NgModule({ 852 | bootstrap: [ AppComponent ], 853 | declarations: [ 854 | AppComponent, 855 | ], 856 | imports: [ 857 | RestangularModule.forRoot([Http], RestangularConfigFactory), 858 | ] 859 | }) 860 | export class AppModule { 861 | } 862 | ```` 863 | 864 | **[Back to top](#table-of-contents)** 865 | 866 | ### How to create a Restangular service with a different configuration from the global one 867 | Let's assume that for most requests you need some configuration (The global one), and for just a bunch of methods you need another configuration. In that case, you'll need to create another Restangular service with this particular configuration. This scoped configuration will inherit all defaults from the global one. Let's see how. 868 | 869 | ````javascript 870 | // Function for settting the default restangular configuration 871 | export function RestangularConfigFactory (RestangularProvider) { 872 | RestangularProvider.setBaseUrl('http://www.google.com'); 873 | } 874 | 875 | //Restangular service that uses Bing 876 | export const RESTANGULAR_BING = new InjectionToken('RestangularBing'); 877 | export function RestangularBingFactory(restangular: Restangular) { 878 | return restangular.withConfig((RestangularConfigurer) => { 879 | RestangularConfigurer.setBaseUrl('http://www.bing.com'); 880 | }); 881 | } 882 | 883 | 884 | // AppModule is the main entry point into Angular2 bootstraping process 885 | @NgModule({ 886 | bootstrap: [ AppComponent ], 887 | declarations: [ 888 | AppComponent, 889 | ], 890 | imports: [ 891 | // Global configuration 892 | RestangularModule.forRoot(RestangularConfigFactory), 893 | ], 894 | providers: [ 895 | { provide: RESTANGULAR_BING, useFactory: RestangularBingFactory, deps: [Restangular] } 896 | ] 897 | }) 898 | export class AppModule {} 899 | 900 | 901 | // Let's use it in the component 902 | @Component({ 903 | ... 904 | }) 905 | export class OtherComponent { 906 | constructor( 907 | @Inject(Restangular) public Restangular, 908 | @Inject(RESTANGULAR_BING) public RestangularBing 909 | ) {} 910 | 911 | ngOnInit() { 912 | // GET to http://www.google.com/users 913 | // Uses global configuration 914 | Restangular.all('users').getList() 915 | 916 | // GET to http://www.bing.com/users 917 | // Uses Bing configuration which is based on Global one, therefore .json is added. 918 | RestangularBing.all('users').getList() 919 | } 920 | }; 921 | ```` 922 | 923 | **[Back to top](#table-of-contents)** 924 | 925 | ### Decoupled Restangular Service 926 | 927 | There're some times where you want to use Restangular but you don't want to expose Restangular object anywhere. For those cases, you can actually use the `service` feature of Restangular. 928 | 929 | Let's see how it works: 930 | 931 | ````js 932 | // Restangular factory that uses Users 933 | export const USER_REST = new InjectionToken('UserRest'); 934 | export function UserRestFactory(restangular: Restangular) { 935 | return restangular.service('users'); 936 | } 937 | 938 | 939 | // AppModule is the main entry point into Angular2 bootstraping process 940 | @NgModule({ 941 | bootstrap: [ AppComponent ], 942 | declarations: [ 943 | AppComponent, 944 | ], 945 | imports: [RestangularModule], 946 | providers: [ 947 | { provide: USER_REST, useFactory: UserRestFactory, deps: [Restangular] } // Configurating our factory 948 | ] 949 | }) 950 | export class AppModule { 951 | } 952 | 953 | 954 | // Let's use it in the component 955 | export class OtherComponent { 956 | constructor(@Inject(USER_REST) public User) { 957 | Users.one(2).get() // GET to /users/2 958 | Users.post({data}) // POST to /users 959 | 960 | // GET to /users 961 | Users.getList().subscribe( users => { 962 | var user = users[0]; // user === {id: 1, name: "Tonto"} 963 | user.name = "Gonto"; 964 | // PUT to /users/1 965 | user.put(); 966 | }) 967 | } 968 | } 969 | ```` 970 | 971 | We can also use Nested RESTful resources with this: 972 | 973 | ````js 974 | var Cars = Restangular.service('cars', Restangular.one('users', 1)); 975 | 976 | Cars.getList() // GET to /users/1/cars 977 | ```` 978 | 979 | **[Back to top](#table-of-contents)** 980 | 981 | ## Methods description 982 | 983 | There are 3 sets of methods. Collections have some methods and elements have others. There are are also some common methods for all of them 984 | 985 | ### Restangular methods 986 | These are the methods that can be called on the Restangular object. 987 | * **one(route, id)**: This will create a new Restangular object that is just a pointer to one element with the route `route` and the specified id. 988 | * **all(route)**: This will create a new Restangular object that is just a pointer to a list of elements for the specified path. 989 | * **oneUrl(route, url)**: This will create a new Restangular object that is just a pointer to one element with the specified URL. 990 | * **allUrl(route, url)**: This creates a Restangular object that is just a pointer to a list at the specified URL. 991 | * **copy(fromElement)**: This will create a copy of the from element so that we can modify the copied one. 992 | * **restangularizeElement(parent, element, route, queryParams)**: Restangularizes a new element 993 | * **restangularizeCollection(parent, element, route, queryParams)**: Restangularizes a new collection 994 | 995 | **[Back to top](#table-of-contents)** 996 | 997 | ### Element methods 998 | * **get([queryParams, headers])**: Gets the element. Query params and headers are optionals 999 | * **getList(subElement, [queryParams, headers])**: Gets a nested resource. subElement is mandatory. **It's a string with the name of the nested resource (and URL)**. For example `buildings` 1000 | * **put([queryParams, headers])**: Does a put to the current element 1001 | * **post(subElement, elementToPost, [queryParams, headers])**: Does a POST and creates a subElement. Subelement is mandatory and is the nested resource. Element to post is the object to post to the server 1002 | * **remove([queryParams, headers])**: Does a DELETE. By default, `remove` sends a request with an empty object, which may cause problems with some servers or browsers. [This](https://github.com/mgonto/restangular/issues/193) shows how to configure RESTangular to have no payload. 1003 | * **head([queryParams, headers])**: Does a HEAD 1004 | * **trace([queryParams, headers])**: Does a TRACE 1005 | * **options([queryParams, headers])**: Does a OPTIONS 1006 | * **patch(object, [queryParams, headers])**: Does a PATCH 1007 | * **one(route, id)**: Used for RequestLess connections and URL Building. See section below. 1008 | * **all(route)**: Used for RequestLess connections and URL Building. See section below. 1009 | * **several(route, ids*)**: Used for RequestLess connections and URL Building. See section below. 1010 | * **oneUrl(route, url)**: This will create a new Restangular object that is just a pointer to one element with the specified URL. 1011 | * **allUrl(route, url)**: This creates a Restangular object that is just a pointer to a list at the specified URL. 1012 | * **getRestangularUrl()**: Gets the URL of the current object. 1013 | * **getRequestedUrl()**: Gets the real URL the current object was requested with (incl. GET parameters). Will equal getRestangularUrl() when no parameters were used, before calling `get()`, or when using on a nested child. 1014 | * **getParentList()**: Gets the parent list to which it belongs (if any) 1015 | * **clone()**: Copies the element. It's an alias to calling `Restangular.copy(elem)`. 1016 | * **plain()**: Returns the plain element received from the server without any of the enhanced methods from Restangular. It's an alias to calling `Restangular.stripRestangular(elem)` 1017 | * **save**: Calling save will determine whether to do PUT or POST accordingly 1018 | 1019 | **[Back to top](#table-of-contents)** 1020 | 1021 | ### Collection methods 1022 | * **getList([queryParams, headers]): Gets itself again (Remember this is a collection)**. 1023 | * **get(id): Gets one item from the collection by id**. 1024 | * **post(elementToPost, [queryParams, headers])**: Creates a new element of this collection. 1025 | * **head([queryParams, headers])**: Does a HEAD 1026 | * **trace: ([queryParams, headers])**: Does a TRACE 1027 | * **options: ([queryParams, headers])**: Does a OPTIONS 1028 | * **patch(object, [queryParams, headers])**: Does a PATCH 1029 | * **remove([queryParams, headers])**: Does a DELETE. By default, `remove` sends a request with an empty object, which may cause problems with some servers or browsers. [This](https://github.com/mgonto/restangular/issues/193) shows how to configure RESTangular to have no payload. 1030 | * **putElement(index, params, headers)**: Puts the element on the required index and returns a observable of the updated new array 1031 | ````js 1032 | Restangular.all('users').getList() 1033 | .subscribe( users => { 1034 | users.putElement(2, {'name': 'new name'}); 1035 | }); 1036 | ```` 1037 | * **getRestangularUrl()**: Gets the URL of the current object. 1038 | * **getRequestedUrl()**: Gets the real URL the current object was requested with (incl. GET parameters). Will equal getRestangularUrl() when no parameters were used, before calling `getList()`, or when using on a nested child. 1039 | * **one(route, id)**: Used for RequestLess connections and URL Building. See section below. 1040 | * **all(route)**: Used for RequestLess connections and URL Building. See section below. 1041 | * **several(route, ids*)**: Used for RequestLess connections and URL Building. See section below. 1042 | * **oneUrl(route, url)**: This will create a new Restangular object that is just a pointer to one element with the specified URL. 1043 | * **allUrl(route, url)**: This creates a Restangular object that is just a pointer to a list at the specified URL. 1044 | * **clone()**: Copies the collection. It's an alias to calling `Restangular.copy(collection)`. 1045 | 1046 | **[Back to top](#table-of-contents)** 1047 | 1048 | ### Custom methods 1049 | * **customGET(path, [params, headers])**: Does a GET to the specific path. Optionally you can set params and headers. 1050 | * **customGETLIST(path, [params, headers])**: Does a GET to the specific path. **In this case, you expect to get an array, not a single element**. Optionally you can set params and headers. 1051 | * **customDELETE(path, [params, headers])**: Does a DELETE to the specific path. Optionally you can set params and headers. 1052 | * **customPOST([elem, path, params, headers])**: Does a POST to the specific path. Optionally you can set params and headers and elem. Elem is the element to post. If it's not set, it's assumed that it's the element itself from which you're calling this function. 1053 | * **customPUT([elem, path, params, headers])**: Does a PUT to the specific path. Optionally you can set params and headers and elem. Elem is the element to post. If it's not set, it's assumed that it's the element itself from which you're calling this function. 1054 | * **customPATCH([elem, path, params, headers])**: Does a PATCH to the specific path. Accepts the same arguments as customPUT. 1055 | * **customOperation(operation, path, [params, headers, elem])**: This does a custom operation to the path that we specify. This method is actually used from all the others in this subsection. Operation can be one of: get, post, put, remove, head, options, patch, trace 1056 | * **addRestangularMethod(name, operation, [path, params, headers, elem])**: This will add a new restangular method to this object with the name `name` to the operation and path specified (or current path otherwise). There's a section on how to do this later. 1057 | 1058 | Let's see an example of this: 1059 | 1060 | ````javascript 1061 | // GET /accounts/123/messages 1062 | Restangular.one("accounts", 123).customGET("messages") 1063 | 1064 | // GET /accounts/messages?param=param2 1065 | Restangular.all("accounts").customGET("messages", {param: "param2"}) 1066 | ```` 1067 | 1068 | **[Back to top](#table-of-contents)** 1069 | 1070 | ## Copying elements 1071 | Before modifying an object, we sometimes want to copy it and then modify the copied object. We can use `Restangular.copy(fromElement)`. 1072 | 1073 | **[Back to top](#table-of-contents)** 1074 | 1075 | ## Using values directly in templates with Observables 1076 | 1077 | If you want to use values directly in templates use `AsyncPipe` 1078 | 1079 | ````js 1080 | this.accounts = this.restangular.all('accounts').getList(); 1081 | ```` 1082 | 1083 | ````html 1084 | 1085 | {{account.fullName}} 1086 | 1087 | ```` 1088 | 1089 | **[Back to top](#table-of-contents)** 1090 | 1091 | ## URL Building 1092 | Sometimes, we have a lot of nested entities (and their IDs), but we just want the last child. In those cases, doing a request for everything to get the last child is overkill. For those cases, I've added the possibility to create URLs using the same API as creating a new Restangular object. This connections are created without making any requests. Let's see how to do this: 1093 | 1094 | ````javascript 1095 | 1096 | var restangularSpaces = Restangular.one("accounts",123).one("buildings", 456).all("spaces"); 1097 | 1098 | // This will do ONE get to /accounts/123/buildings/456/spaces 1099 | restangularSpaces.getList() 1100 | 1101 | // This will do ONE get to /accounts/123/buildings/456/spaces/789 1102 | Restangular.one("accounts", 123).one("buildings", 456).one("spaces", 789).get() 1103 | 1104 | // POST /accounts/123/buildings/456/spaces 1105 | Restangular.one("accounts", 123).one("buildings", 456).all("spaces").post({name: "New Space"}); 1106 | 1107 | // DELETE /accounts/123/buildings/456 1108 | Restangular.one("accounts", 123).one("buildings", 456).remove(); 1109 | ```` 1110 | 1111 | **[Back to top](#table-of-contents)** 1112 | 1113 | ## Creating new Restangular Methods 1114 | 1115 | Let's assume that your API needs some custom methods to work. If that's the case, always calling customGET or customPOST for that method with all parameters is a pain in the ass. That's why every element has a `addRestangularMethod` method. 1116 | 1117 | This can be used together with the hook `addElementTransformer` to do some neat stuff. Let's see an example to learn this: 1118 | 1119 | ````javascript 1120 | // Function for settting the default restangular configuration 1121 | export function RestangularConfigFactory (RestangularProvider) { 1122 | // It will transform all building elements, NOT collections 1123 | RestangularProvider.addElementTransformer('buildings', false, function(building) { 1124 | // This will add a method called evaluate that will do a get to path evaluate with NO default 1125 | // query params and with some default header 1126 | // signature is (name, operation, path, params, headers, elementToPost) 1127 | 1128 | building.addRestangularMethod('evaluate', 'get', 'evaluate', undefined, {'myHeader': 'value'}); 1129 | 1130 | return building; 1131 | }); 1132 | 1133 | RestangularProvider.addElementTransformer('users', true, function(user) { 1134 | // This will add a method called login that will do a POST to the path login 1135 | // signature is (name, operation, path, params, headers, elementToPost) 1136 | 1137 | user.addRestangularMethod('login', 'post', 'login'); 1138 | 1139 | return user; 1140 | }); 1141 | } 1142 | 1143 | // AppModule is the main entry point into Angular2 bootstraping process 1144 | @NgModule({ 1145 | bootstrap: [ AppComponent ], 1146 | imports: [ // import Angular's modules 1147 | RestangularModule.forRoot(RestangularConfigFactory), 1148 | ], 1149 | }) 1150 | 1151 | // Then, later in your code you can do the following: 1152 | 1153 | // GET to /buildings/123/evaluate?myParam=param with headers myHeader: value 1154 | 1155 | // Signature for this "custom created" methods is (params, headers, elem) if it's a safe operation (GET, OPTIONS, etc.) 1156 | // If it's an unsafe operation (POST, PUT, etc.), signature is (elem, params, headers). 1157 | 1158 | // If something is set to any of this variables, the default set in the method creation will be overridden 1159 | // If nothing is set, then the defaults are sent 1160 | Restangular.one('buildings', 123).evaluate({myParam: 'param'}); 1161 | 1162 | // GET to /buildings/123/evaluate?myParam=param with headers myHeader: specialHeaderCase 1163 | 1164 | Restangular.one('buildings', 123).evaluate({myParam: 'param'}, {'myHeader': 'specialHeaderCase'}); 1165 | 1166 | // Here the body of the POST is going to be {key: value} as POST is an unsafe operation 1167 | Restangular.all('users').login({key: value}); 1168 | 1169 | ```` 1170 | 1171 | **[Back to top](#table-of-contents)** 1172 | 1173 | ## Adding Custom Methods to Collections 1174 | 1175 | Create custom methods for your collection using Restangular.extendCollection(). This is an alias for: 1176 | 1177 | ```js 1178 | RestangularProvider.addElementTransformer(route, true, fn); 1179 | ``` 1180 | 1181 | ### Example: 1182 | ```js 1183 | // create methods for your collection 1184 | Restangular.extendCollection('accounts', function(collection) { 1185 | collection.totalAmount = function() { 1186 | // implementation here 1187 | }; 1188 | 1189 | return collection; 1190 | }); 1191 | 1192 | var accounts$ = Restangular.all('accounts').getList(); 1193 | 1194 | accounts$.subscribe( accounts => { 1195 | accounts.totalAmount(); // invoke your custom collection method 1196 | }); 1197 | ``` 1198 | 1199 | **[Back to top](#table-of-contents)** 1200 | 1201 | ## Adding Custom Methods to Models 1202 | 1203 | Create custom methods for your models using Restangular.extendModel(). This is an alias for: 1204 | 1205 | ```js 1206 | RestangularProvider.addElementTransformer(route, false, fn); 1207 | ``` 1208 | 1209 | **[Back to top](#table-of-contents)** 1210 | 1211 | ### Example: 1212 | ```js 1213 | Restangular.extendModel('accounts', function(model) { 1214 | model.prettifyAmount = function() {}; 1215 | return model; 1216 | }); 1217 | 1218 | var account$ = Restangular.one('accounts', 1).get(); 1219 | 1220 | account$.subscribe(function(account) { 1221 | account.prettifyAmount(); // invoke your custom model method 1222 | }); 1223 | ``` 1224 | 1225 | **[Back to top](#table-of-contents)** 1226 | 1227 | # FAQ 1228 | 1229 | #### **How can I handle errors?** 1230 | 1231 | Errors can be checked on the second argument of the subscribe. 1232 | 1233 | ````javascript 1234 | Restangular.all("accounts").getList().subscribe( response => { 1235 | console.log("All ok"); 1236 | }, errorResponse => { 1237 | console.log("Error with status code", errorResponse.status); 1238 | }); 1239 | ```` 1240 | 1241 | #### **I need to send Authorization token in EVERY Restangular request, how can I do this?** 1242 | 1243 | You can use `setDefaultHeaders` or `addFullRequestInterceptor` 1244 | 1245 | ````javascript 1246 | import { NgModule } from '@angular/core'; 1247 | import { AppComponent } from './app.component'; 1248 | import { RestangularModule } from 'ngx-restangular'; 1249 | import { authService } from '../your-services'; 1250 | 1251 | // Function for settting the default restangular configuration 1252 | export function RestangularConfigFactory (RestangularProvider, authService) { 1253 | 1254 | // set static header 1255 | RestangularProvider.setDefaultHeaders({'Authorization': 'Bearer UDXPx-Xko0w4BRKajozCVy20X11MRZs1'}); 1256 | 1257 | // by each request to the server receive a token and update headers with it 1258 | RestangularProvider.addFullRequestInterceptor((element, operation, path, url, headers, params) => { 1259 | let bearerToken = authService.getBearerToken(); 1260 | 1261 | return { 1262 | headers: Object.assign({}, headers, {Authorization: `Bearer ${bearerToken}`}) 1263 | }; 1264 | }); 1265 | } 1266 | 1267 | // AppModule is the main entry point into Angular2 bootstraping process 1268 | @NgModule({ 1269 | bootstrap: [ AppComponent ], 1270 | declarations: [ 1271 | AppComponent, 1272 | ], 1273 | imports: [ 1274 | // Importing RestangularModule and making default configs for restanglar 1275 | RestangularModule.forRoot([authService], RestangularConfigFactory), 1276 | ] 1277 | }) 1278 | export class AppModule { 1279 | } 1280 | ```` 1281 | **[Back to top](#table-of-contents)** 1282 | 1283 | 1284 | #### **I need to send one header in EVERY Restangular request, how can I do this?** 1285 | 1286 | You can use `defaultHeaders` property for this. `defaultsHeaders` can be scoped with `withConfig` so it's really cool. 1287 | 1288 | #### **How can I send a delete WITHOUT a body?** 1289 | 1290 | You must add a requestInterceptor for this. 1291 | 1292 | ````js 1293 | RestangularProvider.setRequestInterceptor(function(elem, operation) { 1294 | if (operation === "remove") { 1295 | return null; 1296 | } 1297 | return elem; 1298 | }) 1299 | ```` 1300 | 1301 | #### **I use Mongo and the ID of the elements is `_id` not `id` as the default. Therefore requests are sent to undefined routes** 1302 | 1303 | What you need to do is to configure the `RestangularFields` and set the `id` field to `_id`. Let's see how: 1304 | 1305 | ````javascript 1306 | RestangularProvider.setRestangularFields({ 1307 | id: "_id" 1308 | }); 1309 | ```` 1310 | 1311 | #### **What if each of my models has a different ID name like CustomerID for Customer** 1312 | 1313 | In some cases, people have different ID name for each entity. For example, they have CustomerID for customer and EquipmentID for Equipment. If that's the case, you can override Restangular's getIdFromElem. For that, you need to do: 1314 | 1315 | ````js 1316 | RestangularProvider.configuration.getIdFromElem = function(elem) { 1317 | // if route is customers ==> returns customerID 1318 | return elem[_.initial(elem.route).join('') + "ID"]; 1319 | } 1320 | ```` 1321 | With that, you'd get what you need :) 1322 | 1323 | #### **How can I send files in my request using Restangular?** 1324 | 1325 | This can be done using the customPOST / customPUT method. Look at the following example: 1326 | ````js 1327 | Restangular.all('users') 1328 | .customPOST(formData, undefined, undefined, { 'Content-Type': undefined }); 1329 | ```` 1330 | This basically tells the request to use the *Content-Type: multipart/form-data* as the header. Also *formData* is the body of the request, be sure to add all the params here, including the File you want to send of course. 1331 | 1332 | #### **How do I handle CRUD operations in a List returned by Restangular?** 1333 | 1334 | ````javascript 1335 | Restangular.all('users').getList().subscribe( users => { 1336 | this.users = users; 1337 | var userWithId = _.find(users, function(user) { 1338 | return user.id === 123; 1339 | }); 1340 | 1341 | userWithId.name = "Gonto"; 1342 | userWithId.put(); 1343 | 1344 | // Alternatively delete the element from the list when finished 1345 | userWithId.remove().subscribe( () => { 1346 | // Updating the list and removing the user after the response is OK. 1347 | this.users = _.without(this.users, userWithId); 1348 | }); 1349 | 1350 | }); 1351 | ```` 1352 | 1353 | #### Removing an element from a collection, keeping the collection restangularized 1354 | 1355 | While the example above removes the deleted user from the collection, it also overwrites the collection object with a plain array (because of `_.without`) which no longer knows about its Restangular attributes. 1356 | 1357 | If want to keep the restangularized collection, remove the element by modifying the collection in place: 1358 | 1359 | ```javascript 1360 | userWithId.remove().subscribe( () => { 1361 | let index = $scope.users.indexOf(userWithId); 1362 | if (index > -1) this.users.splice(index, 1); 1363 | }); 1364 | ``` 1365 | 1366 | #### How can I access the `unrestangularized` element as well as the `restangularized` one? 1367 | 1368 | In order to get this done, you need to use the `responseExtractor`. You need to set a property there that will point to the original response received. Also, you need to actually copy this response as that response is the one that's going to be `restangularized` later 1369 | 1370 | ````javascript 1371 | RestangularProvider.setResponseExtractor( (response) => { 1372 | var newResponse = response; 1373 | if (_.isArray(response)) { 1374 | _.forEach(newResponse, function(value, key) { 1375 | newResponse[key].originalElement = _.clone(value); 1376 | }); 1377 | } else { 1378 | newResponse.originalElement = _.clone(response); 1379 | } 1380 | 1381 | return newResponse; 1382 | }); 1383 | ```` 1384 | Alternatively, if you just want the stripped out response on any given call, you can use the .plain() method, doing something like this: 1385 | 1386 | ````javascript 1387 | 1388 | this.showData = function () { 1389 | baseUrl.post(someData).subscribe( (response) => { 1390 | console.log(response.plain()); 1391 | }); 1392 | }; 1393 | ```` 1394 | 1395 | **[Back to top](#table-of-contents)** 1396 | 1397 | #### How can add withCredentials params to requests? 1398 | ````javascript 1399 | // Function for settting the default restangular configuration 1400 | export function RestangularConfigFactory (RestangularProvider) { 1401 | // Adding withCredential parametr to all Restangular requests 1402 | RestangularProvider.setDefaultHttpFields({ withCredentials: true }); 1403 | } 1404 | 1405 | @NgModule({ 1406 | bootstrap: [ AppComponent ], 1407 | declarations: [ 1408 | AppComponent, 1409 | ], 1410 | imports: [ 1411 | // Global configuration 1412 | RestangularModule.forRoot(RestangularConfigFactory), 1413 | ] 1414 | }) 1415 | export class AppModule {} 1416 | ```` 1417 | 1418 | **[Back to top](#table-of-contents)** 1419 | 1420 | # Server Frameworks 1421 | 1422 | Users reported that this server frameworks play real nice with Ngx-restangular, as they let you create a Nested RESTful Resources API easily: 1423 | 1424 | * Ruby on Rails 1425 | * CakePHP, Laravel and FatFREE, Symfony2 with RestBundle, Silex for PHP 1426 | * Play1 & 2 for Java & scala 1427 | * Dropwizard for Java 1428 | * Restify and Express for NodeJS 1429 | * Tastypie and Django Rest Framework for Django 1430 | * Slim Framework 1431 | * Symfony2 with FOSRestBundle (PHP) 1432 | * Microsoft ASP.NET Web API 2 1433 | * Grails Framework 1434 | 1435 | **[Back to top](#table-of-contents)** 1436 | 1437 | 1438 | # Contributing 1439 | Please read [contributing guidelines here](https://github.com/2muchcoffeecom/ngx-restangular/blob/master/CONTRIBUTING.md). 1440 | 1441 | **[Back to top](#table-of-contents)** 1442 | 1443 | 1444 | # License 1445 | 1446 | The MIT License 1447 | 1448 | **[Back to top](#table-of-contents)** 1449 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-restangular-demo": { 7 | "root": "demo", 8 | "sourceRoot": "demo", 9 | "projectType": "application", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:browser", 13 | "options": { 14 | "outputPath": "dist/demo", 15 | "index": "demo/index.html", 16 | "main": "demo/main.ts", 17 | "tsConfig": "demo/tsconfig.app.json", 18 | "polyfills": "demo/polyfills.ts", 19 | "assets": [ 20 | "demo/assets", 21 | "demo/favicon.ico" 22 | ], 23 | "styles": [ 24 | "demo/styles.css" 25 | ], 26 | "scripts": [] 27 | }, 28 | "configurations": { 29 | "production": { 30 | "optimization": true, 31 | "outputHashing": "all", 32 | "sourceMap": false, 33 | "extractCss": true, 34 | "namedChunks": false, 35 | "aot": true, 36 | "extractLicenses": true, 37 | "vendorChunk": false, 38 | "buildOptimizer": true, 39 | "fileReplacements": [ 40 | { 41 | "replace": "environments/environment.ts", 42 | "with": "environments/environment.prod.ts" 43 | } 44 | ] 45 | } 46 | } 47 | }, 48 | "serve": { 49 | "builder": "@angular-devkit/build-angular:dev-server", 50 | "options": { 51 | "browserTarget": "ngx-restangular-demo:build" 52 | }, 53 | "configurations": { 54 | "production": { 55 | "browserTarget": "ngx-restangular-demo:build:production" 56 | } 57 | } 58 | }, 59 | "lint": { 60 | "builder": "@angular-devkit/build-angular:tslint", 61 | "options": { 62 | "tsConfig": [ 63 | "demo/tsconfig.app.json" 64 | ], 65 | "exclude": [ 66 | "**/node_modules/**" 67 | ] 68 | } 69 | } 70 | } 71 | }, 72 | "ngx-restangular": { 73 | "root": "projects/ngx-restangular", 74 | "sourceRoot": "projects/ngx-restangular/src", 75 | "projectType": "library", 76 | "prefix": "lib", 77 | "architect": { 78 | "build": { 79 | "builder": "@angular-devkit/build-ng-packagr:build", 80 | "options": { 81 | "tsConfig": "projects/ngx-restangular/tsconfig.lib.json", 82 | "project": "projects/ngx-restangular/ng-package.json" 83 | }, 84 | "configurations": { 85 | "production": { 86 | "project": "projects/ngx-restangular/ng-package.prod.json" 87 | } 88 | } 89 | }, 90 | "test": { 91 | "builder": "@angular-devkit/build-angular:karma", 92 | "options": { 93 | "main": "projects/ngx-restangular/src/test.ts", 94 | "tsConfig": "projects/ngx-restangular/tsconfig.spec.json", 95 | "karmaConfig": "projects/ngx-restangular/karma.conf.js" 96 | } 97 | }, 98 | "lint": { 99 | "builder": "@angular-devkit/build-angular:tslint", 100 | "options": { 101 | "tsConfig": [ 102 | "projects/ngx-restangular/tsconfig.lib.json", 103 | "projects/ngx-restangular/tsconfig.spec.json" 104 | ], 105 | "exclude": [ 106 | "**/node_modules/**" 107 | ] 108 | } 109 | } 110 | } 111 | } 112 | }, 113 | "defaultProject": "ngx-restangular-demo", 114 | "schematics": { 115 | "@schematics/angular:component": { 116 | "prefix": "app", 117 | "styleext": "css" 118 | }, 119 | "@schematics/angular:directive": { 120 | "prefix": "app" 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /config/externals.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'lodash', 3 | '@angular/core', 4 | '@angular/common/http', 5 | 'core-js/fn/object', 6 | 'rxjs/BehaviorSubject', 7 | 'rxjs/Observable', 8 | 'rxjs/add/observable/throw', 9 | 'rxjs/add/operator/filter', 10 | 'rxjs/add/operator/map', 11 | 'rxjs/add/operator/catch', 12 | ] 13 | -------------------------------------------------------------------------------- /config/rollup-esm2015.conf.js: -------------------------------------------------------------------------------- 1 | import external from "./externals"; 2 | 3 | export default { 4 | input: 'tmp/esm2015/ngx-restangular.js', 5 | output: { 6 | file: 'dist/esm2015/ngx-restangular.js', 7 | format: 'es', 8 | sourcemap: true 9 | }, 10 | external, 11 | }; 12 | -------------------------------------------------------------------------------- /config/rollup-esm5.conf.js: -------------------------------------------------------------------------------- 1 | import external from './externals'; 2 | 3 | export default { 4 | input: 'tmp/esm5/ngx-restangular.js', 5 | output: { 6 | file: 'dist/esm5/ngx-restangular.js', 7 | format: 'es', 8 | sourcemap: true 9 | }, 10 | external, 11 | }; 12 | -------------------------------------------------------------------------------- /config/rollup-umd.conf.js: -------------------------------------------------------------------------------- 1 | import external from "./externals"; 2 | 3 | export default { 4 | input: 'tmp/esm5/ngx-restangular.js', 5 | output: { 6 | file: 'dist/bundles/ngx-restangular.umd.js', 7 | format: 'umd', 8 | name: 'ngxRestangular', 9 | globals: { 10 | // Angular dependencies 11 | '@angular/core': 'ng.core', 12 | '@angular/common': 'ng.common', 13 | '@angular/common/http': 'ng.common.http', 14 | 'lodash': '_', 15 | 'core-js/fn/object': 'core.Object', 16 | 'rxjs/Observable': 'Rx.Observable', 17 | 'rxjs/BehaviorSubject': 'Rx.BehaviorSubject', 18 | }, 19 | }, 20 | external, 21 | }; 22 | -------------------------------------------------------------------------------- /config/rollup.conf.js: -------------------------------------------------------------------------------- 1 | import esm5 from './rollup-esm5.conf'; 2 | import esm2015 from './rollup-esm2015.conf'; 3 | import umd from './rollup-umd.conf'; 4 | 5 | export default [ 6 | esm5, 7 | esm2015, 8 | umd 9 | ]; 10 | -------------------------------------------------------------------------------- /config/tsconfig-esm2015.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "sourceMap": true, 5 | "target": "es2015", 6 | "module": "es2015", 7 | "baseUrl": "../lib", 8 | "experimentalDecorators": true, 9 | "moduleResolution": "node", 10 | "outDir": "../tmp/esm2015", 11 | "rootDir": "../lib", 12 | "skipLibCheck": true, 13 | "lib": ["dom", "es2015"] 14 | }, 15 | "files": ["../lib/public_api.ts"], 16 | "angularCompilerOptions": { 17 | "annotateForClosureCompiler": true, 18 | "strictMetadataEmit": true, 19 | "skipTemplateCodegen": true, 20 | "flatModuleOutFile": "ngx-restangular.js", 21 | "flatModuleId": "ngx-restangular" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/tsconfig-esm5.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-esm2015.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "outDir": "../tmp/esm5", 6 | "rootDir": "../lib" 7 | }, 8 | "angularCompilerOptions": { 9 | "annotateForClosureCompiler": true, 10 | "strictMetadataEmit": true, 11 | "skipTemplateCodegen": true, 12 | "flatModuleOutFile": "ngx-restangular.js", 13 | "flatModuleId": "ngx-restangular" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2muchcoffeecom/ngx-restangular/6407f48dd20c77d5a077ef5d666a896664db012b/demo/app/app.component.css -------------------------------------------------------------------------------- /demo/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | Welcome to {{ title }}! 5 |

6 | Angular Logo 7 |
8 |

Here are some links to help you start:

9 | 20 | 21 | -------------------------------------------------------------------------------- /demo/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'app'; 10 | } 11 | -------------------------------------------------------------------------------- /demo/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | 5 | import { AppComponent } from './app.component'; 6 | 7 | 8 | @NgModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | imports: [ 13 | BrowserModule 14 | ], 15 | providers: [], 16 | bootstrap: [AppComponent] 17 | }) 18 | export class AppModule { } 19 | -------------------------------------------------------------------------------- /demo/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2muchcoffeecom/ngx-restangular/6407f48dd20c77d5a077ef5d666a896664db012b/demo/assets/.gitkeep -------------------------------------------------------------------------------- /demo/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /demo/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /demo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2muchcoffeecom/ngx-restangular/6407f48dd20c77d5a077ef5d666a896664db012b/demo/favicon.ico -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ngx-restangular Demo App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /demo/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | /** 56 | * By default, zone.js will patch all possible macroTask and DomEvents 57 | * user can disable parts of macroTask/DomEvents patch by setting following flags 58 | */ 59 | 60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 63 | 64 | /* 65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 67 | */ 68 | // (window as any).__Zone_enable_cross_context_check = true; 69 | 70 | /*************************************************************************************************** 71 | * Zone JS is required by default for Angular itself. 72 | */ 73 | import 'zone.js/dist/zone'; // Included with Angular CLI. 74 | 75 | 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | -------------------------------------------------------------------------------- /demo/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [], 8 | "paths": { 9 | "ngx-restangular": [ 10 | "../dist/ngx-restangular" 11 | ] 12 | } 13 | }, 14 | "exclude": [ 15 | "test.ts", 16 | "**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /demo/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-restangular", 3 | "scripts": { 4 | "start": "ng serve", 5 | "build:ngx-restangular": "ng build ngx-restangular" 6 | }, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/2muchcoffeecom/ngx-restangular.git" 10 | }, 11 | "keywords": [ 12 | "ng2-rest", 13 | "ng2-restangular", 14 | "ngx-restangular", 15 | "angular2-restangular", 16 | "angular4-restangular", 17 | "angular5-restangular", 18 | "angular6-restangular", 19 | "angular7-restangular", 20 | "angular8-restangular", 21 | "angular9-restangular", 22 | "angular10-restangular", 23 | "angular2-rest", 24 | "angular4-rest", 25 | "angular5-rest", 26 | "angular6-rest", 27 | "angular7-rest", 28 | "angular8-rest", 29 | "angular9-rest", 30 | "angular10-rest", 31 | "restangular", 32 | "angular 2 restangular", 33 | "angular 4 restangular", 34 | "angular 5 restangular", 35 | "angular 6 restangular", 36 | "angular 7 restangular", 37 | "angular 8 restangular", 38 | "angular 9 restangular", 39 | "angular 10 restangular", 40 | "angular 2 rest", 41 | "angular 4 rest", 42 | "angular 5 rest", 43 | "angular 6 rest", 44 | "angular 7 rest", 45 | "angular 8 rest", 46 | "angular 9 rest", 47 | "angular 10 rest" 48 | ], 49 | "author": "logvinoleg89 ", 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/2muchcoffeecom/ngx-restangular/issues" 53 | }, 54 | "homepage": "https://github.com/2muchcoffeecom/ngx-restangular#readme", 55 | "devDependencies": { 56 | "@angular-devkit/build-angular": "~0.1000.5", 57 | "@angular-devkit/build-ng-packagr": "~0.1000.5", 58 | "@angular/cli": "^10.0.0", 59 | "@angular/common": "^10.0.0", 60 | "@angular/compiler": "^10.0.0", 61 | "@angular/compiler-cli": "^10.0.0", 62 | "@angular/core": "^10.0.0", 63 | "@angular/forms": "^10.0.0", 64 | "@angular/platform-browser": "^10.0.0", 65 | "@angular/platform-browser-dynamic": "^10.0.0", 66 | "@angular/router": "^10.0.0", 67 | "@types/jasmine": "~3.5.0", 68 | "@types/lodash": "^4.14.62", 69 | "codelyzer": "^6.0.0", 70 | "ng-packagr": "~10.0.0", 71 | "rxjs": "~6.5.5", 72 | "tsickle": "^0.39.1", 73 | "tslib": "^2.0.0", 74 | "tslint": "~6.1.0", 75 | "typescript": "~3.9.5", 76 | "zone.js": "~0.10.0", 77 | "core-js": "^3.5.0" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /projects/ngx-restangular/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/ngx-restangular/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-restangular", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | }, 7 | "whitelistedNonPeerDependencies": ["@types/lodash"] 8 | } 9 | -------------------------------------------------------------------------------- /projects/ngx-restangular/ng-package.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/lib", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | }, 7 | "whitelistedNonPeerDependencies": ["@types/lodash"] 8 | } 9 | -------------------------------------------------------------------------------- /projects/ngx-restangular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-restangular", 3 | "version": "6.0.0", 4 | "description": "ngx-restangular", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/2muchcoffeecom/ngx-restangular.git" 8 | }, 9 | "keywords": [ 10 | "ng2-rest", 11 | "ng2-restangular", 12 | "ngx-restangular", 13 | "angular2-restangular", 14 | "angular4-restangular", 15 | "angular5-restangular", 16 | "angular6-restangular", 17 | "angular7-restangular", 18 | "angular8-restangular", 19 | "angular9-restangular", 20 | "angular10-restangular", 21 | "angular2-rest", 22 | "angular4-rest", 23 | "angular5-rest", 24 | "angular6-rest", 25 | "angular7-rest", 26 | "angular8-rest", 27 | "angular9-rest", 28 | "angular10-rest", 29 | "restangular", 30 | "angular 2 restangular", 31 | "angular 4 restangular", 32 | "angular 5 restangular", 33 | "angular 6 restangular", 34 | "angular 7 restangular", 35 | "angular 8 restangular", 36 | "angular 9 restangular", 37 | "angular 10 restangular", 38 | "angular 2 rest", 39 | "angular 4 rest", 40 | "angular 5 rest", 41 | "angular 6 rest", 42 | "angular 7 rest", 43 | "angular 8 rest", 44 | "angular 9 rest", 45 | "angular 10 rest" 46 | ], 47 | "author": "logvinoleg89 ", 48 | "license": "MIT", 49 | "bugs": { 50 | "url": "https://github.com/2muchcoffeecom/ngx-restangular/issues" 51 | }, 52 | "homepage": "https://github.com/2muchcoffeecom/ngx-restangular#readme", 53 | "peerDependencies": { 54 | "@angular/core": "^10.0.0", 55 | "@angular/common": "^10.0.0", 56 | "@angular/platform-browser": "^10.0.0", 57 | "core-js": "^3.5.0", 58 | "rxjs": "^6.0" 59 | }, 60 | "dependencies": { 61 | "@types/lodash": "^4.14.62" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { RestangularModule } from './ngx-restangular.module'; 2 | export { Restangular } from './ngx-restangular'; 3 | export { RestangularHttp } from './ngx-restangular-http'; 4 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/lib/ngx-restangular-config.factory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | includes, 3 | isUndefined, 4 | isNull, 5 | isArray, 6 | isObject, 7 | isBoolean, 8 | defaults, 9 | each, 10 | extend, 11 | find, 12 | has, 13 | initial, 14 | last, 15 | clone, 16 | reduce, 17 | keys, 18 | isEmpty, 19 | forEach, 20 | } from 'lodash'; 21 | 22 | export function RestangularConfigurer(object, configuration) { 23 | object.configuration = configuration; 24 | 25 | /** 26 | * Those are HTTP safe methods for which there is no need to pass any data with the request. 27 | */ 28 | const safeMethods = ['get', 'head', 'options', 'trace', 'getlist']; 29 | configuration.isSafe = function (operation) { 30 | return includes(safeMethods, operation.toLowerCase()); 31 | }; 32 | 33 | const absolutePattern = /^https?:\/\//i; 34 | configuration.isAbsoluteUrl = function (string) { 35 | return isUndefined(configuration.absoluteUrl) || isNull(configuration.absoluteUrl) ? 36 | string && absolutePattern.test(string) : 37 | configuration.absoluteUrl; 38 | }; 39 | 40 | configuration.absoluteUrl = isUndefined(configuration.absoluteUrl) ? true : configuration.absoluteUrl; 41 | object.setSelfLinkAbsoluteUrl = function (value) { 42 | configuration.absoluteUrl = value; 43 | }; 44 | /** 45 | * This is the BaseURL to be used with Restangular 46 | */ 47 | configuration.baseUrl = isUndefined(configuration.baseUrl) ? '' : configuration.baseUrl; 48 | object.setBaseUrl = function (newBaseUrl) { 49 | configuration.baseUrl = /\/$/.test(newBaseUrl) ? 50 | newBaseUrl.substring(0, newBaseUrl.length - 1) : 51 | newBaseUrl; 52 | return this; 53 | }; 54 | 55 | /** 56 | * Sets the extra fields to keep from the parents 57 | */ 58 | configuration.extraFields = configuration.extraFields || []; 59 | object.setExtraFields = function (newExtraFields) { 60 | configuration.extraFields = newExtraFields; 61 | return this; 62 | }; 63 | 64 | /** 65 | * Some default $http parameter to be used in EVERY call 66 | **/ 67 | configuration.defaultHttpFields = configuration.defaultHttpFields || {}; 68 | object.setDefaultHttpFields = function (values) { 69 | configuration.defaultHttpFields = values; 70 | return this; 71 | }; 72 | 73 | /** 74 | * Always return plain data, no restangularized object 75 | **/ 76 | configuration.plainByDefault = configuration.plainByDefault || false; 77 | object.setPlainByDefault = function (value) { 78 | configuration.plainByDefault = value === true ? true : false; 79 | return this; 80 | }; 81 | 82 | configuration.withHttpValues = function (httpLocalConfig, obj) { 83 | return defaults(obj, httpLocalConfig, configuration.defaultHttpFields); 84 | }; 85 | 86 | configuration.encodeIds = isUndefined(configuration.encodeIds) ? true : configuration.encodeIds; 87 | object.setEncodeIds = function (encode) { 88 | configuration.encodeIds = encode; 89 | }; 90 | 91 | configuration.defaultRequestParams = configuration.defaultRequestParams || { 92 | get: {}, 93 | post: {}, 94 | put: {}, 95 | remove: {}, 96 | common: {} 97 | }; 98 | 99 | object.setDefaultRequestParams = function (param1, param2) { 100 | let methods = []; 101 | const params = param2 || param1; 102 | if (!isUndefined(param2)) { 103 | if (isArray(param1)) { 104 | methods = param1; 105 | } else { 106 | methods.push(param1); 107 | } 108 | } else { 109 | methods.push('common'); 110 | } 111 | 112 | each(methods, function (method) { 113 | configuration.defaultRequestParams[method] = params; 114 | }); 115 | return this; 116 | }; 117 | 118 | object.requestParams = configuration.defaultRequestParams; 119 | 120 | configuration.defaultHeaders = configuration.defaultHeaders || {}; 121 | object.setDefaultHeaders = function (headers) { 122 | configuration.defaultHeaders = headers; 123 | object.defaultHeaders = configuration.defaultHeaders; 124 | return this; 125 | }; 126 | 127 | object.defaultHeaders = configuration.defaultHeaders; 128 | 129 | 130 | /** 131 | * Method overriders response Method 132 | **/ 133 | configuration.defaultResponseMethod = configuration.defaultResponseMethod || 'promise'; 134 | object.setDefaultResponseMethod = function (method) { 135 | configuration.defaultResponseMethod = method; 136 | object.defaultResponseMethod = configuration.defaultResponseMethod; 137 | return this; 138 | }; 139 | object.defaultResponseMethod = configuration.defaultResponseMethod; 140 | 141 | /** 142 | * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override 143 | **/ 144 | configuration.methodOverriders = configuration.methodOverriders || []; 145 | object.setMethodOverriders = function (values) { 146 | const overriders = extend([], values); 147 | if (configuration.isOverridenMethod('delete', overriders)) { 148 | overriders.push('remove'); 149 | } 150 | configuration.methodOverriders = overriders; 151 | return this; 152 | }; 153 | 154 | configuration.jsonp = isUndefined(configuration.jsonp) ? false : configuration.jsonp; 155 | object.setJsonp = function (active) { 156 | configuration.jsonp = active; 157 | }; 158 | 159 | configuration.isOverridenMethod = function (method, values) { 160 | const search = values || configuration.methodOverriders; 161 | return !isUndefined(find(search, function (one: string) { 162 | return one.toLowerCase() === method.toLowerCase(); 163 | })); 164 | }; 165 | 166 | /** 167 | * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams 168 | **/ 169 | configuration.urlCreator = configuration.urlCreator || 'path'; 170 | object.setUrlCreator = function (name) { 171 | if (!has(configuration.urlCreatorFactory, name)) { 172 | throw new Error('URL Path selected isn\'t valid'); 173 | } 174 | 175 | configuration.urlCreator = name; 176 | return this; 177 | }; 178 | 179 | /** 180 | * You can set the restangular fields here. The 3 required fields for Restangular are: 181 | * 182 | * id: Id of the element 183 | * route: name of the route of this element 184 | * parentResource: the reference to the parent resource 185 | * 186 | * All of this fields except for id, are handled (and created) by Restangular. By default, 187 | * the field values will be id, route and parentResource respectively 188 | */ 189 | configuration.restangularFields = configuration.restangularFields || { 190 | id: 'id', 191 | route: 'route', 192 | parentResource: 'parentResource', 193 | restangularCollection: 'restangularCollection', 194 | cannonicalId: '__cannonicalId', 195 | etag: 'restangularEtag', 196 | selfLink: 'href', 197 | get: 'get', 198 | getList: 'getList', 199 | put: 'put', 200 | post: 'post', 201 | remove: 'remove', 202 | head: 'head', 203 | trace: 'trace', 204 | options: 'options', 205 | patch: 'patch', 206 | getRestangularUrl: 'getRestangularUrl', 207 | getRequestedUrl: 'getRequestedUrl', 208 | putElement: 'putElement', 209 | addRestangularMethod: 'addRestangularMethod', 210 | getParentList: 'getParentList', 211 | clone: 'clone', 212 | ids: 'ids', 213 | httpConfig: '_$httpConfig', 214 | reqParams: 'reqParams', 215 | one: 'one', 216 | all: 'all', 217 | several: 'several', 218 | oneUrl: 'oneUrl', 219 | allUrl: 'allUrl', 220 | customPUT: 'customPUT', 221 | customPATCH: 'customPATCH', 222 | customPOST: 'customPOST', 223 | customDELETE: 'customDELETE', 224 | customGET: 'customGET', 225 | customGETLIST: 'customGETLIST', 226 | customOperation: 'customOperation', 227 | doPUT: 'doPUT', 228 | doPATCH: 'doPATCH', 229 | doPOST: 'doPOST', 230 | doDELETE: 'doDELETE', 231 | doGET: 'doGET', 232 | doGETLIST: 'doGETLIST', 233 | fromServer: 'fromServer', 234 | withConfig: 'withConfig', 235 | withHttpConfig: 'withHttpConfig', 236 | singleOne: 'singleOne', 237 | plain: 'plain', 238 | save: 'save', 239 | restangularized: 'restangularized' 240 | }; 241 | object.setRestangularFields = function (resFields) { 242 | configuration.restangularFields = 243 | extend({}, configuration.restangularFields, resFields); 244 | return this; 245 | }; 246 | 247 | configuration.isRestangularized = function (obj) { 248 | return !!obj[configuration.restangularFields.restangularized]; 249 | }; 250 | 251 | configuration.setFieldToElem = function (field, elem, value) { 252 | const properties = field.split('.'); 253 | let idValue = elem; 254 | each(initial(properties), function (prop: any) { 255 | idValue[prop] = {}; 256 | idValue = idValue[prop]; 257 | }); 258 | const index: any = last(properties); 259 | idValue[index] = value; 260 | return this; 261 | }; 262 | 263 | configuration.getFieldFromElem = function (field, elem) { 264 | const properties = field.split('.'); 265 | let idValue: any = elem; 266 | each(properties, function (prop) { 267 | if (idValue) { 268 | idValue = idValue[prop]; 269 | } 270 | }); 271 | return clone(idValue); 272 | }; 273 | 274 | configuration.setIdToElem = function (elem, id /*, route */) { 275 | configuration.setFieldToElem(configuration.restangularFields.id, elem, id); 276 | return this; 277 | }; 278 | 279 | configuration.getIdFromElem = function (elem) { 280 | return configuration.getFieldFromElem(configuration.restangularFields.id, elem); 281 | }; 282 | 283 | configuration.isValidId = function (elemId) { 284 | return '' !== elemId && !isUndefined(elemId) && !isNull(elemId); 285 | }; 286 | 287 | configuration.setUrlToElem = function (elem, url /*, route */) { 288 | configuration.setFieldToElem(configuration.restangularFields.selfLink, elem, url); 289 | return this; 290 | }; 291 | 292 | configuration.getUrlFromElem = function (elem) { 293 | return configuration.getFieldFromElem(configuration.restangularFields.selfLink, elem); 294 | }; 295 | 296 | configuration.useCannonicalId = isUndefined(configuration.useCannonicalId) ? false : configuration.useCannonicalId; 297 | object.setUseCannonicalId = function (value) { 298 | configuration.useCannonicalId = value; 299 | return this; 300 | }; 301 | 302 | configuration.getCannonicalIdFromElem = function (elem) { 303 | const cannonicalId = elem[configuration.restangularFields.cannonicalId]; 304 | const actualId = configuration.isValidId(cannonicalId) ? cannonicalId : configuration.getIdFromElem(elem); 305 | return actualId; 306 | }; 307 | 308 | /** 309 | * Sets the Response parser. This is used in case your response isn't directly the data. 310 | * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}} 311 | * you can extract this data which is the one that needs wrapping 312 | * 313 | * The ResponseExtractor is a function that receives the response and the method executed. 314 | */ 315 | 316 | configuration.responseInterceptors = configuration.responseInterceptors ? [...configuration.responseInterceptors] : []; 317 | 318 | configuration.defaultResponseInterceptor = function (data /*, operation, what, url, response, subject */) { 319 | return data || {}; 320 | }; 321 | 322 | configuration.responseExtractor = function (data, operation, what, url, response, subject) { 323 | const interceptors = clone(configuration.responseInterceptors); 324 | interceptors.push(configuration.defaultResponseInterceptor); 325 | let theData = data; 326 | each(interceptors, function (interceptor: any) { 327 | theData = interceptor(theData, operation, 328 | what, url, response, subject); 329 | }); 330 | return theData; 331 | }; 332 | 333 | object.addResponseInterceptor = function (extractor) { 334 | configuration.responseInterceptors.push(extractor); 335 | return this; 336 | }; 337 | 338 | configuration.errorInterceptors = configuration.errorInterceptors ? [...configuration.errorInterceptors] : []; 339 | object.addErrorInterceptor = function (interceptor) { 340 | configuration.errorInterceptors = [interceptor, ...configuration.errorInterceptors]; 341 | return this; 342 | }; 343 | 344 | object.setResponseInterceptor = object.addResponseInterceptor; 345 | object.setResponseExtractor = object.addResponseInterceptor; 346 | object.setErrorInterceptor = object.addErrorInterceptor; 347 | 348 | /** 349 | * Response interceptor is called just before resolving promises. 350 | */ 351 | 352 | 353 | /** 354 | * Request interceptor is called before sending an object to the server. 355 | */ 356 | configuration.requestInterceptors = configuration.requestInterceptors ? [...configuration.requestInterceptors] : []; 357 | 358 | configuration.defaultInterceptor = function (element, operation, path, url, headers, params, httpConfig) { 359 | return { 360 | element: element, 361 | headers: headers, 362 | params: params, 363 | httpConfig: httpConfig 364 | }; 365 | }; 366 | 367 | configuration.fullRequestInterceptor = function (element, operation, path, url, headers, params, httpConfig) { 368 | const interceptors = clone(configuration.requestInterceptors); 369 | const defaultRequest = configuration.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); 370 | return reduce(interceptors, function (request: any, interceptor: any) { 371 | 372 | const returnInterceptor: any = interceptor( 373 | request.element, 374 | operation, 375 | path, 376 | url, 377 | request.headers, 378 | request.params, 379 | request.httpConfig 380 | ); 381 | return extend(request, returnInterceptor); 382 | }, defaultRequest); 383 | }; 384 | 385 | object.addRequestInterceptor = function (interceptor) { 386 | configuration.requestInterceptors.push(function (elem, operation, path, url, headers, params, httpConfig) { 387 | return { 388 | headers: headers, 389 | params: params, 390 | element: interceptor(elem, operation, path, url), 391 | httpConfig: httpConfig 392 | }; 393 | }); 394 | return this; 395 | }; 396 | 397 | object.setRequestInterceptor = object.addRequestInterceptor; 398 | 399 | object.addFullRequestInterceptor = function (interceptor) { 400 | configuration.requestInterceptors.push(interceptor); 401 | return this; 402 | }; 403 | 404 | object.setFullRequestInterceptor = object.addFullRequestInterceptor; 405 | 406 | configuration.onBeforeElemRestangularized = configuration.onBeforeElemRestangularized || function (elem) { 407 | return elem; 408 | }; 409 | object.setOnBeforeElemRestangularized = function (post) { 410 | configuration.onBeforeElemRestangularized = post; 411 | return this; 412 | }; 413 | 414 | object.setRestangularizePromiseInterceptor = function (interceptor) { 415 | configuration.restangularizePromiseInterceptor = interceptor; 416 | return this; 417 | }; 418 | 419 | /** 420 | * This method is called after an element has been "Restangularized". 421 | * 422 | * It receives the element, a boolean indicating if it's an element or a collection 423 | * and the name of the model 424 | * 425 | */ 426 | configuration.onElemRestangularized = configuration.onElemRestangularized || function (elem) { 427 | return elem; 428 | }; 429 | object.setOnElemRestangularized = function (post) { 430 | configuration.onElemRestangularized = post; 431 | return this; 432 | }; 433 | 434 | configuration.shouldSaveParent = configuration.shouldSaveParent || function () { 435 | return true; 436 | }; 437 | object.setParentless = function (values) { 438 | if (isArray(values)) { 439 | configuration.shouldSaveParent = function (route) { 440 | return !includes(values, route); 441 | }; 442 | } else if (isBoolean(values)) { 443 | configuration.shouldSaveParent = function () { 444 | return !values; 445 | }; 446 | } 447 | return this; 448 | }; 449 | 450 | /** 451 | * This lets you set a suffix to every request. 452 | * 453 | * For example, if your api requires that for JSon requests you do /users/123.json, you can set that 454 | * in here. 455 | * 456 | * 457 | * By default, the suffix is null 458 | */ 459 | configuration.suffix = isUndefined(configuration.suffix) ? null : configuration.suffix; 460 | object.setRequestSuffix = function (newSuffix) { 461 | configuration.suffix = newSuffix; 462 | return this; 463 | }; 464 | 465 | /** 466 | * Add element transformers for certain routes. 467 | */ 468 | configuration.transformers = configuration.transformers || {}; 469 | object.addElementTransformer = function (type, secondArg, thirdArg) { 470 | let isCollection = null; 471 | let transformer = null; 472 | if (arguments.length === 2) { 473 | transformer = secondArg; 474 | } else { 475 | transformer = thirdArg; 476 | isCollection = secondArg; 477 | } 478 | 479 | let typeTransformers = configuration.transformers[type]; 480 | if (!typeTransformers) { 481 | typeTransformers = configuration.transformers[type] = []; 482 | } 483 | 484 | typeTransformers.push(function (coll, elem) { 485 | if (isNull(isCollection) || (coll === isCollection)) { 486 | return transformer(elem); 487 | } 488 | return elem; 489 | }); 490 | 491 | return object; 492 | }; 493 | 494 | object.extendCollection = function (route, fn) { 495 | return object.addElementTransformer(route, true, fn); 496 | }; 497 | 498 | object.extendModel = function (route, fn) { 499 | return object.addElementTransformer(route, false, fn); 500 | }; 501 | 502 | configuration.transformElem = function (elem, isCollection, route, Restangular, force) { 503 | if (!force && !configuration.transformLocalElements && !elem[configuration.restangularFields.fromServer]) { 504 | return elem; 505 | } 506 | const typeTransformers = configuration.transformers[route]; 507 | let changedElem = elem; 508 | if (typeTransformers) { 509 | each(typeTransformers, function (transformer: (isCollection: boolean, changedElem: any) => any) { 510 | changedElem = transformer(isCollection, changedElem); 511 | }); 512 | } 513 | return configuration.onElemRestangularized(changedElem, isCollection, route, Restangular); 514 | }; 515 | 516 | configuration.transformLocalElements = isUndefined(configuration.transformLocalElements) ? 517 | false : 518 | configuration.transformLocalElements; 519 | 520 | object.setTransformOnlyServerElements = function (active) { 521 | configuration.transformLocalElements = !active; 522 | }; 523 | 524 | configuration.fullResponse = isUndefined(configuration.fullResponse) ? false : configuration.fullResponse; 525 | object.setFullResponse = function (full) { 526 | configuration.fullResponse = full; 527 | return this; 528 | }; 529 | 530 | 531 | // Internal values and functions 532 | configuration.urlCreatorFactory = {}; 533 | 534 | /** 535 | * Base URL Creator. Base prototype for everything related to it 536 | **/ 537 | 538 | const BaseCreator = function () { 539 | }; 540 | 541 | BaseCreator.prototype.setConfig = function (config) { 542 | this.config = config; 543 | return this; 544 | }; 545 | 546 | BaseCreator.prototype.parentsArray = function (current) { 547 | const parents = []; 548 | while (current) { 549 | parents.push(current); 550 | current = current[this.config.restangularFields.parentResource]; 551 | } 552 | return parents.reverse(); 553 | }; 554 | 555 | function RestangularResource(config, $http, url, configurer) { 556 | const resource = {}; 557 | each(keys(configurer), function (key) { 558 | const value = configurer[key]; 559 | 560 | // Add default parameters 561 | value.params = extend({}, value.params, config.defaultRequestParams[value.method.toLowerCase()]); 562 | // We don't want the ? if no params are there 563 | if (isEmpty(value.params)) { 564 | delete value.params; 565 | } 566 | 567 | if (config.isSafe(value.method)) { 568 | 569 | resource[key] = function () { 570 | const resultConfig = extend(value, { 571 | url: url 572 | }); 573 | return $http.createRequest(resultConfig); 574 | }; 575 | 576 | } else { 577 | 578 | resource[key] = function (data) { 579 | const resultConfig = extend(value, { 580 | url: url, 581 | data: data 582 | }); 583 | return $http.createRequest(resultConfig); 584 | }; 585 | 586 | } 587 | }); 588 | 589 | return resource; 590 | } 591 | 592 | BaseCreator.prototype.resource = function (current, $http, localHttpConfig, callHeaders, callParams, what, etag, operation) { 593 | const params = defaults(callParams || {}, this.config.defaultRequestParams.common); 594 | const headers = defaults(callHeaders || {}, this.config.defaultHeaders); 595 | 596 | if (etag) { 597 | if (!configuration.isSafe(operation)) { 598 | headers['If-Match'] = etag; 599 | } else { 600 | headers['If-None-Match'] = etag; 601 | } 602 | } 603 | 604 | let url = this.base(current); 605 | 606 | if (what) { 607 | let add = ''; 608 | if (!/\/$/.test(url)) { 609 | add += '/'; 610 | } 611 | add += what; 612 | url += add; 613 | } 614 | 615 | if (this.config.suffix && 616 | url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1 && !this.config.getUrlFromElem(current)) { 617 | url += this.config.suffix; 618 | } 619 | 620 | current[this.config.restangularFields.httpConfig] = undefined; 621 | 622 | return RestangularResource(this.config, $http, url, { 623 | getList: this.config.withHttpValues(localHttpConfig, 624 | { 625 | method: 'GET', 626 | params: params, 627 | headers: headers 628 | }), 629 | 630 | get: this.config.withHttpValues(localHttpConfig, 631 | { 632 | method: 'GET', 633 | params: params, 634 | headers: headers 635 | }), 636 | 637 | jsonp: this.config.withHttpValues(localHttpConfig, 638 | { 639 | method: 'jsonp', 640 | params: params, 641 | headers: headers 642 | }), 643 | 644 | put: this.config.withHttpValues(localHttpConfig, 645 | { 646 | method: 'PUT', 647 | params: params, 648 | headers: headers 649 | }), 650 | 651 | post: this.config.withHttpValues(localHttpConfig, 652 | { 653 | method: 'POST', 654 | params: params, 655 | headers: headers 656 | }), 657 | 658 | remove: this.config.withHttpValues(localHttpConfig, 659 | { 660 | method: 'DELETE', 661 | params: params, 662 | headers: headers 663 | }), 664 | 665 | head: this.config.withHttpValues(localHttpConfig, 666 | { 667 | method: 'HEAD', 668 | params: params, 669 | headers: headers 670 | }), 671 | 672 | trace: this.config.withHttpValues(localHttpConfig, 673 | { 674 | method: 'TRACE', 675 | params: params, 676 | headers: headers 677 | }), 678 | 679 | options: this.config.withHttpValues(localHttpConfig, 680 | { 681 | method: 'OPTIONS', 682 | params: params, 683 | headers: headers 684 | }), 685 | 686 | patch: this.config.withHttpValues(localHttpConfig, 687 | { 688 | method: 'PATCH', 689 | params: params, 690 | headers: headers 691 | }) 692 | }); 693 | }; 694 | 695 | /** 696 | * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API. 697 | * This means that if you have an Account that then has a set of Buildings, a URL to a building 698 | * would be /accounts/123/buildings/456 699 | **/ 700 | const Path = function () { 701 | }; 702 | 703 | Path.prototype = new BaseCreator(); 704 | 705 | Path.prototype.normalizeUrl = function (url) { 706 | const parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); 707 | parts[2] = parts[2].replace(/[\\\/]+/g, '/'); 708 | return (typeof parts[1] !== 'undefined') ? parts[1] + parts[2] : parts[2]; 709 | }; 710 | 711 | Path.prototype.base = function (current) { 712 | const __this = this; 713 | return reduce(this.parentsArray(current), function (acum: any, elem: any) { 714 | let elemUrl; 715 | const elemSelfLink = __this.config.getUrlFromElem(elem); 716 | if (elemSelfLink) { 717 | if (__this.config.isAbsoluteUrl(elemSelfLink)) { 718 | return elemSelfLink; 719 | } else { 720 | elemUrl = elemSelfLink; 721 | } 722 | } else { 723 | elemUrl = elem[__this.config.restangularFields.route]; 724 | 725 | if (elem[__this.config.restangularFields.restangularCollection]) { 726 | const ids = elem[__this.config.restangularFields.ids]; 727 | if (ids) { 728 | elemUrl += '/' + ids.join(','); 729 | } 730 | } else { 731 | let elemId: any; 732 | if (__this.config.useCannonicalId) { 733 | elemId = __this.config.getCannonicalIdFromElem(elem); 734 | } else { 735 | elemId = __this.config.getIdFromElem(elem); 736 | } 737 | 738 | if (configuration.isValidId(elemId) && !elem.singleOne) { 739 | elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); 740 | } 741 | } 742 | } 743 | acum = acum.replace(/\/$/, '') + '/' + elemUrl; 744 | return __this.normalizeUrl(acum); 745 | 746 | }, this.config.baseUrl); 747 | }; 748 | 749 | 750 | Path.prototype.fetchUrl = function (current, what) { 751 | let baseUrl = this.base(current); 752 | if (what) { 753 | baseUrl += '/' + what; 754 | } 755 | return baseUrl; 756 | }; 757 | 758 | Path.prototype.fetchRequestedUrl = function (current, what) { 759 | const url = this.fetchUrl(current, what); 760 | const params = current[configuration.restangularFields.reqParams]; 761 | 762 | // From here on and until the end of fetchRequestedUrl, 763 | // the code has been kindly borrowed from angular.js 764 | // The reason for such code bloating is coherence: 765 | // If the user were to use this for cache management, the 766 | // serialization of parameters would need to be identical 767 | // to the one done by angular for cache keys to match. 768 | function sortedKeys(obj) { 769 | const resultKeys = []; 770 | for (const key in obj) { 771 | if (obj.hasOwnProperty(key)) { 772 | resultKeys.push(key); 773 | } 774 | } 775 | return resultKeys.sort(); 776 | } 777 | 778 | function forEachSorted(obj, iterator?, context?) { 779 | const sortedKeysArray = sortedKeys(obj); 780 | for (let i = 0; i < sortedKeysArray.length; i++) { 781 | iterator.call(context, obj[sortedKeysArray[i]], sortedKeysArray[i]); 782 | } 783 | return sortedKeysArray; 784 | } 785 | 786 | function encodeUriQuery(val, pctEncodeSpaces?) { 787 | return encodeURIComponent(val) 788 | .replace(/%40/gi, '@') 789 | .replace(/%3A/gi, ':') 790 | .replace(/%24/g, '$') 791 | .replace(/%2C/gi, ',') 792 | .replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 793 | } 794 | 795 | if (!params) { 796 | return url + (this.config.suffix || ''); 797 | } 798 | 799 | const parts = []; 800 | forEachSorted(params, function (value, key) { 801 | if (value === null || value === undefined) { 802 | return; 803 | } 804 | if (!isArray(value)) { 805 | value = [value]; 806 | } 807 | 808 | forEach(value, function (v) { 809 | if (isObject(v)) { 810 | v = JSON.stringify(v); 811 | } 812 | parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); 813 | }); 814 | }); 815 | 816 | return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); 817 | }; 818 | 819 | configuration.urlCreatorFactory.path = Path; 820 | } 821 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/lib/ngx-restangular-helper.ts: -------------------------------------------------------------------------------- 1 | import { HttpRequest, HttpHeaders, HttpParams } from '@angular/common/http'; 2 | 3 | import { assign } from 'core-js/features/object'; 4 | 5 | export class RestangularHelper { 6 | 7 | static createRequest(options) { 8 | const requestQueryParams = RestangularHelper.createRequestQueryParams(options.params); 9 | const requestHeaders = RestangularHelper.createRequestHeaders(options.headers); 10 | const methodName = options.method.toUpperCase(); 11 | const withCredentials = options.withCredentials || false; 12 | 13 | let request = new HttpRequest( 14 | methodName, 15 | options.url, 16 | options.data, 17 | { 18 | headers: requestHeaders, 19 | params: requestQueryParams, 20 | responseType: options.responseType, 21 | withCredentials 22 | } 23 | ); 24 | 25 | if (['GET', 'DELETE', 'HEAD', 'JSONP', 'OPTIONS'].indexOf(methodName) >= 0) { 26 | request = new HttpRequest( 27 | methodName, 28 | options.url, 29 | { 30 | headers: requestHeaders, 31 | params: requestQueryParams, 32 | responseType: options.responseType, 33 | withCredentials 34 | } 35 | ); 36 | } 37 | return request; 38 | } 39 | 40 | static createRequestQueryParams(queryParams) { 41 | const requestQueryParams = assign({}, queryParams); 42 | let search: HttpParams = new HttpParams(); 43 | 44 | for (const key in requestQueryParams) { 45 | let value: any = requestQueryParams[key]; 46 | 47 | if (Array.isArray(value)) { 48 | value.forEach(function (val) { 49 | search = search.append(key, val); 50 | }); 51 | } else { 52 | if (typeof value === 'object') { 53 | value = JSON.stringify(value); 54 | } 55 | search = search.append(key, value); 56 | } 57 | } 58 | 59 | return search; 60 | } 61 | 62 | static createRequestHeaders(headers) { 63 | for (const key in headers) { 64 | const value: any = headers[key]; 65 | if (typeof value === 'undefined') { 66 | delete headers[key]; 67 | } 68 | } 69 | 70 | return new HttpHeaders(assign({}, headers)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/lib/ngx-restangular-http.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpBackend, HttpErrorResponse, HttpEvent, HttpRequest, HttpResponse } from '@angular/common/http'; 3 | 4 | import { throwError, Observable } from 'rxjs'; 5 | 6 | import { RestangularHelper } from './ngx-restangular-helper'; 7 | import { catchError, filter, map } from 'rxjs/operators'; 8 | 9 | @Injectable() 10 | export class RestangularHttp { 11 | 12 | constructor(public http: HttpBackend) { 13 | } 14 | 15 | createRequest(options): Observable> { 16 | const request = RestangularHelper.createRequest(options); 17 | 18 | return this.request(request); 19 | } 20 | 21 | request(request: HttpRequest): Observable> { 22 | return this.http.handle(request) 23 | .pipe( 24 | filter(event => event instanceof HttpResponse), 25 | map((response: any) => { 26 | if (!response.ok) { 27 | return throwError(new HttpErrorResponse(response)); 28 | } 29 | return response; 30 | }), 31 | map(response => { 32 | response.config = {params: request}; 33 | return response; 34 | }), 35 | catchError(err => { 36 | err.request = request; 37 | err.data = err.error; 38 | err.repeatRequest = (newRequest?) => { 39 | return this.request(newRequest || request); 40 | }; 41 | 42 | return throwError(err); 43 | }) 44 | ); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/lib/ngx-restangular.config.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | import { isArray } from 'lodash'; 4 | 5 | 6 | export const RESTANGULAR = new InjectionToken('restangularWithConfig'); 7 | 8 | export function RestangularFactory([callbackOrServices, callback]) { 9 | let arrServices = []; 10 | let fn = callbackOrServices; 11 | 12 | if (isArray(callbackOrServices)) { 13 | arrServices = callbackOrServices; 14 | fn = callback; 15 | } 16 | 17 | return {fn, arrServices}; 18 | } 19 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/lib/ngx-restangular.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule, Optional, SkipSelf, InjectionToken } from '@angular/core'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | import { RESTANGULAR, RestangularFactory } from './ngx-restangular.config'; 4 | import { Restangular } from './ngx-restangular'; 5 | import { RestangularHttp } from './ngx-restangular-http'; 6 | 7 | export const CONFIG_OBJ = new InjectionToken('configObj'); 8 | 9 | @NgModule({ 10 | imports: [HttpClientModule], 11 | providers: [RestangularHttp, Restangular] 12 | }) 13 | export class RestangularModule { 14 | 15 | constructor(@Optional() @SkipSelf() parentModule: RestangularModule) { 16 | if (parentModule) { 17 | throw new Error( 18 | 'RestangularModule is already loaded. Import it in the AppModule only'); 19 | } 20 | } 21 | 22 | static forRoot(configFunction?: (provider: any, ...arg: any[]) => void): ModuleWithProviders; 23 | static forRoot(providers?: any[], configFunction?: (provider: any, ...arg: any[]) => void): ModuleWithProviders; 24 | static forRoot(config1?, config2?): ModuleWithProviders { 25 | return { 26 | ngModule: RestangularModule, 27 | providers: [ 28 | {provide: CONFIG_OBJ, useValue: [config1, config2]}, 29 | {provide: RESTANGULAR, useFactory: RestangularFactory, deps: [CONFIG_OBJ]}, 30 | ] 31 | }; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/lib/ngx-restangular.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, Injector, Optional, Type } from '@angular/core'; 2 | import { assign } from 'core-js/features/object'; 3 | import { 4 | map, 5 | bind, 6 | union, 7 | values, 8 | pick, 9 | isEmpty, 10 | isFunction, 11 | isNumber, 12 | isUndefined, 13 | isArray, 14 | isObject, 15 | extend, 16 | each, 17 | every, 18 | omit, 19 | get, 20 | defaults, 21 | clone, 22 | includes 23 | } from 'lodash'; 24 | 25 | import { BehaviorSubject } from 'rxjs'; 26 | import { filter } from 'rxjs/operators'; 27 | 28 | import { RESTANGULAR } from './ngx-restangular.config'; 29 | import { RestangularHttp } from './ngx-restangular-http'; 30 | import { RestangularConfigurer } from './ngx-restangular-config.factory'; 31 | 32 | @Injectable() 33 | export class Restangular { 34 | provider: { 35 | setBaseUrl: any, 36 | setDefaultHeaders: any, 37 | configuration: any, 38 | setSelfLinkAbsoluteUrl: any, 39 | setExtraFields: any, 40 | setDefaultHttpFields: any, 41 | setPlainByDefault: any, 42 | setEncodeIds: any, 43 | setDefaultRequestParams: any, 44 | requestParams: any, 45 | defaultHeaders: any, 46 | setDefaultResponseMethod: any, 47 | defaultResponseMethod: any, 48 | setMethodOverriders: any, 49 | setJsonp: any, 50 | setUrlCreator: any, 51 | setRestangularFields: any, 52 | setUseCannonicalId: any, 53 | addResponseInterceptor: any, 54 | addErrorInterceptor: any, 55 | setResponseInterceptor: any, 56 | setResponseExtractor: any, 57 | setErrorInterceptor: any, 58 | addRequestInterceptor: any, 59 | setRequestInterceptor: any, 60 | setFullRequestInterceptor: any, 61 | addFullRequestInterceptor: any, 62 | setOnBeforeElemRestangularized: any, 63 | setRestangularizePromiseInterceptor: any, 64 | setOnElemRestangularized: any, 65 | setParentless: any, 66 | setRequestSuffix: any, 67 | addElementTransformer: any, 68 | extendCollection: any, 69 | extendModel: any, 70 | setTransformOnlyServerElements: any, 71 | setFullResponse: any, 72 | $get: any 73 | }; 74 | addElementTransformer: any; 75 | extendCollection: any; 76 | extendModel: any; 77 | copy; 78 | configuration; 79 | service; 80 | id; 81 | route; 82 | parentResource; 83 | restangularCollection; 84 | cannonicalId; 85 | etag; 86 | selfLink; 87 | get; 88 | getList; 89 | put; 90 | post; 91 | remove; 92 | head; 93 | trace; 94 | options; 95 | patch; 96 | getRestangularUrl; 97 | getRequestedUrl; 98 | putElement; 99 | addRestangularMethod; 100 | getParentList; 101 | clone; 102 | ids; 103 | httpConfig; 104 | reqParams; 105 | one; 106 | all; 107 | several; 108 | oneUrl; 109 | allUrl; 110 | customPUT; 111 | customPATCH; 112 | customPOST; 113 | customDELETE; 114 | customGET; 115 | customGETLIST; 116 | customOperation; 117 | doPUT; 118 | doPATCH; 119 | doPOST; 120 | doDELETE; 121 | doGET; 122 | doGETLIST; 123 | fromServer; 124 | withConfig; 125 | withHttpConfig; 126 | singleOne; 127 | plain; 128 | save; 129 | restangularized; 130 | restangularizeElement; 131 | restangularizeCollection; 132 | 133 | constructor( 134 | @Optional() @Inject(RESTANGULAR) public configObj, 135 | private injector: Injector, 136 | private http: RestangularHttp 137 | ) { 138 | this.provider = new providerConfig(http); 139 | const element = this.provider.$get(); 140 | assign(this, element); 141 | 142 | this.setDefaultConfig(); 143 | } 144 | 145 | setDefaultConfig() { 146 | if (!this.configObj || !isFunction(this.configObj.fn)) { 147 | return; 148 | } 149 | 150 | const arrDI = map(this.configObj.arrServices, (services: Type) => { 151 | return this.injector.get(services); 152 | }); 153 | 154 | this.configObj.fn(...[this.provider, ...arrDI]); 155 | } 156 | } 157 | 158 | function providerConfig($http) { 159 | const globalConfiguration = {}; 160 | 161 | RestangularConfigurer(this, globalConfiguration); 162 | 163 | this.$get = $get; 164 | 165 | function $get() { 166 | 167 | function createServiceForConfiguration(config) { 168 | const service: any = {}; 169 | 170 | const urlHandler = new config.urlCreatorFactory[config.urlCreator](); 171 | urlHandler.setConfig(config); 172 | 173 | function restangularizeBase(parent, elem, route, reqParams, fromServer) { 174 | elem[config.restangularFields.route] = route; 175 | elem[config.restangularFields.getRestangularUrl] = bind(urlHandler.fetchUrl, urlHandler, elem); 176 | elem[config.restangularFields.getRequestedUrl] = bind(urlHandler.fetchRequestedUrl, urlHandler, elem); 177 | elem[config.restangularFields.addRestangularMethod] = bind(addRestangularMethodFunction, elem); 178 | elem[config.restangularFields.clone] = bind(copyRestangularizedElement, elem); 179 | elem[config.restangularFields.reqParams] = isEmpty(reqParams) ? null : reqParams; 180 | elem[config.restangularFields.withHttpConfig] = bind(withHttpConfig, elem); 181 | elem[config.restangularFields.plain] = bind(stripRestangular, elem, elem); 182 | 183 | // Tag element as restangularized 184 | elem[config.restangularFields.restangularized] = true; 185 | 186 | // RequestLess connection 187 | elem[config.restangularFields.one] = bind(one, elem, elem); 188 | elem[config.restangularFields.all] = bind(all, elem, elem); 189 | elem[config.restangularFields.several] = bind(several, elem, elem); 190 | elem[config.restangularFields.oneUrl] = bind(oneUrl, elem, elem); 191 | elem[config.restangularFields.allUrl] = bind(allUrl, elem, elem); 192 | 193 | elem[config.restangularFields.fromServer] = !!fromServer; 194 | 195 | if (parent && config.shouldSaveParent(route)) { 196 | const parentId = config.getIdFromElem(parent); 197 | const parentUrl = config.getUrlFromElem(parent); 198 | 199 | const restangularFieldsForParent = union( 200 | values(pick(config.restangularFields, ['route', 'singleOne', 'parentResource'])), 201 | config.extraFields 202 | ); 203 | const parentResource = pick(parent, restangularFieldsForParent); 204 | 205 | if (config.isValidId(parentId)) { 206 | config.setIdToElem(parentResource, parentId, route); 207 | } 208 | if (config.isValidId(parentUrl)) { 209 | config.setUrlToElem(parentResource, parentUrl, route); 210 | } 211 | 212 | elem[config.restangularFields.parentResource] = parentResource; 213 | } else { 214 | elem[config.restangularFields.parentResource] = null; 215 | } 216 | return elem; 217 | } 218 | 219 | function one(parent, route, id, singleOne) { 220 | let error; 221 | if (isNumber(route) || isNumber(parent)) { 222 | error = 'You\'re creating a Restangular entity with the number '; 223 | error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; 224 | throw new Error(error); 225 | } 226 | if (isUndefined(route)) { 227 | error = 'You\'re creating a Restangular entity either without the path. '; 228 | error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; 229 | throw new Error(error); 230 | } 231 | const elem = {}; 232 | config.setIdToElem(elem, id, route); 233 | config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); 234 | return restangularizeElem(parent, elem, route, false); 235 | } 236 | 237 | function all(parent, route) { 238 | return restangularizeCollection(parent, [], route, false); 239 | } 240 | 241 | function several(parent, route /*, ids */) { 242 | const collection = []; 243 | collection[config.restangularFields.ids] = Array.prototype.splice.call(arguments, 2); 244 | return restangularizeCollection(parent, collection, route, false); 245 | } 246 | 247 | function oneUrl(parent, route, url) { 248 | if (!route) { 249 | throw new Error('Route is mandatory when creating new Restangular objects.'); 250 | } 251 | const elem = {}; 252 | config.setUrlToElem(elem, url, route); 253 | return restangularizeElem(parent, elem, route, false); 254 | } 255 | 256 | function allUrl(parent, route, url) { 257 | if (!route) { 258 | throw new Error('Route is mandatory when creating new Restangular objects.'); 259 | } 260 | const elem = {}; 261 | config.setUrlToElem(elem, url, route); 262 | return restangularizeCollection(parent, elem, route, false); 263 | } 264 | 265 | // Promises 266 | function restangularizeResponse(subject, isCollection, valueToFill) { 267 | return subject.pipe(filter(res => !!res)); 268 | } 269 | 270 | function resolvePromise(subject, response, data, filledValue) { 271 | extend(filledValue, data); 272 | 273 | // Trigger the full response interceptor. 274 | if (config.fullResponse) { 275 | subject.next(extend(response, { 276 | data: data 277 | })); 278 | } else { 279 | subject.next(data); 280 | } 281 | 282 | subject.complete(); 283 | } 284 | 285 | // Elements 286 | function stripRestangular(elem) { 287 | if (isArray(elem)) { 288 | const array = []; 289 | each(elem, function (value) { 290 | array.push(config.isRestangularized(value) ? stripRestangular(value) : value); 291 | }); 292 | return array; 293 | } else { 294 | return omit(elem, values(omit(config.restangularFields, 'id'))); 295 | } 296 | } 297 | 298 | function addCustomOperation(elem) { 299 | elem[config.restangularFields.customOperation] = bind(customFunction, elem); 300 | const requestMethods = {get: customFunction, delete: customFunction}; 301 | each(['put', 'patch', 'post'], function (name) { 302 | requestMethods[name] = function (operation, element, path, params, headers) { 303 | return bind(customFunction, this)(operation, path, params, headers, element); 304 | }; 305 | }); 306 | each(requestMethods, function (requestFunc, name) { 307 | const callOperation = name === 'delete' ? 'remove' : name; 308 | each(['do', 'custom'], function (alias) { 309 | elem[alias + name.toUpperCase()] = bind(requestFunc, elem, callOperation); 310 | }); 311 | }); 312 | elem[config.restangularFields.customGETLIST] = bind(fetchFunction, elem); 313 | elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; 314 | } 315 | 316 | function copyRestangularizedElement(fromElement, toElement = {}) { 317 | const copiedElement = assign(toElement, fromElement); 318 | return restangularizeElem(copiedElement[config.restangularFields.parentResource], 319 | copiedElement, copiedElement[config.restangularFields.route], true); 320 | } 321 | 322 | function restangularizeElem(parent, element, route, fromServer?, collection?, reqParams?) { 323 | const elem = config.onBeforeElemRestangularized(element, false, route); 324 | 325 | const localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); 326 | 327 | if (config.useCannonicalId) { 328 | localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem); 329 | } 330 | 331 | if (collection) { 332 | localElem[config.restangularFields.getParentList] = function () { 333 | return collection; 334 | }; 335 | } 336 | 337 | localElem[config.restangularFields.restangularCollection] = false; 338 | localElem[config.restangularFields.get] = bind(getFunction, localElem); 339 | localElem[config.restangularFields.getList] = bind(fetchFunction, localElem); 340 | localElem[config.restangularFields.put] = bind(putFunction, localElem); 341 | localElem[config.restangularFields.post] = bind(postFunction, localElem); 342 | localElem[config.restangularFields.remove] = bind(deleteFunction, localElem); 343 | localElem[config.restangularFields.head] = bind(headFunction, localElem); 344 | localElem[config.restangularFields.trace] = bind(traceFunction, localElem); 345 | localElem[config.restangularFields.options] = bind(optionsFunction, localElem); 346 | localElem[config.restangularFields.patch] = bind(patchFunction, localElem); 347 | localElem[config.restangularFields.save] = bind(save, localElem); 348 | 349 | addCustomOperation(localElem); 350 | return config.transformElem(localElem, false, route, service, true); 351 | } 352 | 353 | function restangularizeCollection(parent, element, route, fromServer?, reqParams?) { 354 | const elem = config.onBeforeElemRestangularized(element, true, route); 355 | 356 | const localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); 357 | localElem[config.restangularFields.restangularCollection] = true; 358 | localElem[config.restangularFields.post] = bind(postFunction, localElem, null); 359 | localElem[config.restangularFields.remove] = bind(deleteFunction, localElem); 360 | localElem[config.restangularFields.head] = bind(headFunction, localElem); 361 | localElem[config.restangularFields.trace] = bind(traceFunction, localElem); 362 | localElem[config.restangularFields.putElement] = bind(putElementFunction, localElem); 363 | localElem[config.restangularFields.options] = bind(optionsFunction, localElem); 364 | localElem[config.restangularFields.patch] = bind(patchFunction, localElem); 365 | localElem[config.restangularFields.get] = bind(getById, localElem); 366 | localElem[config.restangularFields.getList] = bind(fetchFunction, localElem, null); 367 | 368 | addCustomOperation(localElem); 369 | return config.transformElem(localElem, true, route, service, true); 370 | } 371 | 372 | function restangularizeCollectionAndElements(parent, element, route) { 373 | const collection = restangularizeCollection(parent, element, route, false); 374 | each(collection, function (elem) { 375 | if (elem) { 376 | restangularizeElem(parent, elem, route, false); 377 | } 378 | }); 379 | return collection; 380 | } 381 | 382 | function getById(id, reqParams, headers) { 383 | return this.customGET(id.toString(), reqParams, headers); 384 | } 385 | 386 | function putElementFunction(idx, params, headers) { 387 | const __this = this; 388 | const elemToPut = this[idx]; 389 | const subject = new BehaviorSubject(null); 390 | let filledArray = []; 391 | filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service); 392 | 393 | elemToPut.put(params, headers) 394 | .subscribe(function (serverElem) { 395 | const newArray = copyRestangularizedElement(__this); 396 | newArray[idx] = serverElem; 397 | filledArray = newArray; 398 | subject.next(newArray); 399 | }, function (response) { 400 | subject.error(response); 401 | }, function () { 402 | subject.complete(); 403 | }); 404 | 405 | return restangularizeResponse(subject, true, filledArray); 406 | } 407 | 408 | function parseResponse(resData, operation, route, fetchUrl, response, subject) { 409 | const data = config.responseExtractor(resData, operation, route, fetchUrl, response, subject); 410 | const etag = response.headers.get('ETag'); 411 | if (data && etag) { 412 | data[config.restangularFields.etag] = etag; 413 | } 414 | return data; 415 | } 416 | 417 | function fetchFunction(what, reqParams, headers) { 418 | const __this = this; 419 | const subject = new BehaviorSubject(null); 420 | const operation = 'getList'; 421 | const url = urlHandler.fetchUrl(this, what); 422 | const whatFetched = what || __this[config.restangularFields.route]; 423 | 424 | const request = config.fullRequestInterceptor(null, operation, 425 | whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {}); 426 | 427 | let filledArray = []; 428 | filledArray = config.transformElem(filledArray, true, whatFetched, service); 429 | 430 | let method = 'getList'; 431 | 432 | if (config.jsonp) { 433 | method = 'jsonp'; 434 | } 435 | 436 | const okCallback = function (response) { 437 | const resData = response.body; 438 | const fullParams = response.config.params; 439 | let data = parseResponse(resData, operation, whatFetched, url, response, subject); 440 | 441 | // support empty response for getList() calls (some APIs respond with 204 and empty body) 442 | if (isUndefined(data) || '' === data) { 443 | data = []; 444 | } 445 | if (!isArray(data)) { 446 | throw new Error('Response for getList SHOULD be an array and not an object or something else'); 447 | } 448 | 449 | if (true === config.plainByDefault) { 450 | return resolvePromise(subject, response, data, filledArray); 451 | } 452 | 453 | let processedData = map(data, function (elem) { 454 | if (!__this[config.restangularFields.restangularCollection]) { 455 | return restangularizeElem(__this, elem, what, true, data); 456 | } else { 457 | return restangularizeElem(__this[config.restangularFields.parentResource], 458 | elem, __this[config.restangularFields.route], true, data); 459 | } 460 | }); 461 | 462 | processedData = extend(data, processedData); 463 | 464 | if (!__this[config.restangularFields.restangularCollection]) { 465 | resolvePromise( 466 | subject, 467 | response, 468 | restangularizeCollection( 469 | __this, 470 | processedData, 471 | what, 472 | true, 473 | fullParams 474 | ), 475 | filledArray 476 | ); 477 | } else { 478 | resolvePromise( 479 | subject, 480 | response, 481 | restangularizeCollection( 482 | __this[config.restangularFields.parentResource], 483 | processedData, 484 | __this[config.restangularFields.route], 485 | true, 486 | fullParams 487 | ), 488 | filledArray 489 | ); 490 | } 491 | }; 492 | 493 | urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, 494 | this[config.restangularFields.etag], operation)[method]() 495 | .subscribe(okCallback, function error(response) { 496 | if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { 497 | resolvePromise(subject, response, __this, filledArray); 498 | } else if (every(config.errorInterceptors, function (cb: any) { 499 | 500 | return cb(response, subject, okCallback) !== false; 501 | })) { 502 | // triggered if no callback returns false 503 | subject.error(response); 504 | } 505 | }); 506 | 507 | return restangularizeResponse(subject, true, filledArray); 508 | } 509 | 510 | function withHttpConfig(httpConfig) { 511 | this[config.restangularFields.httpConfig] = httpConfig; 512 | return this; 513 | } 514 | 515 | function save(params, headers) { 516 | if (this[config.restangularFields.fromServer]) { 517 | return this[config.restangularFields.put](params, headers); 518 | } else { 519 | return bind(elemFunction, this)('post', undefined, params, undefined, headers); 520 | } 521 | } 522 | 523 | function elemFunction(operation, what, params, obj, headers) { 524 | const __this = this; 525 | const subject = new BehaviorSubject(null); 526 | const resParams = params || {}; 527 | const route = what || this[config.restangularFields.route]; 528 | const fetchUrl = urlHandler.fetchUrl(this, what); 529 | 530 | let callObj = obj || this; 531 | // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field) 532 | const etag = callObj[config.restangularFields.etag] || (operation !== 'post' ? this[config.restangularFields.etag] : null); 533 | 534 | if (isObject(callObj) && config.isRestangularized(callObj)) { 535 | callObj = stripRestangular(callObj); 536 | } 537 | const request = config.fullRequestInterceptor( 538 | callObj, 539 | operation, 540 | route, 541 | fetchUrl, 542 | headers || {}, 543 | resParams || {}, 544 | this[config.restangularFields.httpConfig] || {} 545 | ); 546 | 547 | let filledObject = {}; 548 | filledObject = config.transformElem(filledObject, false, route, service); 549 | 550 | const okCallback = function (response) { 551 | const resData = get(response, 'body'); 552 | const fullParams = get(response, 'config.params'); 553 | 554 | const elem = parseResponse(resData, operation, route, fetchUrl, response, subject); 555 | 556 | if (elem) { 557 | let data; 558 | if (true === config.plainByDefault) { 559 | return resolvePromise(subject, response, elem, filledObject); 560 | } 561 | 562 | if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { 563 | data = restangularizeElem( 564 | __this[config.restangularFields.parentResource], 565 | elem, 566 | route, 567 | true, 568 | null, 569 | fullParams 570 | ); 571 | resolvePromise(subject, response, data, filledObject); 572 | } else { 573 | data = restangularizeElem( 574 | __this[config.restangularFields.parentResource], 575 | elem, 576 | __this[config.restangularFields.route], 577 | true, 578 | null, 579 | fullParams 580 | ); 581 | 582 | data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; 583 | resolvePromise(subject, response, data, filledObject); 584 | } 585 | 586 | } else { 587 | resolvePromise(subject, response, undefined, filledObject); 588 | } 589 | }; 590 | 591 | const errorCallback = function (response) { 592 | if (response.status === 304 && config.isSafe(operation)) { 593 | resolvePromise(subject, response, __this, filledObject); 594 | } else if (every(config.errorInterceptors, function (cb: any) { 595 | return cb(response, subject, okCallback) !== false; 596 | })) { 597 | // triggered if no callback returns false 598 | subject.error(response); 599 | } 600 | }; 601 | // Overriding HTTP Method 602 | let callOperation = operation; 603 | let callHeaders = extend({}, request.headers); 604 | const isOverrideOperation = config.isOverridenMethod(operation); 605 | if (isOverrideOperation) { 606 | callOperation = 'post'; 607 | callHeaders = extend(callHeaders, {'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation.toUpperCase()}); 608 | } else if (config.jsonp && callOperation === 'get') { 609 | callOperation = 'jsonp'; 610 | } 611 | 612 | if (config.isSafe(operation)) { 613 | if (isOverrideOperation) { 614 | urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, 615 | what, etag, callOperation)[callOperation]({}).subscribe(okCallback, errorCallback); 616 | } else { 617 | urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, 618 | what, etag, callOperation)[callOperation]().subscribe(okCallback, errorCallback); 619 | } 620 | } else { 621 | urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, 622 | what, etag, callOperation)[callOperation](request.element).subscribe(okCallback, errorCallback); 623 | } 624 | 625 | return restangularizeResponse(subject, false, filledObject); 626 | } 627 | 628 | function getFunction(params, headers) { 629 | return bind(elemFunction, this)('get', undefined, params, undefined, headers); 630 | } 631 | 632 | function deleteFunction(params, headers) { 633 | return bind(elemFunction, this)('remove', undefined, params, undefined, headers); 634 | } 635 | 636 | function putFunction(params, headers) { 637 | return bind(elemFunction, this)('put', undefined, params, undefined, headers); 638 | } 639 | 640 | function postFunction(what, elem, params, headers) { 641 | return bind(elemFunction, this)('post', what, params, elem, headers); 642 | } 643 | 644 | function headFunction(params, headers) { 645 | return bind(elemFunction, this)('head', undefined, params, undefined, headers); 646 | } 647 | 648 | function traceFunction(params, headers) { 649 | return bind(elemFunction, this)('trace', undefined, params, undefined, headers); 650 | } 651 | 652 | function optionsFunction(params, headers) { 653 | return bind(elemFunction, this)('options', undefined, params, undefined, headers); 654 | } 655 | 656 | function patchFunction(elem, params, headers) { 657 | return bind(elemFunction, this)('patch', undefined, params, elem, headers); 658 | } 659 | 660 | function customFunction(operation, path, params, headers, elem) { 661 | return bind(elemFunction, this)(operation, path, params, elem, headers); 662 | } 663 | 664 | function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) { 665 | let bindedFunction; 666 | if (operation === 'getList') { 667 | bindedFunction = bind(fetchFunction, this, path); 668 | } else { 669 | bindedFunction = bind(customFunction, this, operation, path); 670 | } 671 | 672 | const createdFunction = function (params, headers, elem) { 673 | const callParams = defaults({ 674 | params: params, 675 | headers: headers, 676 | elem: elem 677 | }, { 678 | params: defaultParams, 679 | headers: defaultHeaders, 680 | elem: defaultElem 681 | }); 682 | return bindedFunction(callParams.params, callParams.headers, callParams.elem); 683 | }; 684 | 685 | if (config.isSafe(operation)) { 686 | this[name] = createdFunction; 687 | } else { 688 | this[name] = function (elem, params, headers) { 689 | return createdFunction(params, headers, elem); 690 | }; 691 | } 692 | } 693 | 694 | function withConfigurationFunction(configurer) { 695 | const newConfig = clone(omit(config, 'configuration')); 696 | RestangularConfigurer(newConfig, newConfig); 697 | configurer(newConfig); 698 | return createServiceForConfiguration(newConfig); 699 | } 700 | 701 | function toService(route, parent) { 702 | const knownCollectionMethods = values(config.restangularFields); 703 | const serv: any = {}; 704 | const collection = (parent || service).all(route); 705 | serv.one = bind(one, (parent || service), parent, route); 706 | serv.all = bind(collection.all, collection); 707 | serv.post = bind(collection.post, collection); 708 | serv.getList = bind(collection.getList, collection); 709 | serv.withHttpConfig = bind(collection.withHttpConfig, collection); 710 | serv.get = bind(collection.get, collection); 711 | 712 | for (const prop in collection) { 713 | if (collection.hasOwnProperty(prop) && isFunction(collection[prop]) && !includes(knownCollectionMethods, prop)) { 714 | serv[prop] = bind(collection[prop], collection); 715 | } 716 | } 717 | 718 | return serv; 719 | } 720 | 721 | RestangularConfigurer(service, config); 722 | 723 | service.copy = bind(copyRestangularizedElement, service); 724 | 725 | service.service = bind(toService, service); 726 | 727 | service.withConfig = bind(withConfigurationFunction, service); 728 | 729 | service.one = bind(one, service, null); 730 | 731 | service.all = bind(all, service, null); 732 | 733 | service.several = bind(several, service, null); 734 | 735 | service.oneUrl = bind(oneUrl, service, null); 736 | 737 | service.allUrl = bind(allUrl, service, null); 738 | 739 | service.stripRestangular = bind(stripRestangular, service); 740 | 741 | service.restangularizeElement = bind(restangularizeElem, service); 742 | 743 | service.restangularizeCollection = bind(restangularizeCollectionAndElements, service); 744 | 745 | return service; 746 | } 747 | 748 | return createServiceForConfiguration(globalConfiguration); 749 | } 750 | 751 | } 752 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-restangular 3 | */ 4 | 5 | export * from './lib/index'; 6 | -------------------------------------------------------------------------------- /projects/ngx-restangular/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/ngx-restangular/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2015" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "skipTemplateCodegen": true, 23 | "strictMetadataEmit": true, 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true, 26 | "flatModuleId": "AUTOGENERATED", 27 | "flatModuleOutFile": "AUTOGENERATED", 28 | "enableIvy": false 29 | }, 30 | "exclude": [ 31 | "src/test.ts", 32 | "**/*.spec.ts" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /projects/ngx-restangular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/ngx-restangular/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json" 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es5", 5 | "moduleResolution": "node", 6 | "baseUrl": "./", 7 | "sourceMap": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "typeRoots": ["/node_modules/@types"], 11 | "lib": ["es2015", "dom"], 12 | "skipLibCheck": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": false, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "directive-selector": [ 120 | true, 121 | "attribute", 122 | "app", 123 | "camelCase" 124 | ], 125 | "component-selector": [ 126 | true, 127 | "element", 128 | "app", 129 | "kebab-case" 130 | ], 131 | "no-output-on-prefix": true, 132 | "use-input-property-decorator": true, 133 | "use-output-property-decorator": true, 134 | "use-host-property-decorator": true, 135 | "no-input-rename": true, 136 | "no-output-rename": true, 137 | "use-life-cycle-interface": true, 138 | "use-pipe-transform-interface": true, 139 | "component-class-suffix": true, 140 | "directive-class-suffix": true 141 | } 142 | } 143 | --------------------------------------------------------------------------------