1

I have a list of states (Florida, Alabama ...) and I want to create named anchors above the first occurance of the first letter.

Letter Links

  <nav>
    <ul class="letters">
      <li ng-repeat="letter in locations.getLetters()">
        <a href="#{{letter}}">{{letter}}</a>
      </li>
    </ul>
  </nav>

States

     <nav>
        <ul class="locations">
          <li ng-repeat="state in locations.states">{{state.state}}
            <a ng-if="" id="{{state.state.charAt(0)}}">fgsdf</a>
            <ul>
              <li ng-repeat="city in state.cities">
                <a href>{{city.name}}</a>
              </li>
            </ul>
          </li>
        </ul>
      </nav>

I am stuck at <a ng-if="" id="{{state.state.charAt(0)}}">fgsdf</a>

I have tried ng-if="!document.getElementById(state.state.charAt(0))" and that doesn't work. Does anyone have any suggestions as to how I should go about this?

Update

I've considered using angular's built-in filters to filter the states in ngRepeat. When a user clicks A, only the states starting with A should show. This seems like a much cleaner and more intuitive approach and will improve UX.

Rafael
  • 7,605
  • 13
  • 31
  • 46
  • Why are you using `ng-if`? I would suggest you to have a look at this question about how to deal with anchor links in angular. https://stackoverflow.com/questions/14712223/how-to-handle-anchor-hash-linking-in-angularjs – pasine May 14 '15 at 22:54
  • 2
    You approach is a big anti-pattern, and so does the suggested answer. You should not rely on the DOM and absolutely not do a `getElementById` in a watcher. The correct way in your `ng-if` (or even better in a `filter` on the repeat) would be to check if `state.state[0]` is included in the `location.getLetters()` array: stay in your model objects, you don't need to interact with the DOM. – floribon May 14 '15 at 23:01
  • @floribon I ended up going with this solution – Rafael May 15 '15 at 14:13

2 Answers2

2

You can try this approach

let's assume you have the input as a simple array of strings. before placing it in the controller, we can group states by the first letter of each state using a simple object (the letter is a key, the value is an array of strings)

http://jsfiddle.net/q2y93ym7/1/

html:

<body ng-app="HelloApp" ng-controller="Controller" class="container"> 
    <div class="well">
        <a href="#{{letter}}" ng-repeat="(letter,states) in letters">{{letter}} </a> 
    </div>
    
    <div ng-attr-id="{{letter}}" ng-repeat="(letter,states) in letters">
         <h1>{{letter}}</h1>
         <h2 ng-repeat="state in states">{{state}}</h2>
        <hr>
    </div>
</body>

js:

angular.module('HelloApp', [])
    .controller('Controller', function ($scope) {
    var states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyomin'];

    // Let's prepare the input

    var letters = $scope.letters = {};
    states.forEach(function (state) {
        var letter = state.charAt(0);
        if (!letters[letter]) {
            letters[letter] = [];
        }

        letters[letter].push(state);
    });

})

EDIT:

  • As @DRobinson says, nothing guarantees keys of an Object will be sorted. therefore you can try using this great approach / instead an Object, use an array
  • added <h1>{{letter}}</h1>, thanks @Tony
Community
  • 1
  • 1
Jossef Harush Kadouri
  • 32,361
  • 10
  • 130
  • 129
  • 1
    This is a *much* better solution, but you shouldn't assume that the keys of an Object will be sorted ("a" before "b", before "c", etc.). Consider taking the letters Object and returning an Array (e.g. `[{letter: 'A', states: [...]}, {letter: 'B', states: [...]}, ...]`) – DRobinson May 14 '15 at 23:17
  • The only thing missing is I think OP wants the letter heading the states, [like this](http://jsfiddle.net/kz4y4kx9/). Regardless, great answer. – Tony May 14 '15 at 23:20
  • Great solution. @DRobinson I will consider manipulating the JSON data like that. Thank you everyone – Rafael May 15 '15 at 00:27
-2

That won't work because document is not part of the current scope. Here's what you can do:

Controller:

$scope.elementExists = function (selector) {
    return typeof $document.getElementById(selector) !== 'undefined';
}

Html:

<a ng-if="!elementExists('elementId')">Link</a>

Don't forget to add $document as a dependency of your controller.

bamboo_inside
  • 462
  • 4
  • 15
  • 2
    Don't do this. For one thing, every `$digest` cycle that function will have to be called for every state. That's unnecessary, and a slippery slope. Scanning for the element on the page is surely a crutch masking a logical/architectural problem - I'm certain one can solve this better, without resulting to looking for the element on the page. Further: it looks like it will become wrong - it will alternate between not finding anything so creating the node, and finding itself (and then removing itself because it was found). – DRobinson May 14 '15 at 23:02
  • (Note: the creating / removing itself is contingent on you actually keeping the ID on the element, as is the case in the original question). – DRobinson May 14 '15 at 23:02