Services for Glimmer.js
// src/utils/tracked.ts
import { tracked } from '@glimmer/component';
import { getOwner } from '@glimmer/di';
export const REGISTER_TRACKING = Symbol('register-component');
/*
* Lifted from https://github.com/shahata/dasherize/blob/master/index.js
*/
function dashCase(str) {
return str.replace(/[A-Z](?:(?=[^A-Z])|[A-Z]*(?=[A-Z][^A-Z]|$))/g, function (s, i) {
return (i > 0 ? '-' : '') + s.toLowerCase();
});
}
export default function trackService(propertyWithService): any {
return (baseConstructor) => {
let propertyCache = Symbol('property-cache');
class SubClassWithInjection extends baseConstructor {
@tracked
get [propertyWithService]() {
if (!this[propertyCache]) {
let service = getOwner(this).lookup(`service:${dashCase(propertyWithService)}`);
if (service[REGISTER_TRACKING]) {
service[REGISTER_TRACKING](() => this[propertyWithService] = service);
}
this[propertyCache] = service;
}
return this[propertyCache];
}
set [propertyWithService](value) {
this[propertyCache] = value;
}
}
return SubClassWithInjection;
}
}
// src/services/-utils/service.ts
import { REGISTER_TRACKING } from '../../utils/tracked';
export default class Service {
_tracking = [];
static create(options) {
return new this(options);
}
constructor(options) {
Object.assign(this, options);
}
[REGISTER_TRACKING](instance) {
this._tracking.push(instance);
}
notify() {
this._tracking.forEach(t => t());
}
}
In config/environment.js
:
// config/environment.js
'use strict';
/*
* Mostly this is the stock module config.
*/
const moduleConfiguration = {
types: {
application: { definitiveCollection: 'main' },
component: { definitiveCollection: 'components' },
'component-test': { unresolvable: true },
helper: { definitiveCollection: 'components' },
'helper-test': { unresolvable: true },
renderer: { definitiveCollection: 'main' },
template: { definitiveCollection: 'components' },
/*
* Add service as a type.
*/
service: { definitiveCollection: 'services' }
},
collections: {
main: {
types: ['application', 'renderer']
},
components: {
group: 'ui',
types: ['component', 'component-test', 'template', 'helper', 'helper-test'],
defaultType: 'component',
privateCollections: ['utils']
},
styles: {
group: 'ui',
unresolvable: true
},
utils: {
unresolvable: true
},
/*
* Add services as a collection.
*/
services: {
types: ['service'],
defaultType: 'service',
privateCollections: ['utils']
}
}
};
module.exports = function(environment) {
let ENV = {
modulePrefix: 'bodega-glimmer',
environment: environment,
/*
* Pass the module config here.
*/
moduleConfiguration
};
return ENV;
};
// src/ui/components/apple-pay-button/component.ts
import Online from '../../../services/online';
import Component, { tracked } from '@glimmer/component';
import trackService from '../../../utils/tracked';
/*
* Use `trackService` to both inject the dependency and
* allow it to dirty the property.
*/
@trackService('online')
export default class ApplePayButton extends Component {
/*
* Define the property and type.
*/
online: Online;
/*
* Track the service like any other property.
*/
@tracked('online', 'args')
get isDisabled() {
return !this.online.isOnline || this.args.disabled;
}
};
// src/ui/services/apple-pay.ts
import Service from './-utils/service';
import { tracked } from '@glimmer/component';
export default class ApplePay extends Service {
/*
* If you use this property in a template, ala `{{applePay.tracked}}`,
* you will want to `@tracked` it. However you don't need this for
* dirtying the service where it is injected.
*/
@tracked isAvailable = false;
constructor(options) {
super(options);
if (self.Stripe && self.Stripe.applePay) {
self.Stripe.applePay.checkAvailability((result) => {
/*
* Set the property like normal.
*/
this.isAvailable = result;
/*
* This dirties all properties where this service is injected.
*/
this.notify();
});
}
}
}