Package Exports
- @lithiumjs/angular
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@lithiumjs/angular) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Lithium for Angular (@lithiumjs/angular)
A decorator-based library for Angular that enables seamless reactive data binding using RxJS.
Installation
The project can be installed via npm using the following command:
npm install @lithiumjs/angular
Quick Intro Guide
(For more information, see the full API reference)
NOTE: If you are using Angular's AoT compiler, additional considerations are required to write fully AoT-compliant components with Lithium. See the Angular AoT Compiler section for details.
EventSource
EventSource
is the decorator used for reactive event binding. EventSource
creates an Observable
that can be used to react to component UI and lifecycle events.
Template
<button (click)="onButtonPress()"> </button>
Component
@Component({...})
class Component {
@EventSource() private onButtonPress$: Observable<any>;
constructor () {
this.onButtonPress$.subscribe(() => console.log("The button was pressed."));
}
}
As you can see in the example above, an onButtonPress
function is automatically created in the component's template that can be used to bind to events.
EventSource method decorators
Method decorators may be passed to EventSource
and will be forwarded to the underlying facade function.
Example
@Component({...})
class Component {
// Given "Throttle" is a method decorator factory:
@EventSource(Throttle(1000)) private onButtonPress$: Observable<any>;
constructor () {
this.onButtonPress$.subscribe(() => console.log("The button was pressed."));
}
}
Angular decorators may also be declared on the EventSource
property itself. Lithium will automatically move the associated metadata to the facade function. This is useful for staying compliant with Angular's AoT compiler.
Example
@Component({...})
class Component {
@EventSource()
@HostListener("click")
private onClick$: Observable<any>;
constructor () {
this.onClick$.subscribe(() => console.log("The component was clicked."));
}
}
StateEmitter
StateEmitter
is the decorator used for reactive one-way in binding and two-way binding, allowing for state synchronization to and from the UI via a BehaviorSubject
.
Template
<div> You clicked the button {{buttonPressCount}} times.</div>
<button (click)="onButtonPress()"> </button>
Component
@Component({...})
class Component {
@EventSource() private onButtonPress$: Observable<any>;
@StateEmitter({initialValue: 0}) private buttonPressCount$: Subject<number>;
constructor () {
this.onButtonPress$
.flatMap(() => this.buttonPressCount$.take(1))
.subscribe(buttonPressCount => this.buttonPressCount$.next(buttonPressCount + 1));
}
}
As you can see in the example above, a buttonPressCount
property is automatically created in the component's template that can be used to bind to properties.
StateEmitter property decorators
Property decorators may be passed to StateEmitter
and will be forwarded to the underlying property.
Example
@Component({
selector: "component",
...
})
class Component {
// Given "NonNull" is a property decorator factory:
@StateEmitter(NonNull()) private name$: Subject<string>;
constructor () {
this.name$.subscribe(name => console.log(`Name: ${name}`));
}
}
Angular decorators may also be declared on the StateEmitter
property itself. Lithium will automatically move the associated metadata to the underlying facade property. This is useful for staying compliant with Angular's AoT compiler.
Example
<component [disabled]="true"> </component>
@Component({...})
class Component {
@StateEmitter()
@Input("disabled")
private disabled$: Subject<boolean>;
constructor () {
this.disabled$.subscribe(disabled => console.log(`Disabled: ${disabled}`)); // Output: Disabled: true
}
}
Proxied StateEmitters
Angular components will often use information from services and other sources. Proxied StateEmitter
s can be used to represent extenal values from within a component, or even create aliases for existing values.
StateEmitter
proxies create reactive references to properties within the component class and allow them to be used like any other StateEmitter
reference. Dot path notation may be used to reference nested properties.
Example
class FooService {
public get nestedProperty(): number {
return 42;
}
}
@Component({...})
class Component {
@StateEmitter({
proxyMode: EmitterMetadata.ProxyMode.Alias,
proxyPath: "fooService.nestedProperty",
})
private nestedProperty$: Observable<number>;
constructor (private fooService: FooService) { }
}
Static vs dynamic proxy property paths
Proxy paths are considered either dynamic or static depending on the type of the properties within it. If a proxy path is dynamic, the resulting reference to the property will be an Observable
. If a path is static, the reference will be a Subject
.
A proxy path is considered static only if all of the following conditions are met:
- The last property in the path is a
Subject
. - All other properties in the path are not of type
Subject
orObservable
.
Example
interface Settings {
notificationsEnabled: boolean;
someOtherSetting: number;
}
class SettingsService {
public settings$ = new BehaviorSubject<Settings> ({
notificationsEnabled: true,
someOtherSetting: 1
});
}
@Component({...})
class FormComponent {
@StateEmitter.Alias("settingsService.settings$")
private settings$: Subject<Settings>;
@StateEmitter.Alias("settings$.notificationsEnabled")
private notificationsEnabled$: Observable<boolean>;
constructor (private settingsService: SettingsService) { }
}
In the above example, the proxy path settingsService.settings$
is considered static, because the last property is a Subject
and the rest of the path does not contain any Observable
s or Subject
s. The proxy path settings$.notificationsEnabled
is not static, because the last property is not a Subject
, and the first property in the path is a Subject
.
Updating Subjects in dynamic proxy property paths
If a dynamic property path contains a Subject
, it will automatically be notified of changes to the proxied property. The example below illustrates two-way binding of an aliased property with a dynamic property path containing a Subject
.
Example
<form>
<input [(ngModel)]="notificationsEnabled" type="checkbox">
</form>
@Component({...})
class FormComponent {
// Dynamic proxy property path that contains a Subject
@StateEmitter.Alias("settingsService.settings$.notificationsEnabled")
private notificationsEnabled$: Observable<boolean> ;
constructor (private settingsService: SettingsService) { }
}
When notificationsEnabled
is updated via the form input, settingsService.settings$
will automatically emit a merged Settings
object with the new value of notificationsEnabled
. All other property values on the Settings
object will also be preserved.
Types of StateEmitter Proxies
Alias
From
Merge
Alias
The Alias
proxy type simply resolves the given property path and creates an observable that directly maps back to the property value.
Example
class SessionManager {
public session$: Subject<Session> ;
}
<div> Welcome back, {{session.username}}</div>
@Component({...})
class Component {
@StateEmitter.Alias("sessionManager.session$") private session$: Subject<Session>;
constructor (private sessionManager: SessionManager) { }
}
Alias
can also be used with other StateEmitter
references:
<div> Welcome back, {{username}}</div>
@Component({...})
class Component {
@StateEmitter.Alias("sessionManager.session$") private session$: Subject<Session>;
@StateEmitter.Alias("session$.username") private username$: Observable<string>;
constructor (private sessionManager: SessionManager) { }
}
From
The From
proxy type creates a new Subject
that gets its initial value from the source.
Example
<form>
<input type="text" [(ngModel)]="username">
</form>
@Component({...})
class FormComponent {
@StateEmitter.From("sessionManager.session$.username") private username$: Subject<string>;
constructor (private sessionManager: SessionManager) { }
}
In the above example, any form updates to username
will only be reflected on FormComponent.username$
. sessionManager.session$
will not receive any updates.
Merge
The Merge
proxy type creates a new Subject
that subscribes to all updates from the source.
Example
<form>
<input type="date" [(ngModel)]="date">
</form>
@Component({...})
class FormComponent {
@StateEmitter.Merge("fooService.date$") private date$: Subject<Date>;
constructor (private fooService: FooService) { }
}
In the above example, any form updates to date
will only be reflected on FormComponent.date$
. fooService.date$
will not receive any updates.
Lifecycle Event Decorators
Helper decorators are provided that proxy all of the Angular component lifecycle events. These are:
- OnChanges
- OnInit
- OnDestroy
- DoCheck
- AfterContentInit
- AfterContentChecked
- AfterViewInit
- AfterViewChecked
Example
@Component({...})
class Component {
@OnInit() private onInit$: Observable<void>;
constructor () {
this.onInit$.subscribe(() => console.log("Component is initialized."));
}
}
Angular AoT Compiler
Lithium for Angular is compatible with Angular's AoT compiler. However, due to current limitations of the compiler there are a few rules that need to be adhered to in order to write fully AoT-compliant code with Lithium:
1. skipTemplateCodegen
must be set to true
.
Because Lithium's StateEmitter
takes care of managing the properties that a component's view template will access, it is incompatible with how the current Angular AoT compiler generates template metadata. To remedy this issue, you must set the skipTemplateCodegen
flag to true
under angularCompilerOptions
in your project's tsconfig.json
. For more info, see the official Angular AoT compiler documentation.
2. Angular component lifecycle EventSource
decorators will not work in AoT mode unless a corresponding method declaration is created for the event.
When using component lifecycle events (i.e. ngOnInit
), Angular's AoT compiler requires that a method for that event be explicitly declared in the class or a parent class for it to be invoked. However, Lithium adds these methods dynamically through a class decorator, and since the AoT compiler does not evaluate expressions, the corresponding lifecycle EventSource
decorator will never emit.
For example, the following code, while valid without the AoT compiler, will fail to work correctly when compiled with AoT:
@Component({...})
class Component {
// EventSource proxy for ngOnInit
@OnInit() private onInit$: Observable<void>;
constructor () {
// ngOnInit will *never* fire in AoT mode for this class, because we did not explicitly declare an ngOnInit method (note that the AoT compiler cannot resolve mixins/anonymous classes).
this.onInit$.subscribe(() => console.log("Component is initialized."));
}
}
To help with this limitation, Lithium for Angular provides an AotAware
class that a component class can extend to resolve this automatically:
@Component({...})
// Extending AotAware takes care of providing the Angular lifecycle event method declarations.
class Component extends AotAware {
// EventSource proxy for ngOnInit
@OnInit() private onInit$: Observable<void>;
constructor () {
super();
// This will fire as expected.
this.onInit$.subscribe(() => console.log("Component is initialized."));
}
}
If you do not wish to use the AotAware
class, you must provide an empty stub declaration of the corresponding Angular event method and set skipMethodCheck
to true
for the EventSource
decorator. Example:
@Component({...})
class Component {
// EventSource proxy for ngOnInit
// Disable EventSource method usage checks
@OnInit({ skipMethodCheck: true }) private onInit$: Observable<void>;
// This stub declaration must be provided to allow onInit$ to fire in AoT mode.
public ngOnInit() {}
constructor () {
// This will fire as expected.
this.onInit$.subscribe(() => console.log("Component is initialized."));
}
}
Please note that the method declaration will be overidden by Lithium. Any code inside the declaration will be ignored.
3. When applying an Angular decorator to an EventSource
, the decorator should be applied to the property directly instead of being passed into EventSource
.
The following example will fail to work when compiled with AoT:
@Component({...})
class Component {
@EventSource(HostListener("click"))
private onClick$: Observable<any>;
constructor () {
// This will never emit when compiled with AoT
this.onClick$.subscribe(() => console.log("The component was clicked."));
}
}
In the above example, HostListener
should be declared on the property instead. The following example will work correctly when compiled with AoT:
@Component({...})
class Component {
@EventSource()
@HostListener("click")
private onClick$: Observable<any>;
constructor () {
// This will emit as expected
this.onClick$.subscribe(() => console.log("The component was clicked."));
}
}
4. When applying an Angular decorator to a StateEmitter
, the decorator should be applied to the property directly instead of being passed into StateEmitter
.
The following example will fail to work when compiled with AoT:
@Component({...})
class Component {
// This will generate a compiler error when "disabled" is bound to in a template.
@StateEmitter(Input("disabled"))
private disabled$: Subject<boolean>;
constructor () {
this.disabled$.subscribe(disabled => console.log(`Disabled: ${disabled}`));
}
}
In the above example, Input
should be declared on the property instead. The following example will work correctly when compiled with AoT:
@Component({...})
class Component {
// This will allow "disabled" to be bound in a template without generating a compiler error
@StateEmitter()
@Input("disabled")
private disabled$: Subject<boolean>;
constructor () {
this.disabled$.subscribe(disabled => console.log(`Disabled: ${disabled}`));
}
}
API
AotAware
abstract class AotAware {
public ngOnChanges();
public ngOnInit();
public ngOnDestroy();
public ngDoCheck();
public ngAfterContentInit();
public ngAfterContentChecked();
public ngAfterViewInit();
public ngAfterViewChecked();
}
An abstract class that an Angular component class can extend to automatically handle defining lifecycle event methods for the AoT compiler.
EventSource
function EventSource(): EventSourceDecorator
function EventSource(...methodDecorators: MethodDecorator[]): EventSourceDecorator
function EventSource(options: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): EventSourceDecorator
Creates an event source, which is an Observable
that automatically emits when the given function (eventType
) is called.
options
- The options to use for this event source. See EventSource.DecoratorOptions
.
methodDecorators
- A list of MethodDecorator
s that should be applied to the underlying event function.
Note: If the target property's name is of the format "<eventType>$
", options.eventType
can be omitted and automatically deduced from the property name.
EventSource.DecoratorOptions
interface DecoratorOptions {
eventType?: EventType;
skipMethodCheck?: boolean;
}
eventType
- (Optional) The name of the function that represents the event or action. If not specified, the name will try to be deduced from the name of the EventSource
property.
skipMethodCheck
- (Optional) Whether or not to ignore existing method declarations in the class when defining the EventSource
. If set to false
, an error will be thrown if a method is defined with the same name as the eventType
. Defaults to false
.
EventType
type EventType = string;
EventType
represents the name of the function being proxied.
StateEmitter
function StateEmitter(): StateEmitterDecorator
function StateEmitter(...propertyDecorators: PropertyDecorator[]): StateEmitterDecorator
function StateEmitter(params: StateEmitter.DecoratorParams, ...propertyDecorators: PropertyDecorator[]): StateEmitterDecorator
Creates a state emitter, which is a Subject
that automatically emits when the underlying property value is modified, and automatically updates the property value when the Subject
emits.
params
- The parameters to use for this state emitter. See StateEmitter.DecoratorParams
.
propertyDecorators
- A list of PropertyDecorator
s that should be applied to the underlying property.
Note: If the target property's name is of the format "<emitterType>$
", params.propertyName
can be omitted and automatically deduced from the property name.
StateEmitter.Alias
Helper decorator that creates a StateEmitter
with proxyMode
set to Alias
.
function Alias(params: ProxyDecoratorParams | string, ...propertyDecorators: PropertyDecorator[]): PropertyDecorator
params
- The parameters to use for this state emitter, or a string
that is shorthand for passing the path
parameter. See StateEmitter.ProxyDecoratorParams
.
propertyDecorators
- A list of PropertyDecorator
s that should be applied to the underlying property.
Note: This is functionally equivalent to:
StateEmitter({proxyMode: EmitterMetadata.ProxyMode.Alias});
StateEmitter.From
Helper decorator that creates a StateEmitter
with proxyMode
set to From
.
function From(params: ProxyDecoratorParams | string, ...propertyDecorators: PropertyDecorator[]): PropertyDecorator
params
- The parameters to use for this state emitter, or a string
that is shorthand for passing the path
parameter. See StateEmitter.ProxyDecoratorParams
.
propertyDecorators
- A list of PropertyDecorator
s that should be applied to the underlying property.
Note: This is functionally equivalent to:
StateEmitter({proxyMode: EmitterMetadata.ProxyMode.From});
StateEmitter.Merge
Helper decorator that creates a StateEmitter
with proxyMode
set to Merge
.
function Merge(params: ProxyDecoratorParams | string, ...propertyDecorators: PropertyDecorator[]): PropertyDecorator
params
- The parameters to use for this state emitter, or a string
that is shorthand for passing the path
parameter. See StateEmitter.ProxyDecoratorParams
.
propertyDecorators
- A list of PropertyDecorator
s that should be applied to the underlying property.
Note: This is functionally equivalent to:
StateEmitter({proxyMode: EmitterMetadata.ProxyMode.Merge});
StateEmitter.DecoratorParams
interface DecoratorParams {
propertyName?: EmitterType;
initialValue?: any;
readOnly?: boolean;
proxyMode?: ProxyMode;
proxyPath?: string;
proxyMergeUpdates?: boolean;
}
propertyName
- (Optional) The name of the underlying property that should be created for use by the component's template. If not specified, the name will try to be deduced from the name of the StateEmitter
property.
initialValue
- (Optional) The initial value to be emitted. Defaults to undefined
.
readOnly
- (Optional) Whether or not the underlying property being created should be read-only. Defaults to false
.
proxyMode
- (Optional) The proxy mode to use for the StateEmitter
. Defaults to None
. For all possible values, see StateEmitter.ProxyMode
.
proxyPath
- (Conditional) The path of the property to be proxied. Required if proxyMode
is not set to None
.
proxyMergeUpdates
- (Optional) Whether or not newly emitted values via dynamic proxy property paths should be merged with the previously emitted value. Defaults to true
if the emitted value is an instance of Object
, otherwise defaults to false
.
StateEmitter.ProxyDecoratorParams
interface ProxyDecoratorParams {
path: string;
propertyName?: EmitterType;
mergeUpdates?: boolean;
}
path
- See StateEmitter.DecoratorParams.proxyPath
.
propertyName
- (Optional) See StateEmitter.DecoratorParams.propertyName
.
mergeUpdates
- (Optional) See StateEmitter.DecoratorParams.proxyMergeUpdates
.
StateEmitter.EmitterType
type EmitterType = string;
StateEmitter.ProxyMode
type ProxyMode = keyof {
None,
From,
Alias,
Merge
}
Angular Lifecycle EventSource
decorators
OnChanges
function OnChanges(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
OnInit
function OnInit(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
OnDestroy
function OnDestroy(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
DoCheck
function DoCheck(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
AfterContentInit
function AfterContentInit(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
AfterContentChecked
function AfterContentChecked(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
AfterViewInit
function AfterViewInit(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
AfterViewChecked
function AfterViewChecked(options?: EventSource.DecoratorOptions, ...methodDecorators: MethodDecorator[]): PropertyDecorator
See EventSource
.
Other information
- Lithium for Ionic.