简体   繁体   English

Angular 2:从装饰器访问注入的依赖项

[英]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. 我正在制作一个可重复使用的Angular2组件,我希望用户能够指定一个模板字符串或templateUrl,然后该组件将通过属性或通过某种服务方法设置该组件。

In Angular 1, this is simple, we can do something like this: 在Angular 1中,这很简单,我们可以这样做:

// 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? 如何在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. 这是我对Angular 2组件生命周期的理解。

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: 给定一个指令SimpleTimer ,我们可以提供这样的自定义模板:

<!-- 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: 然后使用如下的TemplateRef和ViewContainerRef注射剂:

// 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. 编辑:我刚刚注意到你的意图是访问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: 您有3个主要选择:

1.Use *ng-if with known components 1.使用已知组件的* ng-if

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 2.使用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 这是一个使用这种确切方法的SO答案

Here is someone using it to load components 100% dynamically (Super hacky, messes with the asyncRouter) 这里有人用它来动态加载组件100% (超级hacky,与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. 3.作弊(走出Angular之外)毕竟只是javascript。 You could create a class or object that you stick on the window and call a self encapsulated function 您可以创建一个类或对象,粘贴在window并调用自封装函数

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! 希望有所帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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