Alireza Mirian
Alireza Mirian

Reputation: 6672

Access child scope corresponding to a model variable

I have a list of items and I've rendered them in a template via ng-repeat. Each of them is controlled by an itemController which exposes some behavior for item (e.g. grabing focus). Here is the HTML:

<body ng-controller="mainController">
  <button ng-click="addItem()">add</button>
  <ul>
    <li ng-repeat="item in items" ng-controller="itemController">
      <div ng-if="item.isEditing">
        <input ng-model="item.name"/>
        <button ng-click="item.isEditing=false">done</button>
      </div>
      <span ng-if="!item.isEditing">{{item.name}}</span>
    </li>
  </ul>
  </body>

In the mainController, I have a function for adding a new item to items. Here is the code for mainController:

app.controller('mainController', function($scope){
  $scope.items = [
    {
      name: "alireza"
    },
    {
      name: "ali"
    }
  ];
  $scope.addItem = function(){
    $scope.items.push({isEditing: true});
  }
});

Whenever I add an item to items array, a corresponding li element is added into the view which is controlled by an instance of itemController, and the corresponding model is the new item I've just added (or maybe the scope of the itemController, which contains item).

Problem:

When I add some item to items, I only have access to item and not the scope of the recently created item. So I can't run some function (like grabFocus) on the scope of new item. Is it something semantically wrong in my design? What is the canonical approach for this problem?

Plunker link

Here is the plunker link with related comments

Upvotes: 0

Views: 973

Answers (2)

Troy Gizzi
Troy Gizzi

Reputation: 2519

You can use $broadcast from the parent scope, along with $on from the child scope, to notify the child scopes of the newly added item. And by passing (as an argument) the $id of the child scope that corresponds to the newly added item, each child catching the event can know whether or not it's the one that needs to have grabFocus() called.

Here's a fork of your Plunker that uses that approach. I wasn't sure what you were trying to accomplish with $element.find(":text").focus(); in your original Plunker, so I tweaked it to toggle a $scope property that in turn controlled a style in the view. The newly added item will be red (by calling its own grabFocus function to toggle the flag to true), and the others will be black (by calling their own loseFocus function to toggle the flag to false).

Modified HTML (just the repeated li):

<li ng-repeat="item in items" ng-controller="itemController">
  <div ng-if="item.isEditing">
    <input ng-model="item.name"/>
    <button ng-click="item.isEditing=false;handleItemAdded($index);">done</button>
  </div>
  <span ng-if="!item.isEditing" ng-style="{ color: isFocused ? 'red' : 'black' }">{{item.name}}</span>
</li>

Full JavaScript:

var app = angular.module("app",[]);
app.controller('mainController', function($rootScope, $scope){
  $scope.items = [ { name: "alireza" }, { name: "ali" } ];
  $scope.addItem = function(){
    $scope.items.push({isEditing: true});
  };
  $scope.handleItemAdded = function (index) {
    // $rootScope.$broadcast('item-added', { index: index });
    for(var cs = $scope.$$childHead; cs; cs = cs.$$nextSibling) {
      if (cs.$index === index) {
        $rootScope.$broadcast('item-added', { id: cs.$id });
        break;
      }
    }
  };
});
app.controller('itemController', function($scope, $element){
  $scope.$on('item-added', function (event, args) {
    if ($scope.$id === args.id + 1) {
      $scope.grabFocus();
    } else {
      $scope.loseFocus();
    }
  });
  $scope.grabFocus = function() { 
    $scope.isFocused = true;
  };
  $scope.loseFocus = function() { 
    $scope.isFocused = false;
  };
});

Upvotes: 1

Urielzen
Urielzen

Reputation: 506

I changed your approach a little bit by creating an unique id for every input, based on its index number. See code below, hope it helps.

// Code goes here


var app = angular.module("app",[]);
app.controller('mainController', function($scope,$timeout){
  $scope.items = [
    {
      name: "alireza"
    },
    {
      name: "ali"
    }
  ];
  $scope.addItem = function(){
    $scope.items.push({isEditing: true});
    $timeout(function(){
      document.getElementById("newItem"+($scope.items.length-1)).focus();
    },0)
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script data-require="[email protected]" data-semver="1.3.0" src="//code.angularjs.org/1.3.0/angular.js"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
  </head>

  <body ng-controller="mainController">
  <button ng-click="addItem()">add</button>
  <ul>
    <li ng-repeat="item in items">
      <div ng-if="item.isEditing">
        <input ng-model="item.name" id="newItem{{$index}}"/>
        <button ng-click="item.isEditing=false">done</button>
      </div>
      <span ng-if="!item.isEditing">{{item.name}}</span>
    </li>
  </ul>
  </body>

</html>

Upvotes: 0

Related Questions