diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b2c57befc0..e58f8a7e10 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -220,6 +220,12 @@ "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.41.tgz", "dev": true }, + "@types/jsonschema": { + "version": "0.0.5", + "from": "@types/jsonschema@0.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonschema/-/jsonschema-0.0.5.tgz", + "dev": true + }, "@types/lodash": { "version": "4.14.50", "from": "@types/lodash@>=4.14.37 <5.0.0", @@ -951,6 +957,11 @@ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "dev": true }, + "cerialize": { + "version": "0.1.13", + "from": "cerialize@>=0.1.13 <0.2.0", + "resolved": "https://registry.npmjs.org/cerialize/-/cerialize-0.1.13.tgz" + }, "chalk": { "version": "1.1.3", "from": "chalk@>=1.1.3 <2.0.0", @@ -3150,6 +3161,11 @@ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", "dev": true }, + "jsonschema": { + "version": "1.1.1", + "from": "jsonschema@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.1.1.tgz" + }, "jsprim": { "version": "1.3.1", "from": "jsprim@>=1.2.2 <2.0.0", diff --git a/package.json b/package.json index 79dc2b0c3f..8715037a40 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@ngrx/router-store": "^1.2.5", "@ngrx/store": "^2.2.1", "@ngrx/store-devtools": "^3.2.2", + "@types/jsonschema": "0.0.5", "angular2-express-engine": "2.1.0-rc.1", "angular2-platform-node": "2.1.0-rc.1", "angular2-universal": "2.1.0-rc.1", @@ -90,11 +91,13 @@ "autoprefixer": "^6.5.4", "body-parser": "1.15.2", "bootstrap": "4.0.0-alpha.5", + "cerialize": "^0.1.13", "compression": "1.6.2", "express": "4.14.0", "font-awesome": "4.7.0", "http-server": "^0.9.0", "js.clone": "0.0.3", + "jsonschema": "^1.1.1", "methods": "1.1.2", "morgan": "1.7.0", "ng2-translate": "4.2.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8927e040f5..e65c2a2c41 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -25,8 +25,8 @@ import { effects } from './app.effects'; ], imports: [ SharedModule, - CoreModule, HomeModule, + CoreModule.forRoot(), AppRoutingModule, /** * StoreModule.provideStore is imported once in the root module, accepting a reducer diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 2d7a39562e..724dc969fb 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -1,18 +1,45 @@ -import { NgModule } from '@angular/core'; +import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { SharedModule } from "../shared/shared.module"; +import { isNotEmpty } from "../shared/empty.util"; +import { FooterComponent } from "./footer/footer.component"; -import { SharedModule } from '../shared/shared.module'; +const IMPORTS = [ + CommonModule, + SharedModule +]; -import { FooterComponent } from './footer/footer.component'; +const DECLARATIONS = [ + FooterComponent +]; + +const EXPORTS = [ + FooterComponent +]; + +const PROVIDERS = [ +]; @NgModule({ - imports: [ - CommonModule, // we use ngFor - SharedModule - ], - exports: [FooterComponent], - declarations: [FooterComponent], - providers: [] + imports: [ ...IMPORTS ], + declarations: [...DECLARATIONS], + exports: [...EXPORTS], + providers: [...PROVIDERS] }) +export class CoreModule { + constructor (@Optional() @SkipSelf() parentModule: CoreModule) { + if (isNotEmpty(parentModule)) { + throw new Error( + 'CoreModule is already loaded. Import it in the AppModule only'); + } + } -export class CoreModule { } + static forRoot(): ModuleWithProviders { + return { + ngModule: CoreModule, + providers: [ + ...PROVIDERS + ] + }; + } +} diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2-response.model.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2-response.model.ts new file mode 100644 index 0000000000..80567bdf7c --- /dev/null +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2-response.model.ts @@ -0,0 +1,4 @@ +export interface DSpaceRESTV2Response { + _embedded?: any; + _links?: any; +} diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.schema.json b/src/app/core/dspace-rest-v2/dspace-rest-v2.schema.json new file mode 100644 index 0000000000..8a7a0bcdc9 --- /dev/null +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.schema.json @@ -0,0 +1,93 @@ +{ + "title": "DSpace REST v2 json-schema", + "description": "Based on http://hyperschema.org/mediatypes/hal", + "type": "object", + "properties": { + "_links": { + "$ref": "#/definitions/links" + }, + "_embedded": { + "$ref": "#/definitions/embedded" + } + }, + "definitions": { + "links": { + "title": "HAL Links", + "description": "Object of links with the rels as the keys", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/linkObject" + }, + { + "$ref": "#/definitions/linkArray" + } + ] + } + }, + "linkArray": { + "title": "HAL Link Array", + "description": "An array of linkObjects of the same link relation", + "type": "array", + "items": { + "$ref": "#/definitions/linkObject" + } + }, + "linkObject": { + "title": "HAL Link Object", + "description": "An object with link information", + "type": "object", + "properties": { + "name": { + "$ref": "http://hyperschema.org/core/base#/definitions/name" + }, + "href": { + "anyOf": [ + { + "$ref": "http://hyperschema.org/core/link#/definitions/href" + }, + { + "$ref": "http://hyperschema.org/core/link#/definitions/hrefTemplated" + } + ] + }, + "templated": { + "$ref": "http://hyperschema.org/core/link#/definitions/isTemplated" + }, + "type": { + "$ref": "http://hyperschema.org/core/base#/definitions/mediaType" + }, + "deprecation": { + "$ref": "http://hyperschema.org/core/link#/definitions/isDeprecated" + } + }, + "required": [ + "href" + ] + }, + "embedded": { + "title": "HAL Embedded Resource", + "description": "An embedded HAL resource", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/embeddedArray" + } + ] + } + }, + "embeddedArray": { + "title": "HAL Embedded Array", + "description": "An array of embedded resources with the same link relation", + "type": "array", + "items": { + "$ref": "#" + } + } + } +} diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.serializer.spec.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.serializer.spec.ts new file mode 100644 index 0000000000..2661b3708d --- /dev/null +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.serializer.spec.ts @@ -0,0 +1,201 @@ +import { DSpaceRESTv2Serializer } from "./dspace-rest-v2.serializer"; +import { autoserialize, autoserializeAs } from "cerialize"; + +class TestModel { + @autoserialize + id: string; + + @autoserialize + name: string; + + @autoserializeAs(TestModel) + parents?: Array; +} + +const testModels = [ + { + "id": "d4466d54-d73b-4d8f-b73f-c702020baa14", + "name": "Model 1", + }, + { + "id": "752a1250-949a-46ad-9bea-fbc45f0b656d", + "name": "Model 2", + } +]; + +const testResponses = [ + { + "_links": { + "self": "/testmodels/9e32a2e2-6b91-4236-a361-995ccdc14c60", + "parents": [ + { "href": "/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78" }, + { "href": "/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5" } + ] + }, + "id": "9e32a2e2-6b91-4236-a361-995ccdc14c60", + "type": "testModels", + "name": "A Test Model" + }, + { + "_links": { + "self": "/testmodels/598ce822-c357-46f3-ab70-63724d02d6ad", + "parents": [ + { "href": "/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5" }, + { "href": "/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78" } + ] + }, + "id": "598ce822-c357-46f3-ab70-63724d02d6ad", + "type": "testModels", + "name": "Another Test Model" + } +]; + +const parentHrefRegex = /^\/testmodels\/(.+)$/g; + + +describe("DSpaceRESTv2Serializer", () => { + + describe("serialize", () => { + + it("should turn a model in to a valid document", () => { + const serializer = new DSpaceRESTv2Serializer(TestModel); + const doc = serializer.serialize(testModels[0]); + expect(testModels[0].id).toBe(doc._embedded.id); + expect(testModels[0].name).toBe(doc._embedded.name); + }); + + }); + + describe("serializeArray", () => { + + it("should turn an array of models in to a valid document", () => { + const serializer = new DSpaceRESTv2Serializer(TestModel); + const doc = serializer.serializeArray(testModels); + + expect(testModels[0].id).toBe(doc._embedded[0].id); + expect(testModels[0].name).toBe(doc._embedded[0].name); + expect(testModels[1].id).toBe(doc._embedded[1].id); + expect(testModels[1].name).toBe(doc._embedded[1].name); + }); + + }); + + describe("deserialize", () => { + + it("should turn a valid document describing a single entity in to a valid model", () => { + const serializer = new DSpaceRESTv2Serializer(TestModel); + const doc = { + "_embedded": testResponses[0], + }; + + const model = serializer.deserialize(doc); + + expect(model.id).toBe(doc._embedded.id); + expect(model.name).toBe(doc._embedded.name); + }); + + //TODO cant implement/test this yet - depends on how relationships + // will be handled in the rest api + // it("should retain relationship information", () => { + // const serializer = new DSpaceRESTv2Serializer(TestModel); + // const doc = { + // "_embedded": testResponses[0], + // }; + // + // const model = serializer.deserialize(doc); + // + // console.log(model); + // + // const modelParentIds = model.parents.map(parent => parent.id).sort(); + // const responseParentIds = doc._embedded._links.parents + // .map(parent => parent.href) + // .map(href => href.replace(parentHrefRegex, '$1')) + // .sort(); + // + // expect(modelParentIds).toEqual(responseParentIds); + // }); + + // TODO enable once validation is enabled in the serializer + // it("should throw an error when dealing with an invalid document", () => { + // const serializer = new DSpaceRESTv2Serializer(TestModel); + // const doc = testResponses[0]; + // + // expect(() => { + // serializer.deserialize(doc); + // }).toThrow(); + // }); + + it("should throw an error when dealing with a document describing an array", () => { + const serializer = new DSpaceRESTv2Serializer(TestModel); + const doc = { + "_embedded": testResponses + }; + + expect(() => { + serializer.deserialize(doc); + }).toThrow(); + }); + + }); + + describe("deserializeArray", () => { + + it("should turn a valid document describing a collection of objects in to an array of valid models", () => { + const serializer = new DSpaceRESTv2Serializer(TestModel); + const doc = { + "_embedded": testResponses + }; + + const models = serializer.deserializeArray(doc); + + expect(models[0].id).toBe(doc._embedded[0].id); + expect(models[0].name).toBe(doc._embedded[0].name); + expect(models[1].id).toBe(doc._embedded[1].id); + expect(models[1].name).toBe(doc._embedded[1].name); + }); + + //TODO cant implement/test this yet - depends on how relationships + // will be handled in the rest api + // it("should retain relationship information", () => { + // const serializer = new DSpaceRESTv2Serializer(TestModel); + // const doc = { + // "_embedded": testResponses, + // }; + // + // const models = serializer.deserializeArray(doc); + // + // models.forEach((model, i) => { + // const modelParentIds = model.parents.map(parent => parent.id).sort(); + // const responseParentIds = doc._embedded[i]._links.parents + // .map(parent => parent.href) + // .map(href => href.replace(parentHrefRegex, '$1')) + // .sort(); + // + // expect(modelParentIds).toEqual(responseParentIds); + // }); + // }); + + // TODO enable once validation is enabled in the serializer + // it("should throw an error when dealing with an invalid document", () => { + // const serializer = new DSpaceRESTv2Serializer(TestModel); + // const doc = testResponses[0]; + // + // expect(() => { + // serializer.deserializeArray(doc); + // }).toThrow(); + // }); + + it("should throw an error when dealing with a document describing a single model", () => { + const serializer = new DSpaceRESTv2Serializer(TestModel); + const doc = { + "_embedded": testResponses[0] + }; + + expect(() => { + serializer.deserializeArray(doc); + }).toThrow(); + }); + + }); + +}); diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.serializer.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.serializer.ts new file mode 100644 index 0000000000..3f457daa94 --- /dev/null +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.serializer.ts @@ -0,0 +1,82 @@ +import { Serialize, Deserialize } from "cerialize"; +import { Serializer } from "../serializer"; +import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model"; +import { DSpaceRESTv2Validator } from "./dspace-rest-v2.validator"; + +/** + * ensures we can use 'typeof T' as a type + * more details: + * https://github.com/Microsoft/TypeScript/issues/204#issuecomment-257722306 + */ +type Constructor = { new (...args: any[]): T } | ((...args: any[]) => T) | Function; + +/** + * This Serializer turns responses from v2 of DSpace's REST API + * to models and vice versa + */ +export class DSpaceRESTv2Serializer implements Serializer { + + /** + * Create a new DSpaceRESTv2Serializer instance + * + * @param modelType a class or interface to indicate + * the kind of model this serializer should work with + */ + constructor(private modelType: Constructor) { + } + + /** + * Convert a model in to the format expected by the backend + * + * @param model The model to serialize + * @returns An object to send to the backend + */ + serialize(model: T): DSpaceRESTV2Response { + return { + "_embedded": Serialize(model, this.modelType) + }; + } + + /** + * Convert an array of models in to the format expected by the backend + * + * @param models The array of models to serialize + * @returns An object to send to the backend + */ + serializeArray(models: Array): DSpaceRESTV2Response { + return { + "_embedded": Serialize(models, this.modelType) + }; + } + + /** + * Convert a response from the backend in to a model. + * + * @param response An object returned by the backend + * @returns a model of type T + */ + deserialize(response: DSpaceRESTV2Response): T { + // TODO enable validation, once rest data stabilizes + // new DSpaceRESTv2Validator(response).validate(); + if (Array.isArray(response._embedded)) { + throw new Error('Expected a single model, use deserializeArray() instead'); + } + return Deserialize(response._embedded, this.modelType); + } + + /** + * Convert a response from the backend in to an array of models + * + * @param response An object returned by the backend + * @returns an array of models of type T + */ + deserializeArray(response: DSpaceRESTV2Response): Array { + //TODO enable validation, once rest data stabilizes + // new DSpaceRESTv2Validator(response).validate(); + if (!Array.isArray(response._embedded)) { + throw new Error('Expected an Array, use deserialize() instead'); + } + return > Deserialize(response._embedded, this.modelType); + } + +} diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.validator.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.validator.ts new file mode 100644 index 0000000000..9cc88f1536 --- /dev/null +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.validator.ts @@ -0,0 +1,34 @@ +import * as schema from './dspace-rest-v2.schema.json' +import { Validator } from "jsonschema"; + +/** + * Verifies a document is a valid response from + * a DSpace REST API v2 + */ +export class DSpaceRESTv2Validator { + + constructor(private document: any) { + + } + + /** + * Throws an exception if this.document isn't a valid response from + * a DSpace REST API v2. Succeeds otherwise. + */ + validate(): void { + const validator = new Validator(); + const result = validator.validate(this.document, schema); + if (!result.valid) { + if (result.errors && result.errors.length > 0) { + const message = result.errors + .map((error) => error.message) + .join("\n"); + throw new Error(message); + } + else { + throw new Error("JSON API validation failed for an unknown reason"); + } + } + } + +} diff --git a/src/app/core/serializer.ts b/src/app/core/serializer.ts new file mode 100644 index 0000000000..27cdb38577 --- /dev/null +++ b/src/app/core/serializer.ts @@ -0,0 +1,38 @@ +/** + * A Serializer turns responses from the backend to models + * and vice versa + */ +export interface Serializer { + + /** + * Convert a model in to the format expected by the backend + * + * @param model The model to serialize + * @returns An object to send to the backend + */ + serialize(model: T): any; + + /** + * Convert an array of models in to the format expected by the backend + * + * @param models The array of models to serialize + * @returns An object to send to the backend + */ + serializeArray(models: Array): any; + + /** + * Convert a response from the backend in to a model. + * + * @param response An object returned by the backend + * @returns a model of type T + */ + deserialize(response: any): T; + + /** + * Convert a response from the backend in to an array of models + * + * @param response An object returned by the backend + * @returns an array of models of type T + */ + deserializeArray(response: any): Array; +} diff --git a/src/app/shared/empty.util.spec.ts b/src/app/shared/empty.util.spec.ts new file mode 100644 index 0000000000..b12e4b40ca --- /dev/null +++ b/src/app/shared/empty.util.spec.ts @@ -0,0 +1,219 @@ +import { isEmpty, hasNoValue, hasValue, isNotEmpty } from "./empty.util"; + +describe("Empty Utils", () => { + const string: string = 'string'; + const fn: () => void = function () {}; + const object: any = { length: 0 }; + const emptyMap: Map = new Map(); + let fullMap: Map = new Map(); + fullMap.set('foo', 'bar'); + + describe("hasNoValue", () => { + it("should return true for null", () => { + expect(hasNoValue(null)).toBe(true); + }); + + it("should return true for undefined", () => { + expect(hasNoValue(undefined)).toBe(true); + }); + + it("should return false for an empty String", () => { + expect(hasNoValue('')).toBe(false); + }); + + it("should return false for true", () => { + expect(hasNoValue(true)).toBe(false); + }); + + it("should return false for false", () => { + expect(hasNoValue(false)).toBe(false); + }); + + it("should return false for a String", () => { + expect(hasNoValue(string)).toBe(false); + }); + + it("should return false for a Function", () => { + expect(hasNoValue(fn)).toBe(false); + }); + + it("should return false for 0", () => { + expect(hasNoValue(0)).toBe(false); + }); + + it("should return false for an empty Array", () => { + expect(hasNoValue([])).toBe(false); + }); + + it("should return false for an empty Object", () => { + expect(hasNoValue({})).toBe(false); + }); + + }); + + describe("hasValue", () => { + + it("should return false for null", () => { + expect(hasValue(null)).toBe(false); + }); + + it("should return false for undefined", () => { + expect(hasValue(undefined)).toBe(false); + }); + + it("should return true for an empty String", () => { + expect(hasValue('')).toBe(true); + }); + + it("should return true for false", () => { + expect(hasValue(false)).toBe(true); + }); + + it("should return true for true", () => { + expect(hasValue(true)).toBe(true); + }); + + it("should return true for a String", () => { + expect(hasValue(string)).toBe(true); + }); + + it("should return true for a Function", () => { + expect(hasValue(fn)).toBe(true); + }); + + it("should return true for 0", () => { + expect(hasValue(0)).toBe(true); + }); + + it("should return true for an empty Array", () => { + expect(hasValue([])).toBe(true); + }); + + it("should return true for an empty Object", () => { + expect(hasValue({})).toBe(true); + }); + + }); + + describe("isEmpty", () => { + it("should return true for null", () => { + expect(isEmpty(null)).toBe(true); + }); + + it("should return true for undefined", () => { + expect(isEmpty(undefined)).toBe(true); + }); + + it("should return true for an empty String", () => { + expect(isEmpty('')).toBe(true); + }); + + it("should return false for a whitespace String", () => { + expect(isEmpty(' ')).toBe(false); + expect(isEmpty('\n\t')).toBe(false); + }); + + it("should return false for true", () => { + expect(isEmpty(true)).toBe(false); + }); + + it("should return false for false", () => { + expect(isEmpty(false)).toBe(false); + }); + + it("should return false for a String", () => { + expect(isEmpty(string)).toBe(false); + }); + + it("should return false for a Function", () => { + expect(isEmpty(fn)).toBe(false); + }); + + it("should return false for 0", () => { + expect(isEmpty(0)).toBe(false); + }); + + it("should return true for an empty Array", () => { + expect(isEmpty([])).toBe(true); + }); + + it("should return false for an empty Object", () => { + expect(isEmpty({})).toBe(false); + }); + + it("should return true for an Object that has zero \'length\'", () => { + expect(isEmpty(object)).toBe(true); + }); + + it("should return true for an Empty map", () => { + expect(isEmpty(emptyMap)).toBe(true); + }); + + it("should return false for a Map that is not empty", () => { + expect(isEmpty(fullMap)).toBe(false); + }); + + }); + + describe("isNotEmpty", () => { + it("should return false for null", () => { + expect(isNotEmpty(null)).toBe(false); + }); + + it("should return false for undefined", () => { + expect(isNotEmpty(undefined)).toBe(false); + }); + + it("should return false for an empty String", () => { + expect(isNotEmpty('')).toBe(false); + }); + + it("should return true for a whitespace String", () => { + expect(isNotEmpty(' ')).toBe(true); + expect(isNotEmpty('\n\t')).toBe(true); + }); + + it("should return true for false", () => { + expect(isNotEmpty(false)).toBe(true); + }); + + it("should return true for true", () => { + expect(isNotEmpty(true)).toBe(true); + }); + + it("should return true for a String", () => { + expect(isNotEmpty(string)).toBe(true); + }); + + it("should return true for a Function", () => { + expect(isNotEmpty(fn)).toBe(true); + }); + + it("should return true for 0", () => { + expect(isNotEmpty(0)).toBe(true); + }); + + it("should return false for an empty Array", () => { + expect(isNotEmpty([])).toBe(false); + }); + + it("should return true for an empty Object", () => { + expect(isNotEmpty({})).toBe(true); + }); + + it("should return false for an Object that has zero length", () => { + expect(isNotEmpty(object)).toBe(false); + }); + + it("should return false for an Empty map", () => { + expect(isNotEmpty(emptyMap)).toBe(false); + }); + + it("should return true for a Map that is not empty", () => { + expect(isNotEmpty(fullMap)).toBe(true); + }); + + }); +}); + + diff --git a/src/app/shared/empty.util.ts b/src/app/shared/empty.util.ts new file mode 100644 index 0000000000..a20d3dc98a --- /dev/null +++ b/src/app/shared/empty.util.ts @@ -0,0 +1,94 @@ +/** + Returns true if the passed value is null or undefined. + + hasNoValue(); // true + hasNoValue(null); // true + hasNoValue(undefined); // true + hasNoValue(''); // false + hasNoValue([]); // false + hasNoValue(function() {}); // false + */ +export function hasNoValue(obj?: any): boolean { + return obj === null || obj === undefined; +} + +/** + Returns true if the passed value is not null or undefined. + + hasValue(); // false + hasValue(null); // false + hasValue(undefined); // false + hasValue(''); // true + hasValue([]); // true + hasValue(function() {}); // true + */ +export function hasValue(obj?: any): boolean { + return !hasNoValue(obj); +} + +/** + Verifies that a value is `null` or an empty string, empty array, + or empty function. + + isEmpty(); // true + isEmpty(null); // true + isEmpty(undefined); // true + isEmpty(''); // true + isEmpty([]); // true + isEmpty({}); // false + isEmpty('Adam Hawkins'); // false + isEmpty([0,1,2]); // false + isEmpty('\n\t'); // false + isEmpty(' '); // false + */ +export function isEmpty(obj?: any): boolean { + if (hasNoValue(obj)) { + return true; + } + + if (typeof obj.size === 'number') { + return !obj.size; + } + + let objectType = typeof obj; + + if (objectType === 'object') { + let size = obj['size']; + if (typeof size === 'number') { + return !size; + } + } + + if (typeof obj.length === 'number' && objectType !== 'function') { + return !obj.length; + } + + if (objectType === 'object') { + let length = obj['length']; + if (typeof length === 'number') { + return !length; + } + } + + return false; +} + + +/** + Verifies that a value is not `null`, an empty string, empty array, + or empty function. + + isNotEmpty(); // false + isNotEmpty(null); // false + isNotEmpty(undefined); // false + isNotEmpty(''); // false + isNotEmpty([]); // false + isNotEmpty({}); // true + isNotEmpty('Adam Hawkins'); // true + isNotEmpty([0,1,2]); // true + isNotEmpty('\n\t'); // true + isNotEmpty(' '); // true + */ +export function isNotEmpty(obj?: any): boolean { + return !isEmpty(obj); +} diff --git a/src/browser.module.ts b/src/browser.module.ts index 20c87fd924..27a39e9b40 100755 --- a/src/browser.module.ts +++ b/src/browser.module.ts @@ -11,6 +11,7 @@ import { TranslateLoader, TranslateModule, TranslateStaticLoader } from 'ng2-tra import { AppModule, AppComponent } from './app/app.module'; import { SharedModule } from './app/shared/shared.module'; import { CacheService } from './app/shared/cache.service'; +import { CoreModule } from "./app/core/core.module"; // Will be merged into @angular/platform-browser in a later release // see https://github.com/angular/angular/pull/12322 @@ -56,7 +57,8 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE'; RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }), IdlePreloadModule.forRoot(), - SharedModule.forRoot(), + CoreModule.forRoot(), + SharedModule, AppModule, ], providers: [ diff --git a/src/node.module.ts b/src/node.module.ts index 1a7ee825c8..1dc29d164c 100755 --- a/src/node.module.ts +++ b/src/node.module.ts @@ -10,6 +10,7 @@ import { TranslateLoader, TranslateModule, TranslateStaticLoader } from 'ng2-tra import { AppModule, AppComponent } from './app/app.module'; import { SharedModule } from './app/shared/shared.module'; import { CacheService } from './app/shared/cache.service'; +import { CoreModule } from "./app/core/core.module"; // Will be merged into @angular/platform-browser in a later release // see https://github.com/angular/angular/pull/12322 @@ -47,7 +48,8 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE'; FormsModule, RouterModule.forRoot([], { useHash: false }), - SharedModule.forRoot(), + CoreModule.forRoot(), + SharedModule, AppModule, ], providers: [ diff --git a/src/typings.d.ts b/src/typings.d.ts index e3ce18864e..2e0285e9b4 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -71,3 +71,10 @@ interface WebpackRequire { interface NodeRequire extends WebpackRequire { } interface NodeModule extends WebpackModule { } interface Global extends GlobalEnvironment { } + +// Allows us to import json files in typescript +// See https://hackernoon.com/import-json-into-typescript-8d465beded79#.88tfoy2df +declare module "*.json" { + const value: any; + export default value; +}