From 68c442b9907de949214b84c82b474856d8c94b28 Mon Sep 17 00:00:00 2001 From: jannyHou Date: Mon, 16 Oct 2017 23:42:26 -0400 Subject: [PATCH 1/3] mixin and components --- pages/en/lb4/Creating-components.md | 21 +++++++++++++++------ pages/en/lb4/Mixin.md | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 pages/en/lb4/Mixin.md diff --git a/pages/en/lb4/Creating-components.md b/pages/en/lb4/Creating-components.md index 10df8badf..3f1e0083c 100644 --- a/pages/en/lb4/Creating-components.md +++ b/pages/en/lb4/Creating-components.md @@ -38,8 +38,8 @@ Providers are the mechanism allowing components to export values that can be use ```js import {Provider} from '@loopback/context'; -export class MyValueProvider { - value() { +export class MyValueProvider implements Provider{ + value(): string { return 'Hello world'; } } @@ -90,12 +90,13 @@ A note on binding names: In order to avoid name conflicts, always add a unique p Provider's `value()` method can be asynchronous too: ```js +import {Provider} from '@loopback/context'; const request = require('request-promise-native'); const weatherUrl = 'http://samples.openweathermap.org/data/2.5/weather?appid=b1b15e88fa797225412429c1c50c122a1' -export class CurrentTemperatureProvider { - async value() { +export class CurrentTemperatureProvider implement Provider{ + async value():number { const data = await(request(`${weatherUrl}&q=Prague,CZ`, {json:true}); return data.main.temp; } @@ -109,14 +110,15 @@ In this case, LoopBack will wait until the promise returned by `value()` is reso In some cases, the provider may depend on other parts of LoopBack, for example the current `request` object. Such dependencies should be listed in provider's constructor and annotated with `@inject` keyword, so that LoopBack runtime can resolve them automatically for you. ```js +import {Provider} from '@loopback/context'; const uuid = require('uuid/v4'); -class CorrelationIdProvider { +class CorrelationIdProvider implement Provider{ constructor(@inject('http.request') request) { this.request = request; } - value() { + value():any { return this.request.headers['X-Correlation-Id'] || uuid(); } } @@ -246,3 +248,10 @@ class EmailComponent { } } ``` + +## Interact with Mixin + +TBD. + +- create a new concept Mixin +- use `RepositoryMixin` as an example to show mount component repositories in a mixin diff --git a/pages/en/lb4/Mixin.md b/pages/en/lb4/Mixin.md new file mode 100644 index 000000000..b1ff62cab --- /dev/null +++ b/pages/en/lb4/Mixin.md @@ -0,0 +1,17 @@ +## Mixin + +TBD + +- Should it be a separate concept or merge into decorator? + +> Interface for functions that can mix properties/methods into a base class + +This makes the essence of a mixin function same as a class decorator. + +> Apply one or more mixin functions + +Could be equivalent to composited class decorators + +A decorator also gives the benefit of supplying metadata. + +- Introduce repositoryMixin \ No newline at end of file From ae869f4aa8f766fa719da29249b2dfaf6d266cfc Mon Sep 17 00:00:00 2001 From: jannyHou Date: Wed, 18 Oct 2017 00:57:49 -0400 Subject: [PATCH 2/3] Add mixin doc --- _data/sidebars/lb4_sidebar.yml | 9 ++ pages/en/lb4/Creating-components.md | 81 +++++++++++--- pages/en/lb4/Language-related-concepts.md | 13 +++ pages/en/lb4/Mixin.md | 125 ++++++++++++++++++++-- pages/en/lb4/Using-components.md | 2 +- 5 files changed, 205 insertions(+), 25 deletions(-) create mode 100644 pages/en/lb4/Language-related-concepts.md diff --git a/_data/sidebars/lb4_sidebar.yml b/_data/sidebars/lb4_sidebar.yml index 61823a57b..94278a81e 100644 --- a/_data/sidebars/lb4_sidebar.yml +++ b/_data/sidebars/lb4_sidebar.yml @@ -98,6 +98,15 @@ children: url: Crafting-LoopBack-4.html output: 'web, pdf' +- title: 'Language-related Concepts' + url: Language-related-concepts.html + output: 'web, pdf' + children: + + - title: 'Mixin' + url: Mixin.html + output: 'web, pdf' + - title: 'Reference' url: Reference.html output: 'web, pdf' diff --git a/pages/en/lb4/Creating-components.md b/pages/en/lb4/Creating-components.md index 3f1e0083c..96d1f942d 100644 --- a/pages/en/lb4/Creating-components.md +++ b/pages/en/lb4/Creating-components.md @@ -38,8 +38,8 @@ Providers are the mechanism allowing components to export values that can be use ```js import {Provider} from '@loopback/context'; -export class MyValueProvider implements Provider{ - value(): string { +export class MyValueProvider { + value() { return 'Hello world'; } } @@ -90,13 +90,12 @@ A note on binding names: In order to avoid name conflicts, always add a unique p Provider's `value()` method can be asynchronous too: ```js -import {Provider} from '@loopback/context'; const request = require('request-promise-native'); const weatherUrl = 'http://samples.openweathermap.org/data/2.5/weather?appid=b1b15e88fa797225412429c1c50c122a1' -export class CurrentTemperatureProvider implement Provider{ - async value():number { +export class CurrentTemperatureProvider { + async value() { const data = await(request(`${weatherUrl}&q=Prague,CZ`, {json:true}); return data.main.temp; } @@ -110,15 +109,14 @@ In this case, LoopBack will wait until the promise returned by `value()` is reso In some cases, the provider may depend on other parts of LoopBack, for example the current `request` object. Such dependencies should be listed in provider's constructor and annotated with `@inject` keyword, so that LoopBack runtime can resolve them automatically for you. ```js -import {Provider} from '@loopback/context'; const uuid = require('uuid/v4'); -class CorrelationIdProvider implement Provider{ +class CorrelationIdProvider { constructor(@inject('http.request') request) { this.request = request; } - value():any { + value() { return this.request.headers['X-Correlation-Id'] || uuid(); } } @@ -230,6 +228,66 @@ export class AuthenticationProvider { } ``` +## Extends Application with Mixin + +When binding a component to an app, you may want to extend the app with the component's +properties and methods. +This can be achieved by using mixin. + +If you are not familiar with concept `mixin`, check [Mixin](Mixin.htm) to know more. + +An example of how a mixin leverages component would be `RepositoryMixin`: +Suppose an app has multiple components with repositories bound to each of them, +you can use function `RepositoryMixin` to mount those repositories to application level context. + +The following snippet is an abbreviated function +[`RepositoryMixin`](https://github.com/strongloop/loopback-next/blob/master/packages/repository/src/repository-mixin.ts): + +{% include code-caption.html content="mixins/src/repository-mixin.ts" %} +```js +export function RepositoryMixin>(superClass: T) { + return class extends superClass { + constructor(...args: any[]) { + super(...args); + ... ... + // detect components attached to the app + if (this.options.components) { + for (const component of this.options.components) { + this.mountComponentRepository(component); + } + } + } + } + mountComponentRepository(component: Class) { + const componentKey = `components.${component.name}`; + const compInstance = this.getSync(componentKey); + + // register a component's repositories in the app + if (compInstance.repositories) { + for (const repo of compInstance.repositories) { + this.repository(repo); + } + } + } +} +``` + +Then you can extend the app with repositories in a component: + +{% include code-caption.html content="index.ts" %} + +```js +import {RepositoryMixin} from 'mixins/src/repository-mixin'; +import {Application} from '@loopback/core'; +import {FooComponent} from 'components/src/Foo'; + +class AppWithRepoMixin extends RepositoryMixin(Application) {}; +let app = new AppWithRepoMixin({components: [FooComponent]}); + +// `app.find` returns all repositories in FooComponent +app.find('repositories.*'); +``` + ## Configuring components More often than not, the component may want to offer different value providers depending on the configuration. For example, a component providing Email API may offer different transports (stub, SMTP, etc.). @@ -248,10 +306,3 @@ class EmailComponent { } } ``` - -## Interact with Mixin - -TBD. - -- create a new concept Mixin -- use `RepositoryMixin` as an example to show mount component repositories in a mixin diff --git a/pages/en/lb4/Language-related-concepts.md b/pages/en/lb4/Language-related-concepts.md new file mode 100644 index 000000000..b237688f7 --- /dev/null +++ b/pages/en/lb4/Language-related-concepts.md @@ -0,0 +1,13 @@ +--- +lang: en +title: 'Language-related Concepts' +keywords: LoopBack 4.0, LoopBack 4 +tags: +sidebar: lb4_sidebar +permalink: /doc/en/lb4/Language-related-concepts.html +summary: +--- + +A module that exports JavaScript/TypeScript concept related functions. + +- [**Mixin**](Mixin.html): Add properties and methods to a class. \ No newline at end of file diff --git a/pages/en/lb4/Mixin.md b/pages/en/lb4/Mixin.md index b1ff62cab..aafe87b1f 100644 --- a/pages/en/lb4/Mixin.md +++ b/pages/en/lb4/Mixin.md @@ -1,17 +1,124 @@ -## Mixin +--- +lang: en +title: 'Mixin' +keywords: LoopBack 4.0, LoopBack 4 +tags: +sidebar: lb4_sidebar +permalink: /doc/en/lb4/Mixin.html +summary: +--- -TBD +It is a commonly used JavaScript/TypeScript strategy to extend a class with new properties and methods. -- Should it be a separate concept or merge into decorator? +A good approach to apply mixins is defining them as sub-class factories. +Then declare the new mixed class as: -> Interface for functions that can mix properties/methods into a base class +```js +class MixedClass extends MixinFoo(MixinBar(BaseClass)) {}; +``` -This makes the essence of a mixin function same as a class decorator. +Check article [real mixins with javascript classes](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/) +to learn more about it. -> Apply one or more mixin functions +## Define Mixin -Could be equivalent to composited class decorators +By defining a mixin, you create a mixin function that takes in a base class, +and returns a new class extending the base class with new properties and methods mixed to it. -A decorator also gives the benefit of supplying metadata. +For example you have a simple controller which only has a greeter function prints out 'hi!': -- Introduce repositoryMixin \ No newline at end of file +{% include code-caption.html content="Controllers/myController.ts" %} + +```ts +class SimpleController { + constructor() { + + } + greet() { + console.log('hi!'); + } +} +``` + +Now let's add mixins to it: + +- A time stamp mixin that adds a property `createdAt` to a record when a +controller instance is created. + +- A logger mixin to provide logging tools. + +Define mixin `timeStampMixin`: + +{% include code-caption.html content="Mixins/timeStampMixin.ts" %} + +```ts +import {Class} from "@loopback/repository"; + +export function timeStampMixin> (baseClass: T) { + return class extends baseClass { + // add a new property `createdAt` + public createdAt: Date; + constructor(...args: any[]) { + super(args); + this.createTS = new Date(); + } + printTimeStamp() { + console.log('Instance created at: ' + this.createdAt); + } + } +} +``` + +And define mixin `loggerMixin`: + +{% include code-caption.html content="Mixins/loggerMixin.ts" %} + +```ts +import {Class} from "@loopback/repository"; + +function loggerMixin> (baseClass: T) { + return class extends baseClass { + // add a new method `log()` + log(str: string) { + console.log('Prints out a string: ' + str); + }; + } +} +``` + +Now you can extend `SimpleController` with the two mixins: + +{% include code-caption.html content="Controllers/myController.ts" %} + +```ts +import {timeStampMixin} from 'Mixins/timeStampMixin.ts'; +import {loggerMixin} from 'Mixins/loggerMixin.ts'; + +class SimpleController { + constructor() { + + } + greet() { + console.log('hi!'); + } +} + +class AdvancedController extends loggerMixin(timeStampMixin(SimpleController)) {}; + +// verify new method and property are added to `AdvancedController`: +let aControllerInst = new AdvancedController(); +aControllerInst.printTimeStamp(); +// print out: Instance created at: Tue Oct 17 2017 22:28:49 GMT-0400 (EDT) +aControllerInst.logger('hello world!'); +// print out: Prints out a string: hello world! +``` + +## References + +Here are some articles explaining ES2015 and TypeScript mixins in more details: + +- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins + +- http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/ + +- https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html \ No newline at end of file diff --git a/pages/en/lb4/Using-components.md b/pages/en/lb4/Using-components.md index 92e933a6d..e06573c2d 100644 --- a/pages/en/lb4/Using-components.md +++ b/pages/en/lb4/Using-components.md @@ -35,4 +35,4 @@ In general, components can contribute the following items: In the future (before the GA release), components will be able to contribute additional items: - Models - - [Repositories](Repositories.html) + - [Repositories](Repositories.html) \ No newline at end of file From 837ee53fa392a5ff97ba0d5d6493780a5fd8dccd Mon Sep 17 00:00:00 2001 From: jannyHou Date: Thu, 26 Oct 2017 22:19:55 -0400 Subject: [PATCH 3/3] Apply feedback --- pages/en/lb4/Creating-components.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/en/lb4/Creating-components.md b/pages/en/lb4/Creating-components.md index 96d1f942d..8e199c3b9 100644 --- a/pages/en/lb4/Creating-components.md +++ b/pages/en/lb4/Creating-components.md @@ -232,9 +232,9 @@ export class AuthenticationProvider { When binding a component to an app, you may want to extend the app with the component's properties and methods. -This can be achieved by using mixin. +This can be achieved by using mixins. -If you are not familiar with concept `mixin`, check [Mixin](Mixin.htm) to know more. +If you are not familiar with the mixin concept, check [Mixin](Mixin.htm) to learn more. An example of how a mixin leverages component would be `RepositoryMixin`: Suppose an app has multiple components with repositories bound to each of them,