Does AngularJS have a syntax to alias a property w

2020-05-27 11:15发布

问题:

This is kind of a weird question, but here's the idea:

Let's say I have a complex JSON object coming back from an HTTP call and attaching to the $scope. Something like this:

$scope.obj = {
    user: {
        id: 10,
        name: { first: 'Joe', last: 'Smith' },
        contact: {
            home: {
                street: '101 First St.',
                city: 'Myville',
                state: 'Jokelahoma',
                zip: '98765'
            },
            email: 'joeshmoe@gmail.com',
            phone: '+12345678901'
        }
    },
    purchase_hist: [
        { item_id: 11004, date: 'Thu, 06 Aug 2015 13:51:17 GMT' },
        { item_id: 97020, date: 'Fri, 31 Jul 2015 18:57:57 GMT' }
    ]
}

Now, if I wanted to display an overview of purchase history in an AngularJS partial, I could do something like this:

<table>
    <tr ng-repeat="p in obj.purchase_hist">
        <td>{{p.item_id}}</td>
        <td>{{p.date}}</td>
    </tr>
</table>

The really convenient thing about this format (though it's not super evident here with so few props) is that the purchase being described is aliased as p. I don't have to do obj.purchase_hist[0].item_id, I can just do p.item_id.

But what about when I go to show the user's home address? Do I really have to do this?:

<div>
    <p>{{obj.user.contact.home.street}}</p>
    <p>{{obj.user.contact.home.city}}</p>
    <p>{{obj.user.contact.home.state}}</p>
    <p>{{obj.user.contact.home.zip}}</p>
</div>

That's really verbose. I would much rather use something akin to the controller as ... syntax, something like this:

<div ng-alias="obj.user.contact.home as uhome">
    <p>{{uhome.street}}</p>
    <p>{{uhome.city}}</p>
    <p>{{uhome.state}}</p>
    <p>{{uhome.zip}}</p>
</div>

Is there such a thing that exists in AngularJS ? Unfortunately I'm not very able to use plugins in my environment, so I'm specifically looking for a part of angular core that will work this way.

Thanks!

回答1:

I've written this little directive, which allow you to perform what you want :

Directive ngAlias

(function(){

  function ngAlias($compile) {
    return {
        restrict: "A",
        link: function(scope, element, attrs) {
          var args = attrs.ngAlias.split('as').map(function(elm){return elm.replace(/ /g,'')});

          scope[args[0]] = '';

          var dot = args[1].split('.');

          var object = {};

          dot.forEach(function(value, index){
            index === 0
            ? object = scope[value]
            : object = object[value] === null ? object[value] = {} : object[value];
          });

          console.log(object)

          scope[args[0]] = object;
        }
    };
  }

angular
  .module('app')
  .directive('ngAlias', ngAlias);


})();

For example, set your object in your controller

Controller

(function(){

function Controller($scope) {


  $scope.obj = {
    toto: {
      nom: 'toto',
      prenom: 'tata'
    }
  };

}

angular
.module('app', [])
.controller('ctrl', Controller);

})();

And you can use it :

HTML

  <body ng-app="app" ng-controller="ctrl">

    <div ng-alias="toto as obj.toto">
      {{toto.nom}}
    </div>

  </body>


回答2:

@PaulBoutes provided the answer I needed, and he should get the credit; I just wanted to add the version of the directive that I settled on based on his answer.

app.directive('alias', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var splits = attrs['alias'].trim().split(/\s+as\s+/);
            scope.$watch(splits[0], function(val) {
                scope.$eval(splits[1]+'=('+splits[0]+')');
            });
        }
    };
});

Same basic idea as Paul's, just cleaned up a little and made a little more flexible in terms of whitespace and such.

Usage example:

<div data-alias="obj.user.contact.home as uhome">
    <p>{{uhome.street}}</p>
    <p>{{uhome.city}}</p>
    <p>{{uhome.state}}</p>
    <p>{{uhome.zip}}</p>
</div>


回答3:

Certainly there's no shortage of workable solutions here, but there shouldn't be harm in adding another.

I threw this together the other day to save myself some typing and make my html easier to read. It creates a new child scope, so it doesn't pollute the existing scope with these aliases. It simply works by binding the parent scope's property to the new child scope with whatever name you specify.

app.directive('with', function() {
    return {
        restrict: 'A',
        scope: true,
        link: function(scope, element, attrs) {
            if (attrs.as) {
                scope[attrs.as] = scope.$eval(attrs.with);
            } else {
                angular.extend(scope, scope.$eval(attrs.with));
            }
        }
    };
});

So, instead of doing this:

<div>{{some.deeply.nested.model.id}} - {{some.deeply.nested.model.name}}</div>

Do this:

<div with="some.deeply.nested.model" as="t">{{t.id}} - {{t.name}}</div>

Or, do it without using as:

<div with="some.deeply.nested.model">{{id}} - {{name}}</div>


回答4:

I came across this just now and tested out the ngAlias directive, which may or may not work fine, I don't know because I had issues with it, probably due to some other unrelated error, and I decided on this simpler method:

<div ng-repeat="uhome in [obj.user.contact.home]">
    <p>{{uhome.street}}</p>
    <p>{{uhome.city}}</p>
    <p>{{uhome.state}}</p>
    <p>{{uhome.zip}}</p>
</div>

Tada! "obj.user.contact.home" is now aliased to "uhome". I believe this solution is actually exactly what you wanted when you asked this question, too, because it literally uses ng-repeat and therefore has all the benefits of ng-repeat's built-in aliasing mechanism.



回答5:

<div ng-init="uhome = obj.user.contact.home">
  <p>{{uhome.street}}</p>
  <p>{{uhome.city}}</p>
  <p>{{uhome.state}}</p>
  <p>{{uhome.zip}}</p>
</div>

https://code.angularjs.org/1.3.16/docs/api/ng/directive/ngInit



回答6:

As far as i know there is nothing really simple. There are some hack. You can remap object to new scope using new controller

<div ng-controller="Ctrl as c" ng-init="c.makeAlias(obj.user.contact.home)">
  <p>{{c.home}}</p>
</div>

where Ctrl just store makeAlias argument to it's scope as home

this.makeAlias(home) {
  this.home = home;
}

Another solution is use $scope.$watch and remap object to the more convenient one.



回答7:

I don't think Angular provides out of box supports for such things. But You can definitely create Domain object.Convert your http response into domain object. Within your domain object you can have helper method to get the required information. For example in your case you can have Domain object like this

  factory('User', function () {
    return function (id,name, contact, purchase_hist) {
        this.id = id;
        this.name = name;
        this.contact = contact;
        this.purchase_hist = purchase_hist;
        this.getContact = function(){
          return this.contact.home;
        };

    };
})

you can use this domain object in your controller

$scope.user =  // domain object return  from your service

in html you can use

{{user.getContact().street}}

You can redefine your domain to make thing easier.It might be additional work. But when you have to do same thing again and again.One can enjoy writing code this way