Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Services for Glimmer.js

Setup

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;
};

Usage

// 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();
      });
    }
  }
}
// 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());
}
}
// 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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment