Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2: Accessing injected dependencies from decorator

Tags:

angular

I am making a re-usable Angular2 component and I want the user to be able to specify a template string or templateUrl which the component will then use, either by an attribute or by setting it via some service method.

In Angular 1, this is simple, we can do something like this:

// somewhere else in app
myService.setTemplateUrl('path/to/template.html');

// directive definition
function myDirective(myService) {

  return {
    template: function(element, attrs) {
      return attrs.templateUrl || myService.getTemplateUrl();
    }
    // ...
  };

}

How is this achieved in Angular2?

@Component({
  selector: 'my-component',
  template: '...' // cannot see `mySerivce` from here, nor access the element attributes
})
export class MyComponent {

  constructor(private myService: MyService) {}

}

While my issue specifically relates to how to implement a dynamic template, the broader question is whether it is possible to access the injected dependency instances from the various decorators.

like image 430
Michael Bromley Avatar asked Sep 26 '22 22:09

Michael Bromley


1 Answers

So I finally figured out a way to do what I wanted with custom templates.

I think the answer to the actual question must be no, injectables are not available in a decorator. This is per my understanding of the life cycle of an Angular 2 component.

For those interested, here is what I came up with for implementing user-defined custom templates:

Given a directive, SimpleTimer, we can provide a custom template like this:

<!-- app.ts template -->
<div *simpleTimer="#timer=timerApi">
  <div class="time">{{ timer.getTime() }}</div>
  <div class="controls">
    <button (click)="timer.toggle()">Toggle</button>
    <button (click)="timer.reset()">Reset</button>
  </div>
</div>

And then make use of the TemplateRef and ViewContainerRef injectables like this:

// SimpleTimer.ts
constructor(private templateRef: TemplateRef,
          private viewContainer: ViewContainerRef,
          private cdr: ChangeDetectorRef) {}

ngOnInit() {
  // we need to detach the change detector initially, to prevent a
  // "changed after checked" error.
  this.cdr.detach();
}

ngAfterViewInit() {
  let view = this.viewContainer.createEmbeddedView(this.templateRef);
  let api = {
    toggle: () => this.toggle(),
    reset: () => this.reset(),
    getTime: () => this.getTime()
  }
  view.setLocal('timerApi', api);

  setTimeout(() => this.cdr.reattach());
}

For a walk-through of how and why this works, please see this blog post I wrote up on the topic.

like image 158
Michael Bromley Avatar answered Oct 11 '22 15:10

Michael Bromley