Skip to content

Custom render services #2409

@tbosch

Description

@tbosch

Custom Render Services

Custom render services are services that live on the render layer and are accessible via an interface on the app layer. We already have two of these services, RenderCompiler and Renderer, but users should be able to create their own.

For the WebWorker scenario, the idea so far was to create manual proxy/stubs for the RenderCompiler and the Renderer to be able to communicate between the processes. To allow a user to create their own render services, it would be great if Angular provided a general RPC mechanism that allows to generate client and services given an interface automatically.

This proposal supersedes custom renderers

Example

Wrapping the JQuery UI Dialog widget. Note that only the directive is specific to the ui dialog, the service can be used for wrapping any other JQuery widget:

@Directive({
  properties: ['closeText'],
  events: ['close'],
  exportAs: 'jqDialog',
  lifecycle: ['onDestroy']
})
class JQueryDialogDirective {
  close:EventEmitter;
  subscriptions:[];
  closeText:string;

  constructor(public renderer:JQueryRenderer, public element:ElementRef) {
    this.renderer.create(this.element, 'dialog', {});
    this.subscriptions.push(
      this.renderer.listen(this.element, 'close').observer(this.close)
    );
  }
  set closeText(value:string) {
    this.renderer.setOptions(this.element, 'dialog', {'closeText': value});
  }
  // TODO: allow directives to continue living over hydrate/dehydrate
  onDestroy() {
    this.subscriptions.forEach( (subscription) => {
      subscription.unsubscribe();
    });
    this.renderer.call(this.element, 'dialog', 'destroy');
  }
  open() {
    this.renderer.call(this.element, 'open');
  }
  close() {
    this.renderer.call(this.element, 'close');
  }
}

class JQueryRenderer {
  // will do `$(el)[widgetName](options)`
  create(element:ElementRef, widgetName:string, options:StringMap):Observable {}
  // will do `$(el)[widgetName]('option', options)`
  setOptions(element:ElementRef, widgetName:string, options:StringMap):Observable {}
  // will do `$(el).on(event)`
  listen(element:ElementRef, event:string):Observable {}
  // will do `$(el)[widgetName](methodName)`
  call(element:ElementRef, widgetName:string, methodName:string):Observable {}
}

class DomJQueryRenderer implements JQueryRenderer {}

Other examples would be a RulerService, CookieService, LocationService.

Why?

  • we don't want to have an injector per element on render side as well for perf and simplicity reasons. The consequence is that custom render logic needs to be encapsulated into singleton services
  • restricting custom render logic to only use setting properties, calling actions and listening to events as means of communication with the app side is very restrictive.
  • allowing custom render logic this just a generalization of our RenderCompiler and Renderer, i.e. it does not add new abstractions / primitives to Angular

TODO: discuss why not use render directives

  • can't reference them from app side, but compiler should not take configuration other than the root component

Restrictions to the interface of custom render services

  • a render service can't call an app service
  • every method must either be void, return a Promise or an Observable (e.g. events).
  • Angular should provide hooks to allow users to marshal any object
  • We might have 2 interfaces, one for the app side that uses ElementRef and one for the render side that gets the dom element with Angular automatically unwrapping the ElementRef into a DOM element and vice versa.

Implications

  • DOM elements should not be referenced in directives, only in render services
  • Rename Renderer into RenderViewManager, as it is only used by the AppViewManager and to signal that it is no so special any more, as users can write their own render services.

Potential later changes

  • allow directives on the app side to implement their own hydrate/dehydrate and to survive hydration.

Non webworker scenario

  • In dev mode: enforce that all parameters given to a render service are serializable
  • TODO: same injector or separate injectors?

Webworker scenario

TODO: Show how to register custom render services in WebWorker scenario

To be discussed

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions