0

I'm trying to develop a single web application which is a blog, displaying posts. They are included in a template by the ng-repeat directive:

   <div class="post" data-ng-repeat="post in postList ">
        <div class="date">published: {{post.published_at | date:'dd-MM-yyyy, HH:mm'}}</div>
            <a class="btn btn-default" data-ng-click="editPost(post)"><span class="glyphicon glyphicon-pencil"></span></a>
            <a class="btn btn-default" data-ng-click="deletePost(post)"><span class="glyphicon glyphicon-remove"></span></a>
            <h1><a href="">{{post.title}}</a></h1>
            <p>{{post.text}}</p>
        </div>
   </div>      

They have fields, such as title, text and publishing date, defined in the controller. I'd like to filter them by various criteria. For this purpose, I tried to implement my own custom filter (so that I can filter by more than 1 field):

angular.module("blog").
filter('bytitle', function() {
return function(posts, title) {
  var out = [];
  // Filter logic here, adding matches to the out var.
  var i;
  for(i = 0; i < posts.length; i++){
     if(posts[i].title.indexOf(title) >=0){
        out.push(posts[i]);
     }
  }
  return out;
}
});

however, if I run the javascript console, I get the following error, caused only by the presence of the code above:

Argument 'postController' is not a function, got undefined

I'm new to angular, and I'm not really sure what this means. Any ideas?

The entire source code: http://plnkr.co/edit/suATcx8dQXZqcmmwlc0b?p=catalogue

Edit 2: The problem is partially solved. I added this filter functionality:

 <div class="post" data-ng-repeat="post in postList | bytitle : filterTerm">

but something goes wrong while running the script:

TypeError: Cannot read property 'length' of undefined

It occurs at line 7 (the one with posts.length).

2
  • in you file with filters instead angular.module("blog", []). you need angular.module("blog").. in first case - you create module in second - get. see doc: When passed two or more arguments, a new module is created. If passed only one argument, an existing module (the name passed as the first argument to module) is retrieved. Commented May 17, 2015 at 23:07
  • okay, fair enough. something is still wrong though, please see my edits Commented May 17, 2015 at 23:15

2 Answers 2

1

in you file with filters instead angular.module("blog", []) you need angular.module("blog").
in first case - you create module in second - get.

see doc:

When passed two or more arguments, a new module is created. If passed only one argument, an existing module (the name passed as the first argument to module) is retrieved.

sidenote: in plunker you have wrong reference to js files

You have error with length property, because before loading posts by ajax, you not init this variables, so in filter passed undefined.

You can modify your filter like

angular.module("blog").
filter('bytitle', function() {
  return function(posts, title) {
    var out = [];

    //if not pass posts - return empty
    if(!posts) return out;

    //if not pass title, or it empty - return same collection
    if(!title) return posts;

    // Filter logic here, adding matches to the out var.
    var i;
    for (i = 0; i < posts.length; i++) {
      if (posts[i].title.indexOf(title) >= 0) {
        out.push(posts[i]);
      }
    }
    return out;
  }
});

var app = angular.module("blog", []);

app.controller("postController", function($scope, $http, $timeout) {

  var path = 'http://private-79b25-blogtt.apiary-mock.com';
  $scope.titleFilter = "";
  $scope.contentFilter = "";

  
  $http.get(path + '/posts')
    .success(function(data, status, headers, config) {
      $timeout(function() {
        $scope.postList = data;
      });
    })
    .error(function(data, status, headers, config) {
      console.log("error getting " + status);
    });
  $scope.form_header = "New post";

  $scope.addPost = function() {
    var post = {
      title: $scope.title,
      text: $scope.text,
      published_at: new Date()
    };

    $http.post(path + '/posts', post)
      .success(function(data, status, headers, config) {
        $scope.postList.push(post);
      })
      .error(function(data, status, headers, config) {
        console.log("error posting " + status);
      });

    $scope.title = "";
    $scope.text = "";
  };

  $scope.deletePost = function(post) {
    var del = confirm("Are you sure you want to delete or modify this post?");
    if (del) {
      var i = $scope.postList.indexOf(post);
      $scope.postList.splice(i, 1);
    }
  };

  var backupPostContent;
  $scope.editPost = function(post) {
    $scope.deletePost(post);
    $scope.form_header = "Edit post";
    $scope.title = post.title;
    $scope.text = post.text;
    backupPostContent = post;
  };

  $scope.cancelEdit = function() {
    $http.post(path + '/posts', backupPostContent)
      .success(function(data, status, headers, config) {
        $scope.postList.push(backupPostContent);
        $scope.form_header = "New post";
      })
      .error(function(data, status, headers, config) {
        console.log("error posting " + status);
      });
    $scope.title = "";
    $scope.text = "";
  };


  $scope.filter = function(term) {

  }


});


angular.module("blog").
filter('bytitle', function() {
  return function(posts, title) {
    var out = [];

    if(!posts) return out;

    if(!title) return posts;
    // Filter logic here, adding matches to the out var.
    var i;
    for (i = 0; i < posts.length; i++) {
      if (posts[i].title.indexOf(title) >= 0) {
        out.push(posts[i]);
      }
    }
    return out;
  }
});
#wrap {
  width: 600px;
  margin: 0 auto;
}
#left_col {
  float: left;
  width: 300px;
}
#right_col {
  float: right;
  width: 300px;
}
body {
  padding: 0px 15px;
}
.row-centered {
  text-align: right;
}
.page-header {
  background-color: #cb892c;
  margin-top: 0;
  padding: 20px 20px 20px 40px;
}
.page-header h1,
.page-header h1 a,
.page-header h1 a:visited,
.page-header h1 a:active {
  color: #ffffff;
  font-size: 36pt;
  text-decoration: none;
}
.content {
  margin-left: 40px;
}
h1,
h2,
h3,
h4 {
  font-family: Helvetica, sans-serif;
}
.date {
  float: right;
  color: #828282;
}
.save {
  float: right;
}
.post-form textarea,
.post-form input {
  width: 60%;
}
.top-menu,
.top-menu:hover,
.top-menu:visited {
  color: #ffffff;
  float: right;
  font-size: 26pt;
  margin-right: 20px;
}
.post {
  margin-bottom: 70px;
}
.post h1 a,
.post h1 a:visited {
  color: #000000;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="blog" ng-controller="postController">
  <div id="wrap">
    <div id="left_col">
      <h3> Search </h3>
      <p>
        <input data-ng-model="filterTerm" />
      </p>
    </div>
    <div id="right_col">
      <div id="wrap">
        <div id="left_col">
          <input type="checkbox" value="topic" id="title" ng-model="titleFilter" />In topics
          <br>
          <input type="checkbox" value="content" id="content" />In contents
          <br>
          <input type="checkbox" value="content" id="content" />In tags
          <br>Between
          <input type="text" type="text" class="datepicker" />
        </div>

        <div id="right_col">
          <br>
          <br>
          <br>and
          <input type="text" type="text" class="datepicker" />
          <br/>
        </div>
      </div>
    </div>
  </div>
  <div class="content container" style="padding-top: 50px">
    <div class="row">
      <div class="col-md-8 col-centered">
        <div class="post" data-ng-repeat="post in postList | bytitle : filterTerm ">
          <div class="date">published: {{post.published_at | date:'dd-MM-yyyy, HH:mm'}}</div>
          <a class="btn btn-default" data-ng-click="editPost(post)"><span class="glyphicon glyphicon-pencil"></span></a>
          <a class="btn btn-default" data-ng-click="deletePost(post)"><span class="glyphicon glyphicon-remove"></span></a>
          <h1><a href="">{{post.title}}</a></h1>
          <p>{{post.text}}</p>
        </div>
      </div>
      <div class="col-md-4 col-centered">
        <h1>New post</h1>
        <form class="post-form">
          <h4>Title:</h4>
          <p>
            <input type="text" name="title" data-ng-model="title">
          </p>
          <h4>Text:</h4>
          <p>
            <textarea name="text" data-ng-model="text"></textarea>
          </p>
          <button type="submit" class="save btn btn-default" ng-click="addPost()">Save</button>
          <button type="reset" class="btn btn-default">Clear</button>
          <button type="button" class="btn btn-default" ng-click="cancelEdit()">Cancel edit</button>
        </form>
      </div>
    </div>
  </div>
</div>

Sign up to request clarification or add additional context in comments.

Comments

1

EDIT

I didn't see the answer from @grundy before making my comments or edits, so it should be accepted as the answer, but I wanted to point out two things:

Working Plunker

My preferred approach is to use the angular.isDefined / angular.isArray:

angular.module("blog").
  filter('bytitle', function() {
    return function(posts, title) {
      if(angular.isDefined(title) && angular.isArray(posts)) {
        var out = [];
        // Filter logic here, adding matches to the out var.
        var i;
        for(i = 0; i < posts.length; i++){
            if(posts[i].title.indexOf(title) >=0){
              out.push(posts[i]);
            }
        }
        return out;
      } else {
        return posts;
      }
   }
});

Second, I just wanted to point out that while writing your own filters is sometimes necessary and certainly a great skill to master, the easiest way to filter on a single property is to use the built in filter filter by adding the property to the model value that you want to search on:

<input data-ng-model="filterTerm.title" />
<input data-ng-model="filterTerm.text" /> 

and then in your repeat add the filter using just the object name as follows:

<div class="post" data-ng-repeat="post in postList | filter: filterTerm ">

You can then use the same filter for multiple properties.

4 Comments

Can you provide a Plunkr so I can see the controller, sample model and the view in context to help you debug? I dont see postController anywhere in the code you posted so I don't know where it's originating.
Actually its probably just a really simple typo, but you've defined your blog module twice! If you look in the filters.js file and simply remove the empty array that is the second argument to the module definition, Angular will instead look for an existing module called blog. So it should be: angular.module("blog").filter( ... or of course you can do: app.filter(...
Yes, it was already pointed out above, corrected it. If you could see what error occurs in my latest edit, would be great ;)
So the filter is adjusted to only run if the title is defined otherwise it just returns the posts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.