├── .npmignore ├── karma.conf.js ├── typings.json ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── src ├── test │ ├── helpers │ │ └── employee.ts │ ├── odataservice.spec.ts │ ├── odataoperation.spec.ts │ └── query.spec.ts ├── index.ts ├── odataservicefactory.ts ├── config.ts ├── odata.ts ├── query.ts └── operation.ts ├── jsconfig.json ├── config ├── helpers.js ├── spec-bundle.js ├── webpack.prod.js ├── karma.conf.js ├── karma.conf-for-karma-typescript.js ├── webpack.common.js ├── webpack.test.js └── webpack.dev.js ├── webpack.config.js ├── tsconfig-test.json ├── tsconfig.json ├── .gitignore ├── LICENSE ├── README.md ├── package.json └── tslint.json /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Look in ./config for karma.conf.js 2 | module.exports = require('./config/karma.conf.js'); 3 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": {}, 3 | "globalDependencies": {}, 4 | "globalDevDependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "typescript.check.workspaceVersion": false 4 | } -------------------------------------------------------------------------------- /src/test/helpers/employee.ts: -------------------------------------------------------------------------------- 1 | export interface IEmployee { 2 | EmployeeID: number; 3 | FirstName: string; 4 | LastName: string; 5 | BirthDate: Date; 6 | } 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { ODataService } from './odata'; 2 | export { ODataConfiguration } from './config'; 3 | export { ODataQuery, PagedResult } from './query'; 4 | export { ODataServiceFactory } from './odataservicefactory'; 5 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "bower_components", 12 | "jspm_packages", 13 | "tmp", 14 | "temp", 15 | "build" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /config/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: @AngularClass 3 | */ 4 | var path = require('path'); 5 | 6 | // Helper functions 7 | var ROOT = path.resolve(__dirname, '..'); 8 | 9 | function hasProcessFlag(flag) { 10 | return process.argv.join('').indexOf(flag) > -1; 11 | } 12 | 13 | function root(args) { 14 | args = Array.prototype.slice.call(arguments, 0); 15 | return path.join.apply(path, [ROOT].concat(args)); 16 | } 17 | 18 | exports.hasProcessFlag = hasProcessFlag; 19 | exports.root = root; 20 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('webpack-config') 2 | 3 | const TARGET = process.env.npm_lifecycle_event 4 | 5 | var webpackConfigFile; 6 | 7 | switch (TARGET) { 8 | case 'start': 9 | webpackConfigFile = './config/webpack-dev.config.js' 10 | break 11 | case 'test': 12 | webpackConfigFile = './config/webpack.test.js' 13 | break 14 | default: 15 | webpackConfigFile = './config/webpack.prod.js' 16 | break 17 | } 18 | 19 | module.exports = new webpackConfig.Config().extend(webpackConfigFile); 20 | -------------------------------------------------------------------------------- /tsconfig-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "rootDir": "./src", 7 | "outDir": "./build-test", 8 | "sourceMap": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "declaration": true 13 | }, 14 | "exclude": [ 15 | ".vscode", 16 | ".git", 17 | "build", 18 | "node_modules" 19 | ], 20 | "compileOnSave": false, 21 | "buildOnSave": false, 22 | "atom": { 23 | "rewriteTsconfig": false 24 | } 25 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "rootDir": "./src", 7 | "outDir": "./build", 8 | "sourceMap": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "declaration": true 13 | }, 14 | "exclude": [ 15 | ".vscode", 16 | ".git", 17 | "build", 18 | "node_modules", 19 | "src/test" 20 | ], 21 | "compileOnSave": false, 22 | "buildOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | } 26 | } -------------------------------------------------------------------------------- /src/odataservicefactory.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { ODataService } from './odata'; 4 | import { ODataConfiguration } from './config'; 5 | 6 | @Injectable() 7 | export class ODataServiceFactory { 8 | 9 | constructor(private http: Http, private config: ODataConfiguration) { 10 | } 11 | 12 | public CreateService(typeName: string, handleError?: (err: any) => any): ODataService { 13 | return new ODataService(typeName, this.http, this.config); 14 | } 15 | 16 | public CreateServiceWithOptions(typeName: string, config: ODataConfiguration): ODataService { 17 | return new ODataService(typeName, this.http, config); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | typings 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | .vscode/chrome 41 | 42 | build-test 43 | /.vscode/symbols.json 44 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack", 3 | "version": "0.1.0", 4 | "command": "npm", 5 | "isShellCommand": true, 6 | "args": [ 7 | ], 8 | "echoCommand": true, 9 | "tasks": [ 10 | { 11 | "args": [ 12 | "run", 13 | "build" 14 | ], 15 | "suppressTaskName": true, 16 | "taskName": "build", 17 | "isBuildCommand": true 18 | }, 19 | { 20 | "args": [ 21 | "test" 22 | ], 23 | "suppressTaskName": true, 24 | "taskName": "test", 25 | "isBuildCommand": false 26 | } 27 | ], 28 | // The problem matcher will find build issues and adds it to the Problems log of VSCode. 29 | // The editor will also highlight the issues in place. 30 | "problemMatcher": { 31 | "owner": "webpack", 32 | "severity": "error", 33 | "fileLocation": "relative", 34 | "pattern": [{ 35 | "regexp": "ERROR in (.*)", 36 | "file" : 1 37 | }, { 38 | "regexp": "\\((\\d+),(\\d+)\\):(.*)", 39 | "line":1, 40 | "column": 2, 41 | "message": 3 42 | }] 43 | } 44 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Gallay Lajos 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 | -------------------------------------------------------------------------------- /config/spec-bundle.js: -------------------------------------------------------------------------------- 1 | Error.stackTraceLimit = 5; 2 | 3 | require('core-js/es6'); 4 | require('core-js/es7/reflect'); 5 | 6 | // Typescript emit helpers polyfill 7 | require('ts-helpers'); 8 | 9 | require('zone.js/dist/zone'); 10 | require('zone.js/dist/long-stack-trace-zone'); 11 | require('zone.js/dist/proxy'); // since zone.js 0.6.15 12 | require('zone.js/dist/sync-test'); 13 | require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14 14 | require('zone.js/dist/async-test'); 15 | require('zone.js/dist/fake-async-test'); 16 | 17 | // RxJS 18 | require('rxjs/Rx'); 19 | 20 | var testing = require('@angular/core/testing'); 21 | var browser = require('@angular/platform-browser-dynamic/testing'); 22 | 23 | testing.TestBed.initTestEnvironment( 24 | browser.BrowserDynamicTestingModule, 25 | browser.platformBrowserDynamicTesting() 26 | ); 27 | 28 | var testContext = require.context('../src', true, /\.spec\.ts/); 29 | 30 | /* 31 | * get all the files, for each file, call the context function 32 | * that will require the file and load it up here. Context will 33 | * loop and require those spec files here 34 | */ 35 | function requireAll(requireContext) { 36 | return requireContext.keys().map(requireContext); 37 | } 38 | 39 | // requires and returns all modules that match 40 | var modules = requireAll(testContext); 41 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/node_modules/karma/bin/karma", 9 | "stopOnEntry": false, 10 | "args": [ 11 | "start" 12 | ], 13 | "cwd": "${workspaceRoot}", 14 | "preLaunchTask": null, 15 | "runtimeExecutable": null, 16 | "runtimeArgs": [ 17 | "--nolazy" 18 | ], 19 | "env": { 20 | "NODE_ENV": "development" 21 | }, 22 | "console": "internalConsole", 23 | "sourceMaps": false 24 | }, 25 | { 26 | "name": "Launch Chrome", 27 | "type": "chrome", 28 | "request": "launch", 29 | "url": "http://localhost:8000/", 30 | "sourceMaps": true, 31 | "diagnosticLogging": true, 32 | "webRoot": "${workspaceRoot}/dist", 33 | "userDataDir": "${workspaceRoot}/.vscode/chrome" 34 | }, 35 | { 36 | "name": "Attach Chrome", 37 | "type": "chrome", 38 | "request": "attach", 39 | "port": 9222, 40 | "sourceMaps": true, 41 | "diagnosticLogging": true, 42 | "webRoot": "${workspaceRoot}/dist" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/test/odataservice.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { assert } from 'chai'; 3 | import { Observable, Operator } from 'rxjs/rx'; 4 | import { Location } from '@angular/common'; 5 | import { inject, TestBed } from '@angular/core/testing'; 6 | import { MockBackend } from '@angular/http/testing'; 7 | import { BaseRequestOptions, Http, ConnectionBackend, HttpModule } from '@angular/http'; 8 | import { IEmployee } from './helpers/employee'; 9 | import { ODataOperation } from '../operation'; 10 | import { ODataServiceFactory } from '../odataservicefactory'; 11 | import { ODataConfiguration } from '../config'; 12 | 13 | describe('ODataService', () => { 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | providers: [ 17 | BaseRequestOptions, 18 | MockBackend, 19 | { 20 | provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => { 21 | return new Http(backend, defaultOptions); 22 | }, 23 | deps: [MockBackend, BaseRequestOptions] 24 | }, 25 | // { 26 | // provide: Location, useFactory: () => { 27 | // return { 28 | // path: 'http://localhost/test' 29 | // }; 30 | // } 31 | // }, 32 | ODataConfiguration, 33 | ODataServiceFactory 34 | ], 35 | imports: [ 36 | HttpModule 37 | ] 38 | }); 39 | }); 40 | 41 | it('Construct via injection', inject([ ODataServiceFactory ], (factory: ODataServiceFactory) => { 42 | // Act 43 | let service = factory.CreateService('Employees'); 44 | 45 | // Assert 46 | assert.isNotNull(service); 47 | })); 48 | }); 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular2-odata 2 | The goal is to create a fluent API for querying, creating, updating and deleting OData resources in Angular2. 3 | OData service for Angular. 4 | 5 | If you are using OData with Angular>2 please check also my other related project, [FuryTech.OdataTypescriptServiceGenerator](https://github.com/gallayl/FuryTech.OdataTypescriptServiceGenerator) 6 | 7 | ##Usage example: 8 | Get the package from NPM: 9 | npm install angular2-odata 10 | 11 | ``` 12 | import { ODataConfiguration, ODataServiceFactory, ODataService } from "angular2-odata"; 13 | import { bootstrap } from "angular2/platform/browser"; 14 | 15 | @Injectable() 16 | class MyODataConfig extends ODataConfiguration{ 17 | baseUrl="http://localhost:54872/odata/"; 18 | } 19 | 20 | bootstrap(app,[ 21 | provide(ODataConfiguration, {useClass:MyODataConfig}), 22 | ODataServiceFactory, 23 | ] 24 | 25 | //An example model interface 26 | interface INotification { 27 | Id: number; 28 | CommentId: number; 29 | Comment: IComment; 30 | FromId: number; 31 | From: IResource; 32 | Priority: number; 33 | SendDate: Date; 34 | IsArchived: boolean; 35 | Text: string; 36 | } 37 | 38 | //An example component 39 | @Component({ 40 | ... 41 | }) 42 | export class NotyListComponent{ 43 | private odata:ODataService; 44 | constructor(private odataFactory:ODataServiceFactory, ...){ 45 | this.odata = this.odataFactory.CreateService("notification"); 46 | } 47 | 48 | getOneNoty(id:int){ 49 | this.odata.Get(id).Select("Id,Text").Expand("From,To").Exec() 50 | .subscribe( 51 | singleNoty=>{...}, 52 | error=>{...} 53 | ); 54 | } 55 | 56 | 57 | getNotys(){ 58 | this.odata 59 | .Query() //Creates a query object 60 | .Top(this.top) 61 | .Skip(this.skip) 62 | .Expand("Comment,From") 63 | .OrderBy("SendDate desc") 64 | .Filter(this.filterString) 65 | .Exec() //Fires the request 66 | .subscribe( //Subscribes to Observable> 67 | notys => { 68 | this.notys = notys; //Do something with the result 69 | }, 70 | error => { 71 | ... //Local error handler 72 | }); 73 | 74 | } 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers'); 2 | const webpackConfig = require('webpack-config') 3 | 4 | module.exports = new webpackConfig.Config().extend('./config/webpack.common.js').merge({ 5 | 6 | /** 7 | * Switch loaders to debug mode. 8 | * 9 | * See: http://webpack.github.io/docs/configuration.html#debug 10 | */ 11 | debug: false, 12 | 13 | /** 14 | * Developer tool to enhance debugging 15 | * 16 | * See: http://webpack.github.io/docs/configuration.html#devtool 17 | * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps 18 | */ 19 | devtool: 'source-map', 20 | 21 | /** 22 | * Options affecting the output of the compilation. 23 | * 24 | * See: http://webpack.github.io/docs/configuration.html#output 25 | */ 26 | output: { 27 | 28 | /** 29 | * The output directory as absolute path (required). 30 | * 31 | * See: http://webpack.github.io/docs/configuration.html#output-path 32 | */ 33 | path: './build', //helpers.root('build'), 34 | 35 | /** 36 | * Specifies the name of each output file on disk. 37 | * IMPORTANT: You must not specify an absolute path here! 38 | * 39 | * See: http://webpack.github.io/docs/configuration.html#output-filename 40 | */ 41 | filename: 'index.js', 42 | 43 | /** 44 | * The filename of the SourceMaps for the JavaScript files. 45 | * They are inside the output.path directory. 46 | * 47 | * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename 48 | */ 49 | sourceMapFilename: 'index.map' 50 | }, 51 | 52 | /** 53 | * Add additional plugins to the compiler. 54 | * 55 | * See: http://webpack.github.io/docs/configuration.html#plugins 56 | */ 57 | plugins: [ 58 | ], 59 | 60 | /** 61 | * Static analysis linter for TypeScript advanced options configuration 62 | * Description: An extensible linter for the TypeScript language. 63 | * 64 | * See: https://github.com/wbuchwalter/tslint-loader 65 | */ 66 | tslint: { 67 | emitErrors: true, 68 | failOnHint: true, 69 | resourcePath: 'src' 70 | }, 71 | 72 | /* 73 | * Include polyfills or mocks for various node stuff 74 | * Description: Node configuration 75 | * 76 | * See: https://webpack.github.io/docs/configuration.html#node 77 | */ 78 | node: { 79 | global: 'window', 80 | crypto: 'empty', 81 | process: false, 82 | module: false, 83 | clearImmediate: false, 84 | setImmediate: false 85 | } 86 | 87 | }) 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-odata", 3 | "version": "0.0.23", 4 | "description": "OData service for Angular2", 5 | "main": "./build/index.js", 6 | "typings": "./build/index.d.ts", 7 | "scripts": { 8 | "typings": "typings i", 9 | "clean": "rimraf ./build", 10 | "build:tscwithclean": "npm run clean && tsc", 11 | "build": "tsc", 12 | "build:test": "tsc --project ./tsconfig-test.json", 13 | "build:webpack": "npm run clean && webpack --config config/webpack.prod.js --progress --profile --bail", 14 | "test": "karma start" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/gallayl/angular2-odata.git" 19 | }, 20 | "keywords": [ 21 | "odata", 22 | "angular", 23 | "angular2", 24 | "rest", 25 | "api", 26 | "service" 27 | ], 28 | "author": "Gallay Lajos", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/gallayl/angular2-odata/issues" 32 | }, 33 | "homepage": "https://github.com/gallayl/angular2-odata#readme", 34 | "dependencies": {}, 35 | "devDependencies": { 36 | "@angular/common": "2.1.0", 37 | "@angular/compiler": "2.1.0", 38 | "@angular/core": "2.1.0", 39 | "@angular/http": "2.1.0", 40 | "@angular/platform-browser": "2.1.0", 41 | "@angular/platform-browser-dynamic": "2.1.0", 42 | "@types/chai": "^3.4.32", 43 | "@types/jasmine": "^2.2.33", 44 | "@types/node": "^6.0.38", 45 | "awesome-typescript-loader": "2.2.1", 46 | "chai": "^3.5.0", 47 | "codelyzer": "0.0.28", 48 | "core-js": "^2.4.1", 49 | "electron-prebuilt": "^1.3.5", 50 | "inline-source-map": "^0.6.2", 51 | "istanbul-instrumenter-loader": "^0.2.0", 52 | "jasmine-core": "^2.5.2", 53 | "karma": "^1.3.0", 54 | "karma-coverage": "^1.1.1", 55 | "karma-electron-launcher": "^0.1.0", 56 | "karma-jasmine": "^1.0.2", 57 | "karma-mocha": "^1.1.1", 58 | "karma-mocha-reporter": "^2.1.0", 59 | "karma-sauce-launcher": "^1.0.0", 60 | "karma-sourcemap-loader": "^0.3.7", 61 | "karma-typescript": "^2.0.6", 62 | "karma-webpack": "^1.8.0", 63 | "mocha": "^3.0.2", 64 | "rimraf": "^2.5.4", 65 | "rxjs": "^5.0.0-rc.1", 66 | "source-map-loader": "^0.1.5", 67 | "string-replace-loader": "^1.0.3", 68 | "ts-helpers": "^1.1.1", 69 | "tslint": "^3.7.1", 70 | "tslint-loader": "^2.1.5", 71 | "typescript": "2.0.0", 72 | "typings": "^1.3.3", 73 | "webpack": "^2.1.0-beta.22", 74 | "webpack-config": "^6.1.2", 75 | "webpack-node-externals": "^1.3.3", 76 | "write-file-webpack-plugin": "^3.3.0", 77 | "zone.js": "^0.6.17" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { RequestOptions, Headers, Response } from '@angular/http'; 3 | import { PagedResult } from './query'; 4 | // import { Location } from '@angular/common'; 5 | 6 | export class KeyConfigs { 7 | public filter: string = '$filter'; 8 | public top: string = '$top'; 9 | public skip: string = '$skip'; 10 | public orderBy: string = '$orderby'; 11 | public select: string = '$select'; 12 | public expand: string = '$expand'; 13 | } 14 | 15 | @Injectable() 16 | export class ODataConfiguration { 17 | public keys: KeyConfigs = new KeyConfigs(); 18 | public baseUrl: string = 'http://localhost/odata'; 19 | 20 | 21 | public getEntityUri(entityKey: string, _typeName: string) { 22 | 23 | // check if string is a GUID (UUID) type 24 | if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(entityKey)) { 25 | return this.baseUrl + '/' + _typeName + "(" + entityKey + ")"; 26 | } 27 | 28 | if (!/^[0-9]*$/.test(entityKey)) { 29 | return this.baseUrl + '/' + _typeName + "('" + entityKey + "')"; 30 | } 31 | 32 | return this.baseUrl + '/' + _typeName + '(' + entityKey + ')'; 33 | } 34 | 35 | handleError(err: any, caught: any): void { 36 | console.warn('OData error: ', err, caught); 37 | }; 38 | 39 | get requestOptions(): RequestOptions { 40 | return new RequestOptions({ body: '' }); 41 | }; 42 | 43 | get postRequestOptions(): RequestOptions { 44 | let headers = new Headers({ 'Content-Type': 'application/json; charset=utf-8' }); 45 | return new RequestOptions({ headers: headers }); 46 | } 47 | 48 | public extractQueryResultData(res: Response): T[] { 49 | if (res.status < 200 || res.status >= 300) { 50 | throw new Error('Bad response status: ' + res.status); 51 | } 52 | let body = res.json(); 53 | let entities: T[] = body.value; 54 | return entities; 55 | } 56 | 57 | public extractQueryResultDataWithCount(res: Response): PagedResult { 58 | let pagedResult = new PagedResult(); 59 | 60 | if (res.status < 200 || res.status >= 300) { 61 | throw new Error('Bad response status: ' + res.status); 62 | } 63 | let body = res.json(); 64 | let entities: T[] = body.value; 65 | 66 | pagedResult.data = entities; 67 | 68 | try { 69 | let count = parseInt(body['@odata.count'], 10) || entities.length; 70 | pagedResult.count = count; 71 | } catch (error) { 72 | console.warn('Cannot determine OData entities count. Falling back to collection length...'); 73 | pagedResult.count = entities.length; 74 | } 75 | 76 | return pagedResult; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/odata.ts: -------------------------------------------------------------------------------- 1 | import { URLSearchParams, Http, Response, Headers, RequestOptions } from '@angular/http'; 2 | import { Observable, Operator } from 'rxjs/rx'; 3 | import { ODataConfiguration } from './config'; 4 | import { ODataQuery } from './query'; 5 | import { GetOperation } from './operation'; 6 | 7 | export class ODataService { 8 | 9 | constructor(private _typeName: string, private http: Http, private config: ODataConfiguration) { } 10 | 11 | public get TypeName(){ 12 | return this._typeName; 13 | } 14 | 15 | public Get(key: string): GetOperation { 16 | return new GetOperation(this._typeName, this.config, this.http, key); 17 | } 18 | 19 | public Post(entity: T): Observable { 20 | let body = JSON.stringify(entity); 21 | return this.handleResponse(this.http.post(this.config.baseUrl + '/' + this.TypeName, body, this.config.postRequestOptions)); 22 | } 23 | 24 | public CustomAction(key: string, actionName: string, postdata: any): Observable { 25 | let body = JSON.stringify(postdata); 26 | return this.http.post(this.getEntityUri(key) + '/' + actionName, body, this.config.requestOptions).map(resp => resp.json()); 27 | } 28 | 29 | public CustomFunction(key: string, actionName: string): Observable { 30 | return this.http.get(this.getEntityUri(key) + '/' + actionName, this.config.requestOptions).map(resp => resp.json()); 31 | } 32 | 33 | public Patch(entity: any, key: string): Observable { 34 | let body = JSON.stringify(entity); 35 | return this.http.patch(this.getEntityUri(key), body, this.config.postRequestOptions); 36 | } 37 | 38 | public Put(entity: T, key: string): Observable { 39 | let body = JSON.stringify(entity); 40 | return this.handleResponse(this.http.put(this.getEntityUri(key), body, this.config.postRequestOptions)); 41 | } 42 | 43 | public Delete(key: string): Observable { 44 | return this.http.delete(this.getEntityUri(key), this.config.requestOptions); 45 | } 46 | 47 | public Query(): ODataQuery { 48 | return new ODataQuery(this.TypeName, this.config, this.http); 49 | } 50 | 51 | protected getEntityUri(entityKey: string): string { 52 | return this.config.getEntityUri(entityKey, this._typeName); 53 | } 54 | 55 | protected handleResponse(entity: Observable): Observable { 56 | return entity.map(this.extractData) 57 | .catch((err: any, caught: Observable) => { 58 | if (this.config.handleError) this.config.handleError(err, caught); 59 | return Observable.throw(err); 60 | }); 61 | } 62 | 63 | private extractData(res: Response): T { 64 | if (res.status < 200 || res.status >= 300) { 65 | throw new Error('Bad response status: ' + res.status); 66 | } 67 | let body: any = res.json(); 68 | let entity: T = body; 69 | return entity || null; 70 | } 71 | 72 | private escapeKey() { 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: @AngularClass 3 | */ 4 | 5 | module.exports = function(config) { 6 | var testWebpackConfig = require('./webpack.test.js')({env: 'test'}); 7 | 8 | var configuration = { 9 | 10 | // base path that will be used to resolve all patterns (e.g. files, exclude) 11 | basePath: '', 12 | 13 | /* 14 | * Frameworks to use 15 | * 16 | * available frameworks: https://npmjs.org/browse/keyword/karma-adapter 17 | */ 18 | frameworks: [ 'jasmine' ], 19 | 20 | // list of files to exclude 21 | exclude: [ ], 22 | 23 | /* 24 | * list of files / patterns to load in the browser 25 | * 26 | * we are building the test environment in ./spec-bundle.js 27 | */ 28 | files: [ { pattern: './config/spec-bundle.js', watched: false } ], 29 | 30 | /* 31 | * preprocess matching files before serving them to the browser 32 | * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 33 | */ 34 | preprocessors: { './config/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] }, 35 | 36 | // Webpack Config at ./webpack.test.js 37 | webpack: testWebpackConfig, 38 | 39 | coverageReporter: { 40 | dir : 'coverage/', 41 | reporters: [ 42 | { type: 'text-summary' }, 43 | { type: 'json' }, 44 | { type: 'html' } 45 | ] 46 | }, 47 | 48 | // Webpack please don't spam the console when running in karma! 49 | webpackServer: { noInfo: false, stats: 'errors-only' }, 50 | 51 | /* 52 | * test results reporter to use 53 | * 54 | * possible values: 'dots', 'progress' 55 | * available reporters: https://npmjs.org/browse/keyword/karma-reporter 56 | */ 57 | reporters: [ 'mocha', 'coverage' ], 58 | 59 | // web server port 60 | port: 9876, 61 | 62 | // enable / disable colors in the output (reporters and logs) 63 | colors: true, 64 | 65 | /* 66 | * level of logging 67 | * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 68 | */ 69 | logLevel: config.LOG_DEBUG, 70 | 71 | // enable / disable watching file and executing tests whenever any file changes 72 | autoWatch: false, 73 | 74 | /* 75 | * start these browsers 76 | * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 77 | */ 78 | browsers: [ 79 | // 'Chrome' 80 | 'Electron' 81 | ], 82 | 83 | electronOpts: { 84 | title: 'Yoo boy!', 85 | show: false 86 | }, 87 | 88 | // customLaunchers: { 89 | // ChromeTravisCi: { 90 | // base: 'Chrome', 91 | // flags: ['--no-sandbox'] 92 | // } 93 | // }, 94 | 95 | /* 96 | * Continuous Integration mode 97 | * if true, Karma captures browsers, runs the tests and exits 98 | */ 99 | singleRun: true 100 | }; 101 | 102 | if (process.env.TRAVIS){ 103 | configuration.browsers = ['ChromeTravisCi']; 104 | } 105 | 106 | config.set(configuration); 107 | }; 108 | -------------------------------------------------------------------------------- /src/query.ts: -------------------------------------------------------------------------------- 1 | import { URLSearchParams, Http, Response } from '@angular/http'; 2 | import { Observable, Operator, Subject } from 'rxjs/rx'; 3 | import { ODataConfiguration } from './config'; 4 | import { ODataOperation } from './operation'; 5 | 6 | export class PagedResult{ 7 | public data: T[]; 8 | public count: number; 9 | } 10 | 11 | export class ODataQuery extends ODataOperation { 12 | private _filter: string; 13 | private _top: number; 14 | private _skip: number; 15 | private _orderBy: string; 16 | 17 | constructor(_typeName: string, config: ODataConfiguration, http: Http) { 18 | super(_typeName, config, http); 19 | } 20 | 21 | public Filter(filter: string): ODataQuery { 22 | this._filter = filter; 23 | return this; 24 | }; 25 | 26 | public Top(top: number): ODataQuery { 27 | this._top = top; 28 | return this; 29 | }; 30 | 31 | public Skip(skip: number): ODataQuery { 32 | this._skip = skip; 33 | return this; 34 | } 35 | 36 | public OrderBy(orderBy: string): ODataQuery { 37 | this._orderBy = orderBy; 38 | return this; 39 | } 40 | 41 | public Exec(): Observable> { 42 | let params = this.getQueryParams(); 43 | let config = this.config; 44 | return this.http.get(this.buildResourceURL(), { search: params }) 45 | .map(res => this.extractArrayData(res, config)) 46 | .catch((err: any, caught: Observable>) => { 47 | if (this.config.handleError) this.config.handleError(err, caught); 48 | return Observable.throw(err); 49 | }); 50 | } 51 | 52 | public ExecWithCount(): Observable> { 53 | let params = this.getQueryParams(); 54 | params.set('$count', 'true'); // OData v4 only 55 | let config = this.config; 56 | 57 | return this.http.get(this.buildResourceURL(), { search: params }) 58 | .map(res => this.extractArrayDataWithCount(res, config)) 59 | .catch((err: any, caught: Observable>) => { 60 | if (this.config.handleError) this.config.handleError(err, caught); 61 | return Observable.throw(err); 62 | }); 63 | } 64 | 65 | private buildResourceURL(): string { 66 | return this.config.baseUrl + '/' + this._typeName + '/'; 67 | } 68 | 69 | private getQueryParams(): URLSearchParams { 70 | let params = super.getParams(); 71 | if (this._filter) params.set(this.config.keys.filter, this._filter); 72 | if (this._top) params.set(this.config.keys.top, this._top.toString()); 73 | if (this._skip) params.set(this.config.keys.skip, this._skip.toString()); 74 | if (this._orderBy) params.set(this.config.keys.orderBy, this._orderBy); 75 | return params; 76 | } 77 | 78 | private extractArrayData(res: Response, config: ODataConfiguration): Array { 79 | return config.extractQueryResultData(res); 80 | } 81 | 82 | private extractArrayDataWithCount(res: Response, config: ODataConfiguration): PagedResult { 83 | return config.extractQueryResultDataWithCount(res); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/odataoperation.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { assert } from 'chai'; 3 | import { Observable, Operator } from 'rxjs/rx'; 4 | import { Location } from '@angular/common'; 5 | import { inject, TestBed } from '@angular/core/testing'; 6 | import { MockBackend } from '@angular/http/testing'; 7 | import { BaseRequestOptions, Http, ConnectionBackend, HttpModule } from '@angular/http'; 8 | import { IEmployee } from './helpers/employee'; 9 | import { ODataOperation } from '../operation'; 10 | import { ODataServiceFactory } from '../odataservicefactory'; 11 | import { ODataConfiguration } from '../config'; 12 | 13 | export class ODataOperationTest extends ODataOperation { 14 | public Exec(): Observable> { 15 | return Observable.of(new Array()); 16 | } 17 | } 18 | 19 | describe('ODataOperation', () => { 20 | beforeEach(() => { 21 | TestBed.configureTestingModule({ 22 | providers: [ 23 | BaseRequestOptions, 24 | MockBackend, 25 | { 26 | provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => { 27 | return new Http(backend, defaultOptions); 28 | }, 29 | deps: [MockBackend, BaseRequestOptions] 30 | }, 31 | // { 32 | // provide: Location, useFactory: () => { 33 | // return { 34 | // path: 'http://localhost/test' 35 | // }; 36 | // } 37 | // }, 38 | ODataConfiguration, 39 | ODataServiceFactory 40 | ], 41 | imports: [ 42 | HttpModule 43 | ] 44 | }); 45 | }); 46 | 47 | it('Expand(string) via injection', inject([ Http, ODataConfiguration ], (http: Http, config: ODataConfiguration) => { 48 | // Assign 49 | let test = new ODataOperationTest('Employees', config, http); 50 | 51 | // Act 52 | test.Expand('x'); 53 | 54 | // Assert 55 | assert.equal(test['_expand'], 'x'); 56 | })); 57 | 58 | it('Expand(string)', () => { 59 | // Assign 60 | let test = new ODataOperationTest('Employees', null, null); 61 | 62 | // Act 63 | test.Expand('x, y'); 64 | 65 | // Assert 66 | assert.equal(test['_expand'], 'x, y'); 67 | }); 68 | 69 | it('Expand(string[])', () => { 70 | // Assign 71 | let test = new ODataOperationTest('Employees', null, null); 72 | 73 | // Act 74 | test.Expand([ 'a', 'b' ]); 75 | 76 | // Assert 77 | assert.equal(test['_expand'], 'a,b'); 78 | }); 79 | 80 | it('Select(string)', () => { 81 | // Assign 82 | let test = new ODataOperationTest('Employees', null, null); 83 | 84 | // Act 85 | test.Select('x,y,z'); 86 | 87 | // Assert 88 | assert.equal(test['_select'], 'x,y,z'); 89 | }); 90 | 91 | it('Select(string[])', () => { 92 | // Assign 93 | let test = new ODataOperationTest('Employees', null, null); 94 | 95 | // Act 96 | test.Select([ 'a', 'b' ]); 97 | 98 | // Assert 99 | assert.equal(test['_select'], 'a,b'); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "member-access": false, 7 | "member-ordering": [ 8 | true, 9 | "public-before-private", 10 | "static-before-instance", 11 | "variables-before-functions" 12 | ], 13 | "no-any": false, 14 | "no-inferrable-types": false, 15 | "no-internal-module": true, 16 | "no-var-requires": false, 17 | "typedef": false, 18 | "typedef-whitespace": [ 19 | true, 20 | { 21 | "call-signature": "nospace", 22 | "index-signature": "nospace", 23 | "parameter": "nospace", 24 | "property-declaration": "nospace", 25 | "variable-declaration": "nospace" 26 | }, 27 | { 28 | "call-signature": "space", 29 | "index-signature": "space", 30 | "parameter": "space", 31 | "property-declaration": "space", 32 | "variable-declaration": "space" 33 | } 34 | ], 35 | 36 | "ban": false, 37 | "curly": false, 38 | "forin": true, 39 | "label-position": true, 40 | "label-undefined": true, 41 | "no-arg": true, 42 | "no-bitwise": true, 43 | "no-conditional-assignment": true, 44 | "no-console": [ 45 | true, 46 | "debug", 47 | "info", 48 | "time", 49 | "timeEnd", 50 | "trace" 51 | ], 52 | "no-construct": true, 53 | "no-debugger": true, 54 | "no-duplicate-key": true, 55 | "no-duplicate-variable": true, 56 | "no-empty": false, 57 | "no-eval": true, 58 | "no-null-keyword": false, 59 | "no-shadowed-variable": true, 60 | "no-string-literal": false, 61 | "no-switch-case-fall-through": true, 62 | "no-unreachable": true, 63 | "no-unused-expression": true, 64 | "no-unused-variable": false, 65 | "no-use-before-declare": true, 66 | "no-var-keyword": true, 67 | "radix": true, 68 | "switch-default": true, 69 | "triple-equals": [ 70 | true, 71 | "allow-null-check" 72 | ], 73 | "use-strict": [ 74 | true, 75 | "check-module" 76 | ], 77 | 78 | "eofline": true, 79 | "indent": [ 80 | true, 81 | "spaces" 82 | ], 83 | "max-line-length": [ 84 | true, 85 | 200 86 | ], 87 | "no-require-imports": false, 88 | "no-trailing-whitespace": true, 89 | "object-literal-sort-keys": false, 90 | "trailing-comma": [ 91 | true, 92 | { 93 | "multiline": false, 94 | "singleline": "never" 95 | } 96 | ], 97 | 98 | "align": false, 99 | "class-name": true, 100 | "comment-format": [ 101 | true, 102 | "check-space" 103 | ], 104 | "interface-name": false, 105 | "jsdoc-format": true, 106 | "no-consecutive-blank-lines": false, 107 | "no-constructor-vars": false, 108 | "one-line": [ 109 | true, 110 | "check-open-brace", 111 | "check-catch", 112 | "check-else", 113 | "check-finally", 114 | "check-whitespace" 115 | ], 116 | "quotemark": [ 117 | true, 118 | "single", 119 | "avoid-escape" 120 | ], 121 | "semicolon": [true, "always"], 122 | "variable-name": [ 123 | true, 124 | "check-format", 125 | "allow-leading-underscore", 126 | "ban-keywords" 127 | ], 128 | "whitespace": [ 129 | true, 130 | "check-branch", 131 | "check-decl", 132 | "check-operator", 133 | "check-separator", 134 | "check-type" 135 | ], 136 | "import-destructuring-spacing": true 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /config/karma.conf-for-karma-typescript.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function(config) { 4 | // var testWebpackConfig = require('./webpack.test.js')({env: 'test'}); 5 | 6 | var configuration = { 7 | 8 | frameworks: ["jasmine", "karma-typescript"], 9 | 10 | files: [ 11 | // { pattern: "src/**/*.ts" }, 12 | { pattern: './config/spec-bundle.js', watched: false } 13 | ], 14 | 15 | preprocessors: { 16 | // "**/*.ts": [ 'coverage', 'karma-typescript', 'sourcemap' ], 17 | './config/spec-bundle.js': [ 'coverage', 'karma-typescript', 'sourcemap' ] 18 | }, 19 | 20 | reporters: [ "mocha", "progress", "karma-typescript" ], 21 | 22 | browsers: [ "Chrome" ], 23 | 24 | // // base path that will be used to resolve all patterns (e.g. files, exclude) 25 | // basePath: '', 26 | 27 | // /* 28 | // * Frameworks to use 29 | // * 30 | // * available frameworks: https://npmjs.org/browse/keyword/karma-adapter 31 | // */ 32 | // frameworks: [ 'jasmine' ], 33 | 34 | // // list of files to exclude 35 | // exclude: [ ], 36 | 37 | // /* 38 | // * list of files / patterns to load in the browser 39 | // * 40 | // * we are building the test environment in ./spec-bundle.js 41 | // */ 42 | // files: [ { pattern: './config/spec-bundle.js', watched: false } ], 43 | 44 | // /* 45 | // * preprocess matching files before serving them to the browser 46 | // * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 47 | // */ 48 | // preprocessors: { './config/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] }, 49 | 50 | // // Webpack Config at ./webpack.test.js 51 | // webpack: testWebpackConfig, 52 | 53 | // coverageReporter: { 54 | // dir : 'coverage/', 55 | // reporters: [ 56 | // { type: 'text-summary' }, 57 | // { type: 'json' }, 58 | // { type: 'html' } 59 | // ] 60 | // }, 61 | 62 | // // Webpack please don't spam the console when running in karma! 63 | // webpackServer: { noInfo: true, stats: 'errors-only' }, 64 | 65 | // /* 66 | // * test results reporter to use 67 | // * 68 | // * possible values: 'dots', 'progress' 69 | // * available reporters: https://npmjs.org/browse/keyword/karma-reporter 70 | // */ 71 | // reporters: [ 'mocha', 'coverage' ], 72 | 73 | // // web server port 74 | // port: 9876, 75 | 76 | // enable / disable colors in the output (reporters and logs) 77 | colors: true, 78 | 79 | /* 80 | * level of logging 81 | * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 82 | */ 83 | logLevel: config.LOG_DEBUG, 84 | 85 | // // enable / disable watching file and executing tests whenever any file changes 86 | // autoWatch: false, 87 | 88 | // /* 89 | // * start these browsers 90 | // * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 91 | // */ 92 | // browsers: [ 93 | // // 'Chrome' 94 | // 'Electron' 95 | // ], 96 | 97 | // electronOpts: { 98 | // title: 'Yoo boy!', 99 | // show: false 100 | // }, 101 | 102 | // // customLaunchers: { 103 | // // ChromeTravisCi: { 104 | // // base: 'Chrome', 105 | // // flags: ['--no-sandbox'] 106 | // // } 107 | // // }, 108 | 109 | /* 110 | * Continuous Integration mode 111 | * if true, Karma captures browsers, runs the tests and exits 112 | */ 113 | singleRun: true 114 | }; 115 | 116 | if (process.env.TRAVIS){ 117 | configuration.browsers = ['ChromeTravisCi']; 118 | } 119 | 120 | config.set(configuration); 121 | }; 122 | -------------------------------------------------------------------------------- /src/test/query.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { assert } from 'chai'; 3 | import { Observable, Operator } from 'rxjs/rx'; 4 | import { Location } from '@angular/common'; 5 | import { inject, TestBed } from '@angular/core/testing'; 6 | import { MockBackend } from '@angular/http/testing'; 7 | import { BaseRequestOptions, Http, ConnectionBackend, HttpModule } from '@angular/http'; 8 | import { IEmployee } from './helpers/employee'; 9 | import { ODataQuery, PagedResult } from '../query'; 10 | import { ODataServiceFactory } from '../odataservicefactory'; 11 | import { ODataConfiguration } from '../config'; 12 | 13 | export class ODataQueryMock extends ODataQuery { 14 | public Exec(): Observable> { 15 | return Observable.of(new Array()); 16 | } 17 | 18 | public ExecWithCount(): Observable> { 19 | let p = new PagedResult(); 20 | p.count = 0; 21 | p.data = new Array(); 22 | return Observable.of(p); 23 | } 24 | } 25 | 26 | describe('ODataQuery', () => { 27 | beforeEach(() => { 28 | TestBed.configureTestingModule({ 29 | providers: [ 30 | BaseRequestOptions, 31 | MockBackend, 32 | { 33 | provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => { 34 | return new Http(backend, defaultOptions); 35 | }, 36 | deps: [MockBackend, BaseRequestOptions] 37 | }, 38 | // { 39 | // provide: Location, useFactory: () => { 40 | // return { 41 | // path: 'http://localhost/test' 42 | // }; 43 | // } 44 | // }, 45 | ODataConfiguration, 46 | ODataServiceFactory 47 | ], 48 | imports: [ 49 | HttpModule 50 | ] 51 | }); 52 | }); 53 | 54 | // it('Exec', inject([ Http, ODataConfiguration ], (http: Http, config: ODataConfiguration) => { 55 | // // Assign 56 | // let query = new ODataQueryMock('Employees', config, http); 57 | 58 | // // Act 59 | // query 60 | // .Filter('x') 61 | // .Top(10) 62 | // .Skip(20) 63 | // .OrderBy('x'); 64 | 65 | // let result = query.Exec(); 66 | 67 | // // Assert 68 | // // spyOn(http, 'get'); 69 | // // expect(http.get).toHaveBeenCalled(); 70 | // })); 71 | 72 | it('Filter(string)', inject([ Http, ODataConfiguration ], (http: Http, config: ODataConfiguration) => { 73 | // Assign 74 | let test = new ODataQueryMock('Employees', config, http); 75 | 76 | // Act 77 | test.Filter('x'); 78 | 79 | // Assert 80 | assert.equal(test['_filter'], 'x'); 81 | })); 82 | 83 | it('OrderBy(string)', inject([ Http, ODataConfiguration ], (http: Http, config: ODataConfiguration) => { 84 | // Assign 85 | let test = new ODataQueryMock('Employees', config, http); 86 | 87 | // Act 88 | test.OrderBy('o'); 89 | 90 | // Assert 91 | assert.equal(test['_orderBy'], 'o'); 92 | })); 93 | 94 | it('Skip(number)', inject([ Http, ODataConfiguration ], (http: Http, config: ODataConfiguration) => { 95 | // Assign 96 | let test = new ODataQueryMock('Employees', config, http); 97 | 98 | // Act 99 | test.Skip(10); 100 | 101 | // Assert 102 | assert.equal(test['_skip'], 10); 103 | })); 104 | 105 | it('Top(number)', inject([ Http, ODataConfiguration ], (http: Http, config: ODataConfiguration) => { 106 | // Assign 107 | let test = new ODataQueryMock('Employees', config, http); 108 | 109 | // Act 110 | test.Top(20); 111 | 112 | // Assert 113 | assert.equal(test['_top'], 20); 114 | })); 115 | }); 116 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const helpers = require('./helpers'); 3 | var webpackConfig = require('webpack-config'); 4 | const nodeExternals = require('webpack-node-externals'); 5 | const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'); 6 | 7 | /* 8 | * Webpack configuration 9 | * 10 | * See: http://webpack.github.io/docs/configuration.html#cli 11 | */ 12 | module.exports = webpackConfig.Config().merge({ 13 | /* 14 | * The entry point for the bundle 15 | * 16 | * See: http://webpack.github.io/docs/configuration.html#entry 17 | */ 18 | entry: { 19 | 'main': './src/index.ts' 20 | }, 21 | 22 | externals: [nodeExternals()], 23 | 24 | /* 25 | * Options affecting the resolving of modules. 26 | * 27 | * See: http://webpack.github.io/docs/configuration.html#resolve 28 | */ 29 | resolve: { 30 | 31 | /* 32 | * An array of extensions that should be used to resolve modules. 33 | * 34 | * See: http://webpack.github.io/docs/configuration.html#resolve-extensions 35 | */ 36 | extensions: ['', '.ts', '.js', '.json'], 37 | 38 | // Make sure root is src 39 | root: helpers.root('src'), 40 | 41 | // remove other default values 42 | modulesDirectories: ['node_modules'] 43 | }, 44 | 45 | /* 46 | * Options affecting the normal modules. 47 | * 48 | * See: http://webpack.github.io/docs/configuration.html#module 49 | */ 50 | module: { 51 | 52 | /* 53 | * An array of applied pre and post loaders. 54 | * 55 | * See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders 56 | */ 57 | preLoaders: [ 58 | { 59 | test: /\.ts$/, 60 | loader: 'string-replace-loader', 61 | query: { 62 | search: '(System|SystemJS)(.*[\\n\\r]\\s*\\.|\\.)import\\((.+)\\)', 63 | replace: '$1.import($3).then(mod => (mod.__esModule && mod.default) ? mod.default : mod)', 64 | flags: 'g' 65 | }, 66 | include: [helpers.root('src')] 67 | }, 68 | 69 | ], 70 | 71 | /* 72 | * An array of automatically applied loaders. 73 | * 74 | * IMPORTANT: The loaders here are resolved relative to the resource which they are applied to. 75 | * This means they are not resolved relative to the configuration file. 76 | * 77 | * See: http://webpack.github.io/docs/configuration.html#module-loaders 78 | */ 79 | loaders: [ 80 | 81 | /* 82 | * Typescript loader support for .ts and Angular 2 async routes via .async.ts 83 | * 84 | * See: https://github.com/s-panferov/awesome-typescript-loader 85 | */ 86 | { 87 | test: /\.ts$/, 88 | loaders: [ 89 | 'awesome-typescript-loader' 90 | ], 91 | exclude: [/\.(spec|e2e)\.ts$/] 92 | }, 93 | 94 | /* 95 | * Json loader support for *.json files. 96 | * 97 | * See: https://github.com/webpack/json-loader 98 | */ 99 | { 100 | test: /\.json$/, 101 | loader: 'json-loader' 102 | } 103 | ] 104 | 105 | }, 106 | 107 | /* 108 | * Add additional plugins to the compiler. 109 | * 110 | * See: http://webpack.github.io/docs/configuration.html#plugins 111 | */ 112 | plugins: [ 113 | /** 114 | * Plugin: ContextReplacementPlugin 115 | * Description: Provides context to Angular's use of System.import 116 | * 117 | * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin 118 | * See: https://github.com/angular/angular/issues/11580 119 | */ 120 | new ContextReplacementPlugin( 121 | // The (\\|\/) piece accounts for path separators in *nix and Windows 122 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 123 | helpers.root('src') // location of your src 124 | ) 125 | ], 126 | 127 | /* 128 | * Include polyfills or mocks for various node stuff 129 | * Description: Node configuration 130 | * 131 | * See: https://webpack.github.io/docs/configuration.html#node 132 | */ 133 | node: { 134 | global: 'window', 135 | crypto: 'empty', 136 | process: true, 137 | module: false, 138 | clearImmediate: false, 139 | setImmediate: false 140 | } 141 | }); 142 | -------------------------------------------------------------------------------- /src/operation.ts: -------------------------------------------------------------------------------- 1 | import { URLSearchParams, Http, Response, RequestOptions } from '@angular/http'; 2 | import { Observable, Operator } from 'rxjs/rx'; 3 | import { ODataConfiguration } from './config'; 4 | 5 | export abstract class ODataOperation { 6 | private _expand: string; 7 | private _select: string; 8 | 9 | constructor(protected _typeName: string, 10 | protected config: ODataConfiguration, 11 | protected http: Http) { } 12 | 13 | public Expand(expand: string | string[]) { 14 | this._expand = this.parseStringOrStringArray(expand); 15 | return this; 16 | } 17 | 18 | public Select(select: string | string[]) { 19 | this._select = this.parseStringOrStringArray(select); 20 | return this; 21 | } 22 | 23 | protected getParams(): URLSearchParams { 24 | let params = new URLSearchParams(); 25 | if (this._select && this._select.length > 0) params.set(this.config.keys.select, this._select); 26 | if (this._expand && this._expand.length > 0) params.set(this.config.keys.expand, this._expand); 27 | return params; 28 | } 29 | 30 | protected handleResponse(entity: Observable): Observable { 31 | return entity.map(this.extractData) 32 | .catch((err: any, caught: Observable) => { 33 | if (this.config.handleError) this.config.handleError(err, caught); 34 | return Observable.throw(err); 35 | }); 36 | } 37 | 38 | protected getEntityUri(entityKey: string): string { 39 | return this.config.getEntityUri(entityKey, this._typeName); 40 | } 41 | 42 | protected getRequestOptions(): RequestOptions { 43 | let options = this.config.requestOptions; 44 | options.search = this.getParams(); 45 | return options; 46 | } 47 | 48 | abstract Exec(...args): Observable; 49 | 50 | protected parseStringOrStringArray(input: string | string[]): string { 51 | if (input instanceof Array) { 52 | return input.join(','); 53 | } 54 | 55 | return input as string; 56 | } 57 | 58 | private extractData(res: Response): T { 59 | if (res.status < 200 || res.status >= 300) { 60 | throw new Error('Bad response status: ' + res.status); 61 | } 62 | let body: any = res.json(); 63 | let entity: T = body; 64 | return entity || null; 65 | } 66 | } 67 | 68 | export abstract class OperationWithKey extends ODataOperation { 69 | constructor(protected _typeName: string, 70 | protected config: ODataConfiguration, 71 | protected http: Http, 72 | protected key: string) { 73 | super(_typeName, config, http); 74 | } 75 | abstract Exec(...args): Observable; 76 | } 77 | 78 | export abstract class OperationWithEntity extends ODataOperation { 79 | constructor(protected _typeName: string, 80 | protected config: ODataConfiguration, 81 | protected http: Http, 82 | protected entity: T) { 83 | super(_typeName, config, http); 84 | } 85 | abstract Exec(...args): Observable; 86 | } 87 | 88 | export abstract class OperationWithKeyAndEntity extends ODataOperation { 89 | constructor(protected _typeName: string, 90 | protected config: ODataConfiguration, 91 | protected http: Http, 92 | protected key: string, 93 | protected entity: T) { 94 | super(_typeName, config, http); 95 | } 96 | abstract Exec(...args): Observable; 97 | } 98 | 99 | 100 | export class GetOperation extends OperationWithKey { 101 | 102 | public Exec(): Observable { 103 | return super.handleResponse(this.http.get(this.getEntityUri(this.key), this.getRequestOptions())); 104 | } 105 | } 106 | 107 | // export class PostOperation extends OperationWithEntity{ 108 | // public Exec():Observable{ //ToDo: Check ODataV4 109 | // let body = JSON.stringify(this.entity); 110 | // return this.handleResponse(this.http.post(this.config.baseUrl + "/"+this._typeName, body, this.getRequestOptions())); 111 | // } 112 | // } 113 | 114 | // export class PatchOperation extends OperationWithKeyAndEntity{ 115 | // public Exec():Observable{ //ToDo: Check ODataV4 116 | // let body = JSON.stringify(this.entity); 117 | // return this.http.patch(this.getEntityUri(this.key),body,this.getRequestOptions()); 118 | // } 119 | // } 120 | 121 | // export class PutOperation extends OperationWithKeyAndEntity{ 122 | // public Exec(){ 123 | // let body = JSON.stringify(this.entity); 124 | // return this.handleResponse(this.http.put(this.getEntityUri(this.key),body,this.getRequestOptions())); 125 | // } 126 | // } 127 | -------------------------------------------------------------------------------- /config/webpack.test.js: -------------------------------------------------------------------------------- 1 | const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'); 2 | const helpers = require('./helpers'); 3 | 4 | /** 5 | * Webpack configuration 6 | * 7 | * See: http://webpack.github.io/docs/configuration.html#cli 8 | */ 9 | module.exports = function (options) { 10 | return { 11 | 12 | /** 13 | * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack 14 | * 15 | * Do not change, leave as is or it wont work. 16 | * See: https://github.com/webpack/karma-webpack#source-maps 17 | */ 18 | devtool: 'inline-source-map', 19 | 20 | /** 21 | * Options affecting the resolving of modules. 22 | * 23 | * See: http://webpack.github.io/docs/configuration.html#resolve 24 | */ 25 | resolve: { 26 | 27 | /** 28 | * An array of extensions that should be used to resolve modules. 29 | * 30 | * See: http://webpack.github.io/docs/configuration.html#resolve-extensions 31 | */ 32 | extensions: ['', '.ts', '.js'], 33 | 34 | /** 35 | * Make sure root is src 36 | */ 37 | root: helpers.root('src') 38 | }, 39 | 40 | /** 41 | * Options affecting the normal modules. 42 | * 43 | * See: http://webpack.github.io/docs/configuration.html#module 44 | */ 45 | module: { 46 | 47 | /** 48 | * An array of applied pre and post loaders. 49 | * 50 | * See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders 51 | */ 52 | preLoaders: [ 53 | 54 | /** 55 | * Tslint loader support for *.ts files 56 | * 57 | * See: https://github.com/wbuchwalter/tslint-loader 58 | */ 59 | { 60 | test: /\.ts$/, 61 | loader: 'tslint-loader', 62 | exclude: [helpers.root('node_modules')] 63 | }, 64 | 65 | /** 66 | * Source map loader support for *.js files 67 | * Extracts SourceMaps for source files that as added as sourceMappingURL comment. 68 | * 69 | * See: https://github.com/webpack/source-map-loader 70 | */ 71 | { 72 | test: /\.js$/, 73 | loader: 'source-map-loader', 74 | exclude: [ 75 | // these packages have problems with their sourcemaps 76 | helpers.root('node_modules/Rxjs'), 77 | helpers.root('node_modules/@angular') 78 | ] 79 | } 80 | ], 81 | 82 | /** 83 | * An array of automatically applied loaders. 84 | * 85 | * IMPORTANT: The loaders here are resolved relative to the resource which they are applied to. 86 | * This means they are not resolved relative to the configuration file. 87 | * 88 | * See: http://webpack.github.io/docs/configuration.html#module-loaders 89 | */ 90 | loaders: [ 91 | 92 | /** 93 | * Typescript loader support for .ts and Angular 2 async routes via .async.ts 94 | * 95 | * See: https://github.com/s-panferov/awesome-typescript-loader 96 | */ 97 | { 98 | test: /\.ts$/, 99 | loader: 'awesome-typescript-loader', 100 | exclude: [/\.e2e\.ts$/] 101 | }, 102 | 103 | /** 104 | * Json loader support for *.json files. 105 | * 106 | * See: https://github.com/webpack/json-loader 107 | */ 108 | { test: /\.json$/, loader: 'json-loader' } 109 | ], 110 | 111 | /** 112 | * An array of applied pre and post loaders. 113 | * 114 | * See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders 115 | */ 116 | postLoaders: [ 117 | 118 | /** 119 | * Instruments JS files with Istanbul for subsequent code coverage reporting. 120 | * Instrument only testing sources. 121 | * 122 | * See: https://github.com/deepsweet/istanbul-instrumenter-loader 123 | */ 124 | { 125 | test: /\.(js|ts)$/, loader: 'istanbul-instrumenter-loader', 126 | include: helpers.root('src'), 127 | exclude: [ 128 | /\.(e2e|spec)\.ts$/, 129 | /node_modules/ 130 | ] 131 | } 132 | ] 133 | }, 134 | 135 | /** 136 | * Add additional plugins to the compiler. 137 | * 138 | * See: http://webpack.github.io/docs/configuration.html#plugins 139 | */ 140 | plugins: [ 141 | new ContextReplacementPlugin( 142 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 143 | __dirname 144 | ), 145 | ], 146 | 147 | /** 148 | * Static analysis linter for TypeScript advanced options configuration 149 | * Description: An extensible linter for the TypeScript language. 150 | * 151 | * See: https://github.com/wbuchwalter/tslint-loader 152 | */ 153 | tslint: { 154 | emitErrors: false, 155 | failOnHint: false, 156 | resourcePath: 'src' 157 | }, 158 | 159 | /** 160 | * Include polyfills or mocks for various node stuff 161 | * Description: Node configuration 162 | * 163 | * See: https://webpack.github.io/docs/configuration.html#node 164 | */ 165 | node: { 166 | global: 'window', 167 | process: false, 168 | crypto: 'empty', 169 | module: false, 170 | clearImmediate: false, 171 | setImmediate: false 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: @AngularClass 3 | */ 4 | 5 | const helpers = require('./helpers'); 6 | const webpackMerge = require('webpack-merge'); // used to merge webpack configs 7 | const commonConfig = require('./webpack.common.js'); // the settings that are common to prod and dev 8 | 9 | /** 10 | * Webpack Plugins 11 | */ 12 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 13 | const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin'); 14 | 15 | /** 16 | * Webpack Constants 17 | */ 18 | const ENV = process.env.ENV = process.env.NODE_ENV = 'development'; 19 | const HOST = process.env.HOST || 'localhost'; 20 | const PORT = process.env.PORT || 3000; 21 | const HMR = helpers.hasProcessFlag('hot'); 22 | const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, { 23 | host: HOST, 24 | port: PORT, 25 | ENV: ENV, 26 | HMR: HMR 27 | }); 28 | 29 | /** 30 | * Webpack configuration 31 | * 32 | * See: http://webpack.github.io/docs/configuration.html#cli 33 | */ 34 | module.exports = function(options) { 35 | return webpackMerge(commonConfig({env: ENV}), { 36 | 37 | /** 38 | * Merged metadata from webpack.common.js for index.html 39 | * 40 | * See: (custom attribute) 41 | */ 42 | metadata: METADATA, 43 | 44 | /** 45 | * Switch loaders to debug mode. 46 | * 47 | * See: http://webpack.github.io/docs/configuration.html#debug 48 | */ 49 | debug: true, 50 | 51 | /** 52 | * Developer tool to enhance debugging 53 | * 54 | * See: http://webpack.github.io/docs/configuration.html#devtool 55 | * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps 56 | */ 57 | devtool: 'cheap-module-source-map', 58 | 59 | /** 60 | * Options affecting the output of the compilation. 61 | * 62 | * See: http://webpack.github.io/docs/configuration.html#output 63 | */ 64 | output: { 65 | 66 | /** 67 | * The output directory as absolute path (required). 68 | * 69 | * See: http://webpack.github.io/docs/configuration.html#output-path 70 | */ 71 | path: helpers.root('dist'), 72 | 73 | /** 74 | * Specifies the name of each output file on disk. 75 | * IMPORTANT: You must not specify an absolute path here! 76 | * 77 | * See: http://webpack.github.io/docs/configuration.html#output-filename 78 | */ 79 | filename: '[name].bundle.js', 80 | 81 | /** 82 | * The filename of the SourceMaps for the JavaScript files. 83 | * They are inside the output.path directory. 84 | * 85 | * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename 86 | */ 87 | sourceMapFilename: '[name].map', 88 | 89 | /** The filename of non-entry chunks as relative path 90 | * inside the output.path directory. 91 | * 92 | * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename 93 | */ 94 | chunkFilename: '[id].chunk.js', 95 | 96 | library: 'ac_[name]', 97 | libraryTarget: 'var', 98 | }, 99 | 100 | plugins: [ 101 | 102 | /** 103 | * Plugin: DefinePlugin 104 | * Description: Define free variables. 105 | * Useful for having development builds with debug logging or adding global constants. 106 | * 107 | * Environment helpers 108 | * 109 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin 110 | */ 111 | // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts 112 | new DefinePlugin({ 113 | 'ENV': JSON.stringify(METADATA.ENV), 114 | 'HMR': METADATA.HMR, 115 | 'process.env': { 116 | 'ENV': JSON.stringify(METADATA.ENV), 117 | 'NODE_ENV': JSON.stringify(METADATA.ENV), 118 | 'HMR': METADATA.HMR, 119 | } 120 | }), 121 | 122 | /** 123 | * Plugin: NamedModulesPlugin (experimental) 124 | * Description: Uses file names as module name. 125 | * 126 | * See: https://github.com/webpack/webpack/commit/a04ffb928365b19feb75087c63f13cadfc08e1eb 127 | */ 128 | new NamedModulesPlugin(), 129 | 130 | ], 131 | 132 | /** 133 | * Static analysis linter for TypeScript advanced options configuration 134 | * Description: An extensible linter for the TypeScript language. 135 | * 136 | * See: https://github.com/wbuchwalter/tslint-loader 137 | */ 138 | tslint: { 139 | emitErrors: false, 140 | failOnHint: false, 141 | resourcePath: 'src' 142 | }, 143 | 144 | /** 145 | * Webpack Development Server configuration 146 | * Description: The webpack-dev-server is a little node.js Express server. 147 | * The server emits information about the compilation state to the client, 148 | * which reacts to those events. 149 | * 150 | * See: https://webpack.github.io/docs/webpack-dev-server.html 151 | */ 152 | devServer: { 153 | port: METADATA.port, 154 | host: METADATA.host, 155 | historyApiFallback: true, 156 | watchOptions: { 157 | aggregateTimeout: 300, 158 | poll: 1000 159 | }, 160 | outputPath: helpers.root('dist') 161 | }, 162 | 163 | /* 164 | * Include polyfills or mocks for various node stuff 165 | * Description: Node configuration 166 | * 167 | * See: https://webpack.github.io/docs/configuration.html#node 168 | */ 169 | node: { 170 | global: 'window', 171 | crypto: 'empty', 172 | process: true, 173 | module: false, 174 | clearImmediate: false, 175 | setImmediate: false 176 | } 177 | 178 | }); 179 | } 180 | --------------------------------------------------------------------------------