如何读取angular TS代码中绑定的值? [英] How to read the value of binding in angular TS code?

查看:24
本文介绍了如何读取angular TS代码中绑定的值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理的项目需要访问绑定到组件的字段的元数据(如果我知道开发人员绑定到我的组件的内容,我可以访问该元数据信息),因此我需要能够阅读其他开发人员以字符串值传递给我的组件的表达式.

在 TS 文件中:@Input() 值:字符串;ngOnInit() {var v = this.value;//=>大卫//需要访问绑定表达式的字符串值var expr = this.?;//=>用户名}

解决方案

从干扰性降低到干扰性增强:

  • Angular 生命周期挂钩:

是否可以使用MyComponent的生​​命周期钩子来获取传递给绑定的表达式:

答案是否定的.当生命周期钩子被调用时,绑定 user.Name 已经被解析为正确的值.这适用于所有生命周期挂钩.

  • @Input 装饰器:

我们可以修改 @Input 装饰器来给我们原始表达式的值吗?

答案是否定的.引用角度:

<块引用>

将类字段标记为输入属性并提供的装饰器配置元数据.输入属性绑定到 DOM 属性在模板中.在变化检测期间,Angular 会自动使用 DOM 属性的值更新数据属性.

这意味着我们需要非常深入地了解 Angular 的变化检测才能获得原始表达式.为此,我们实际上需要复制整个项目,因为我们无法从外部覆盖非常具体的功能.


以上两种方法只有一个共同点,都是在MyComponent端完成动作.很遗憾,我们无法解决此限制的问题.

对此的解决方案必须要求对发生绑定的位置进行某种类型的操作.在这种情况下,它位于 HTML 中,因此应尝试在那里进行解决方案以避免溢出到更多文件中.


  • A Directive 获取表达式:我们可以创建一个指令来获取被绑定的表达式吗?

答案是否定的.考虑以下父组件:

@Component({选择器:'app-parent',模板:`<app-child [qux]="foo.bar></app-child>`})导出类父组件{foo = { bar: 'qux' };}

考虑以下子组件:

@Component({选择器:'app-child',模板:`<p>{{ baz }} ></p>`})导出类父组件{@Input() baz = '';}

我们可以创建以下指令并访问在运行时使用绑定生成的HTML:

@Directive({ 选择器:'[appInputListener]' })导出类 InputListenerDirective 实现 OnInit {构造函数(私有_el:ElementRef){}ngOnInit(): 无效 {console.log(this._el.nativeElement.outerHTML);}

这将打印以下内容:

.

所以我们可以在这里访问绑定.但是有两个问题使该解决方案不可行:

  1. 解析 HTML:解析 HTML 是一件棘手的事情,它可以工作,但如果可能的话应该避免.您可以在此处阅读有关此主题的更多信息.为简化起见,我引用以下内容:

<块引用>

正则表达式只能匹配正则语言,但 HTML 是上下文无关语言,而不是正则语言

  1. 生产:虽然我们设法获得了开发中的表达式,但如果我们启用生产模式,我们就会失去它们.您可以在此处阅读有关此主题的更多信息.为了简化,我引用以下内容

<块引用>

ng-reflect-${name} 属性被添加用于调试目的,并显示组件/指令在其类中声明的输入绑定.

这就是说,虽然我们找到了一个可能的解决方案,但它不是一个有效的解决方案.解析 HTML 可能会出现问题,甚至无法在生产环境中工作.


虽然以前的解决方案不起作用,但有一个使用 @DirectiveSingleton 的可能解决方案这种解决方案的缺点是它需要明确的采用和实践.

我们可以创建以下服务来存储绑定的信息:

@Injectable({ providedIn: 'root' })导出类 BindingStoreService {private _bindings: { [k: string]/* hostSelector */: {选择器:字符串,输入:字符串,表达式:字符串}[] } = { };getBindings(hostSelector: string, childSelector: string): {选择器:字符串,输入:字符串,表达式:字符串} {返回 this._bindings[hostSelector].filter(binding => {bindings.selector === childSelector})};添加绑定(主机选择器:字符串,childSelector:字符串,输入:字符串,表达式:字符串) : 空白 {this._bindings[hostSelector].push({选择器:childSelector,输入:输入,表达式:表达式});}}

我们可以创建以下指令来存储服务中的信息:

@Directive({ 选择器:'[appInputListener]' })导出类 InputListenerDirective 实现 OnInit {@Input() 主机选择器:字符串;@Input() childSelector: 字符串;@Input() 输入:string[] = [];@Input() 表达式:string[] = [];构造函数(私有_bindingsStoreService:BindingsStoreService){}ngOnInit(): 无效 {if (this.inputs.length !== this.expressions.length) {//抛出一个错误,因为我们的输入和表达式不匹配};for(let i = 0; i < this.inputs.length; i++) {this._bindingsStoreService.addBindings(主机选择器:this.hostSelector,childSelector: this.childSelector,输入:this.inputs[i];表达式:this.expressions[i])}}}

将此应用于我以前的父组件 HTML 会导致以下结果:

我个人不喜欢这种方法,因为我们手动编写所有内容.我们可以改进指令或服务,使其不需要太多输入,但现实情况是我们总是手动执行大部分工作.

这个答案有效.然而,它非常笨重且难以应用.它只会有一个起点.


我对这个问题的看法.

想做的事可以做,但应该避免.对于您要解决的任何问题,可能都有更好的方法.使用绑定中传递的表达式可能不是最好的方法.

如果您不需要在运行时知道这些值而只在构建期间需要它们,请考虑创建一个预先运行的脚本并在那里执行一些报废.

I am working on project that requires to access the metadata of the field that is bound to the component (I can access that metadata information if I know what the developer bound to my component), therefore I need to be able to read the expression that other developer pass to my component in string value.

<mycomponent [(value)]="user.Name" />

Template:
<input type="text" [(ngModel)]="value"/>

In TS file:
@Input() value:string;

ngOnInit() {
   var v = this.value; //=> David

   //need to access the string value of the bound expression
   var expr = this.?; //=> user.Name
}

解决方案

Going from less intrusive to more intrusive:

  • Angular Lifecycle Hooks:

Can we use the lifecycle hooks of MyComponent to obtain the expression that passed to the binding in:

<mycomponent [(value)]="user.Name" />

The answer is no. When the lifecycle hooks are called the binding user.Name has already been resolved to the proper value. This applies to all lifecycle hooks.

  • The @Input Decorator:

Can we modify the @Input decorator to give us the value of the original expression?

The answer is no. Quoting Angular:

Decorator that marks a class field as an input property and supplies configuration metadata. The input property is bound to a DOM property in the template. During change detection, Angular automatically updates the data property with the DOM property's value.

This means that we would need to go very deep into the Angular's change detection to be able to obtain the original expression. To do this we would need to practically copy the entire project since we are unable to override very specific functions from the outside.


The above two approaches have only thing in common, the action is being done on the MyComponent side. Unfortunately we can't solve the issue with this restriction.

A solution therefor must require some type of action on the place where the binding occurs. In this case, it's in the HTML so a solution should attempt to take place there to avoid spilling into more files.


  • A Directive to obtain the expression: Can we create an directive to obtain the expression that is being binded?

The answer is no and yes. Consider the following parent component:

@Component({
  selector: 'app-parent',
  template: `<app-child [qux]="foo.bar"></app-child>`
})
export class ParentComponent { 
  foo = { bar: 'qux' };
}

Consider the following child component:

@Component({
  selector: 'app-child',
  template: `<p> {{ baz }} ></p>`
})
export class ParentComponent { 
  @Input() baz = '';
}

We can create the following directive and obtain access to the HTML generated at run time with the bindings:

@Directive({ selector: '[appInputListener]' })
export class InputListenerDirective implements OnInit {

  constructor(private _el: ElementRef) { }

  ngOnInit(): void {
    console.log(this._el.nativeElement.outerHTML);
  }

This would print the followoing:

<app-child _ngcontent-nef-c19="" appinputlistener="" _nghost-nef-c16="" ng-reflect-baz="foo.bar"><p _ngcontent-nef-c16="">qux</p></app-child>

Notice the following in the expression: ng-reflect-baz="foo.bar".

So we can access the binding here. But there are two issues that make this solution not viable:

  1. Parsing HTML: Parsing HTML is a tricky thing, it can work but is something that should be avoided if possible. You can read more on this topic here. I quote the following for simplification:

Regular expressions can only match regular languages but HTML is a context-free language and not a regular language

  1. Production: While we managed to obtain the expressions in development, if we enable production mode, we lose them. You can read more on this topic here. I quote the following for simplification

ng-reflect-${name} attributes are added for debugging purposes and show the input bindings that a component/directive has declared in its class.

This is to say that while we reached a possible solution, it is not a valid one. Parsing HTML can become problematic and furthermore into wont work in production.


While the previously solutions did not work there is a possible solution using a @Directive and a Singleton The downside to this solution is that it requires explicit adoption and practice.

We can create the following service to store the information of the bindings:

@Injectable({ providedIn: 'root' })
export class BindingStoreService {
  
  private _bindings: { [k: string] /* hostSelector */: {
    selector: string,
    input: string,
    expression: string
  }[] } = { };

  getBindings(hostSelector: string, childSelector: string): {
    selector: string, input: string, expression: string
  } {
    return this._bindings[hostSelector].filter(binding => {
      bindings.selector === childSelector
    })
  };

  addBindings(
    hostSelector: string,
    childSelector: string,
    input: string,
    expression: string
  ) : void { 
    this._bindings[hostSelector].push({
      selector: childSelector, input: input, expression: expression
    });
  }
}

We can create the following directive to store the information in the service:

@Directive({ selector: '[appInputListener]' })
export class InputListenerDirective implements OnInit { 
  @Input() hostSelector: string;
  @Input() childSelector: string;
  @Input() inputs: string[] = [];
  @Input() expressions: string[] = [];

  constructor(private _bindingsStoreService: BindingsStoreService) { }

  ngOnInit(): void {
    if (this.inputs.length !== this.expressions.length) {
      // Throw an error since we have a mismatch of inputs and expressions
    };
    
    for(let i = 0; i < this.inputs.length; i++) {
      this._bindingsStoreService.addBindings(
        hostSelector: this.hostSelector,
        childSelector: this.childSelector,
        input: this.inputs[i];
        expression: this.expressions[i]
      )
    }
  }
}

Applying this to my previous parent component HTML would lead to the following:

<app-child 
  appInputListener
  [hostSelector]="app-parent"
  [childSelector]="app-child"
  [inputs]="['qux']"
  [expressions]="['foo.bar']"
  [qux]="foo.bar"
></app-child>

I personally dislike this approach since we are manually writing everything. We could improve the directive or the service to not require so many inputs but the reality is that we would always perform the majority of work manually.

This answer works. However it is very bulky and problematic to apply. It would only work has a starting point.


My thoughts on this issue.

What is wanted can be done, but should be avoided. There is probably a better approach to whatever problem you are trying to solve. Going for the expressions that are passed in the bindings is probably not the best way to go.

If you don't need to know the values at runtime and only require them during a build, consider creating a script that is run beforehand and perform some scrapping there.

这篇关于如何读取angular TS代码中绑定的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆