jokul
jokul

Reputation: 1339

Angular model doesn't scope to variable within child DOM element?

I have this markup:

<div data-ng-model="currentUser.attributes">
    <div>{{username}}</div>
</div>

And this is a stripped down version of my controller:

$scope.username = "Alice";
$scope.currentUser = {
    attributes: {
        username: "Bob"
    }
};

I want Bob to display, but instead, I am getting Alice. It works just fine if I use this:

{{currentUser.attributes.username}}

But I don't want to have to scope down to this variable's properties every time I want to access something. How can I get the element to exist within the scope of currentUser.attributes?

Upvotes: 0

Views: 105

Answers (4)

alettieri
alettieri

Reputation: 396

This post really got me thinking. I had a theory on how to accomplish this using a directive.

Came up with a proof of concept on plnkr: http://embed.plnkr.co/OJDhpJ1maEdSoPvlbiRA/

If I understand correctly, you want to only display the properties within a given block of your struct.

Given the following struct:

$scope.currentUser = {
    attributes: {
      username: 'Batman',
      age: '99',
      address: {
        street: 'Bat Cave'
      }
    }
  };

You want to scope things down with something like:

<div scope-with="currentUser.attributes">
    Username: {{username}}<br />
    Age: {{age}}
    <div scope-with="address">
       Street: {{street}}
    </div>
</div>

Directive:

angular.module('mymodule', [])

.directive('scopeWith', function($interpolate){


  return {

    restrict: 'A',
    scope: {
      scopeWith: '='
    },
    transclude: 'element',
    compile: function(tElement, tAttrs, linker) {

      return function( scope, element, attr) {

        var childScope,
            parent = element.parent(),
            withBlock = null
        ;

        scope.$watch('scopeWith', function(val){
          childScope = scope.$new();

          angular.forEach(val, function(val, prop){
            childScope[prop] = val;  
          });


          if(withBlock) {
            withBlock.el.remove();
            withBlock.scope.$destroy();
          }

          linker(childScope, function(clone){

            withBlock = {};
            parent.append(clone);
            withBlock.el = clone;
            withBlock.scope = childScope;

          });


        }, true);

      };

    }

  };

Upvotes: 1

bhantol
bhantol

Reputation: 9616

If you want Bob just do the the following in your HTML.

<div>{{current user}}</div>//IGNORE THIS

<div>{{currentUser.attributes.username}}</div>//UPDATED CORRECTED

UPDATED based on clarification.

So in Knockout you do this

<p data-bind="with: currentUser.attributes">
    <div data-bind="text: userName></div>
    <div data-bind="text: login></div>
    <div data-bind="text: bhalBlah></div>
    <div data-bind="text: yaddaYadda></div>
</p>

<script type="text/javascript">
    ko.applyBindings({
        currentUser: {
           attributes: {
                userName  : 'Bob',
                login     : 't@e',
                blahBlah  : 'ttttt',
                yaddaYadda: 'x'
           }
        }
    });
</script>

Same thing in AngularJS would be

<p ng-controller="myCtrl">
    <div>{{currentUser.attributes.userName}}</div>
    <div>{{currentUser.attributes.login}}</div>
    <div>{{currentUser.attributes.blahBlah}}</div>
    <div>{{currentUser.attributes.yaddaYadda}}</div>
</p>


<script type="text/javascript">
angular.module('myApp',[]).controller('myCtrl',function($scope){
    $scope =  {
     currentUser: {
           attributes: {
                userName  : 'Bob',
                login     : 't@e',
                blahBlah  : 'ttttt',
                yaddaYadda: 'x'
           }
    };
});
</script>

In this the question is how to avoid how not to repeat the part the full property paths between ** as shown below in angular.

  **currentUser.attributes.**userName
  **currentUser.attributes.**login
  **currentUser.attributes.**blahBlah
  **currentUser.attributes.**yaddaYadda

Here is one way see plnkr using ng-init which reduces 'currentUser.attributes' to just 'attr'.

With just attr.<properties> repeated

{{attr.userName}} {{attr.login}} {{attr.blahBlah}} {{attr.yaddaYadda}}

Another way is you restructure your object and flatten it on the $scope. This is not recommended because now you are putting primitives on to the $scope and are widening the scope with $scope.userName = currentUser.attributes.username. Also your 'repetitive' code is still there just in the Javascript.

In lieu of ng-init

ng-init="attr = currentUser.attributes"

You could also do this in controller

$scope.attr = currentUser.attributes;

Upvotes: 1

Shelby L Morris
Shelby L Morris

Reputation: 726

While I don't think you should really do this, it is what you're asking for. You can essentially mimic with by using ng-repeat on an array that you populate with the relevant object. For example:

<div ng-repeat="user in [currentUser.attributes]">
    {{ user.username }}
</div>

Working plunker: http://plnkr.co/edit/svwYEeWMQXjuAnLkr9Vz?p=preview

Other possible solutions would be to have a service or controller that has functions to get the attributes and return them, cleaning up the syntax of your HTML and making it easier to change backend stuff without breaking your frontend. Your choice.

Edit: I noticed you actually expect to be able to do {{ username }} and get the relevant info, if that's really what you want then I suggest my second proposal. Create functions that return the relevant info.

<div>
    {{ getCurrentUserName() }}
</div>

$scope.getCurrentUserName = function() {
    return $scope.currentUser.attributes.username;
};

Your call, take it or leave it.

Upvotes: 2

peterorum
peterorum

Reputation: 1421

Use {{currentUser.username}} to show Bob.

The ng-model on the div is irrelevant as it only applies to input elements.

Upvotes: 0

Related Questions