根据规则绑定到对象上的任意深度属性

Ale*_*lex 7 javascript angularjs

(对不起,如果我的问题标题不是很好,我想不出更好的.请随意提出更好的选择.)

我正在尝试在Angular中创建一个可重用的"属性网格",其中可以将对象绑定到网格,但是这样可以在某种程度上定制对象的呈现.

这是指令模板的样子(这form-element对我的问题不重要,所以我会把它留下来):

<div ng-repeat="prop in propertyData({object: propertyObject})">
    <div ng-switch on="prop.type">
        <div ng-switch-when="text">
            <form-element type="text"
                          label-translation-key="{{prop.key}}"
                          label="{{prop.key}}"
                          name="{{prop.key}}"
                          model="propertyObject[prop.key]"
                          focus-events-enabled="false">
            </form-element>
        </div>
    </div>
</div>
Run Code Online (Sandbox Code Playgroud)

并且,指令代码:

angular.module("app.shared").directive('propertyGrid', ['$log', function($log) {
    return {
        restrict: 'E',
        scope: {
            propertyObject: '=',
            propertyData: '&'
        }
        templateUrl: 'views/propertyGrid.html'
    };
}]);
Run Code Online (Sandbox Code Playgroud)

这是一个示例用法:

<property-grid edit-mode="true"
               property-object="selectedSite"
               property-data="getSitePropertyData(object)">
</property-grid>
Run Code Online (Sandbox Code Playgroud)

和getSitePropertyData()它一起使用的功能:

var lastSite;
var lastSitePropertyData;
$scope.getSitePropertyData = function (site) {
    if (site == undefined) return null;

    if (site == lastSite)
        return lastSitePropertyData;

    lastSite = site;
    lastSitePropertyData = [
        {key:"SiteName", value:site.SiteName, editable: true, type:"text"},
        //{key:"Company.CompanyName", value:site.Company.CompanyName, editable: false, type:"text"},
        {key:"Address1", value:site.Address1, editable: true, type:"text"},
        {key:"Address2", value:site.Address2, editable: true, type:"text"},
        {key:"PostalCode", value:site.PostalCode, editable: true, type:"text"},
        {key:"City", value:site.City, editable: true, type:"text"},
        {key:"Country", value:site.Country, editable: true, type:"text"},
        {key:"ContactName", value:site.ContactName, editable: true, type:"text"},
        {key: "ContactEmail", value: site.ContactEmail, editable: true, type:"email"},
        {key: "ContactPhone", value: site.ContactPhone, editable: true, type:"text"},
        {key: "Info", value: site.Info, editable: true, type:"text"}
    ];
    return lastSitePropertyData;
};
Run Code Online (Sandbox Code Playgroud)

我正在经历这样一个"属性数据"功能而不仅仅是直接绑定到对象上的属性的原因是我需要控制属性的顺序,以及是否应该向用户显示它们,为了呈现,它还有什么样的属性(文本,电子邮件,数字,日期等).

首先,正如您可以从函数中的value属性残余中看出的那样getSitePropertyData(),我首先尝试直接从此函数提供值,但是这不会绑定到对象,因此更改对象或表单属性网格没有来回同步.然后,接下来,正在使用这个key想法,这让我这样做:propertyObject[prop.key]- 这对于直接属性很有用,但正如你所看到的,我必须注释掉"公司"字段,因为它是属性的属性,并且propertyObject["a.b"]不起作用.

我在努力想弄清楚这里要做什么.我需要绑定才能工作,我需要能够在绑定中使用任意深度属性.我知道这种事情在理论上是可行的; 我已经看到它在UI Grid中完成了,但是这样的项目有很多代码,我可能会花几天时间找出它们是如何做到的.

我接近了,还是我说错了?

Nik*_*los 2

您想要在对象上运行任意 Angular 表达式。$parse这正是( ref )的目的。该服务可以很好地...解析 Angular 表达式并返回 getter 和 setter。以下示例是formElement指令的过于简化的实现,演示了 的用法$parse:

app.directive('formElement', ['$parse', function($parse) {
  return {
    restrict: 'E',
    scope: {
      label: '@',
      name: '@',
      rootObj: '=',
      path: '@'
    },
    template:
      '<label>{{ label }}</label>' +
      '<input type="text" ng-model="data.model" />',
    link: function(scope) {
      var getModel = $parse(scope.path);
      var setModel = getModel.assign;
      scope.data = {};
      Object.defineProperty(scope.data, 'model', {
        get: function() {
          return getModel(scope.rootObj);
        },
        set: function(value) {
          setModel(scope.rootObj, value);
        }
      });
    }
  };
}]);
Run Code Online (Sandbox Code Playgroud)

我稍微改变了指令的使用方式,希望不改变语义:

<form-element type="text"
    label-translation-key="{{prop.key}}"
    label="{{prop.key}}"
    name="{{prop.key}}"
    root-obj="propertyObject"
    path="{{prop.key}}"
    focus-events-enabled="false">
Run Code Online (Sandbox Code Playgroud)

其中root-obj是模型的顶部,path是到达实际数据的表达式。

如您所见,$parse为任何根对象的给定表达式创建 getter 和 setter 函数。在model.data属性中,您将创建的访问器函数应用于$parse根对象。整个Object.defineProperty构造可以用监视来替换,但这只会增加摘要周期的开销。

这是一个工作小提琴:https://jsfiddle.net/zb6cfk6y/


顺便说一句,另一种(更简洁和惯用的)编写 get/set 的方法是:

  Object.defineProperty(scope.data, 'model', {
    get: getModel.bind(null, scope.rootObj),
    set: setModel.bind(null, scope.rootObj)
  });
Run Code Online (Sandbox Code Playgroud)