简体   繁体   中英

Angular 2: Accessing injected dependencies from decorator

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.

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 .

EDIT: I just noticed your intent was accessing the DI. As of right now you can't because they fire too late. The rest of this answer is to do the template stuff you asked about.

I was pretty interested in this question so I spent way more time than I thought I would today looking into this. From what I can tell at the moment there isn't an EASY way to do this.

You have 3 main options:

1.Use *ng-if with known components

This is by far the simplest way of solving this. By only having a few options you can only load the component you need

<special *ngIf="!type">Default</special>
<special *ngIf="type == 'awesome'"> I'm Awesome </special>
<special *ngIf="type == 'admin'">Admin Only</special>

Pros: Easy, template syntax. Cons: Must know types, gets annoying when many options

2. Create the components dynamically with DynamicComponentLoader

This gets hairy and pretty advanced pretty quick. You basically call loading a component based on passed parameters. This would allow you to define the template value and then pass it to create a new component.

This is a good article to learn how to start using it

Here is a SO Answer that uses this exact method

Here is someone using it to load components 100% dynamically (Super hacky, messes with the asyncRouter)

Pros: The "Angular" way of solving this issue, super flexible. Cons: Pretty involved if you just need a simple switch. Not many people doing it so help wouldn't be so easy.

3. Cheat (go outside of Angular) It is just javascript after all. You could create a class or object that you stick on the window and call a self encapsulated function

template: (function() {
    return "<supertemplate-" + window.superTempId + "' />";
}())

(DISCLAIMER) I haven't tested this, but it seems like it would work

The only thing is WHEN. That's why you can't do the other services as they don't exist when the metadata is being done, but if you set your template or whatever first I don't see why it wouldn't work

Pros: Likely to work without a ton of hassle Cons: Very much not the "Angular Way." Super Hacky.

It's a fairly common request so I assume we'll see some more on this with either a "Preferred Method" or more standard functionality..

Hope that helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM