Extending text filter - angularjs

Here I'm attempting to extend the standard text filter to perform a get request and pass value to extended filter user has entered
The filter name is 'search' :
myapp.filter('search', function($filter){
console.log('search param'+$scope.search)
$http.get('http-hello2.html').success(function (data) {
return $filter;
});
But receive error :
Error: [$http:badreq] http://errors.angularjs.org/1.4.9/$http/badreq?p0=undefined
at Error (native)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:6:416
at m (https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:85:218)
at Function.c.$get.m.(anonymous function) [as get] (https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:90:109)
at link (https://run.plnkr.co/rz2TWpQpyYaVbHXN/script.js:14:27)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:73:222
at ca (https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:73:279)
at I (https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:62:174)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:69:193
at https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js:119:221 <status-viewer url="sourceUrl" class="ng-isolate-scope">
How to extend standard AngularJS text filter to invoke custom functionality and pass parameter to this filter? The custom functionality should occur prior to the standard filter logic being invoked.
plnnkr : https://plnkr.co/edit/F0XsOPZKq5HArFo9vtFs?p=preview
src :
goob.html :
goob
http-hello2.html
2. http-hello2.html
test.html :
test
index.html :
<!doctype html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<label>Filter: <input ng-model="search"></label>
<div ng-repeat="sourceUrl in sourceUrls | filter:search">
<status-viewer url="sourceUrl"> </status-viewer>
</div>
</div>
</body>
</html>
mytemplate.html :
<!--<h1>{{url}}</h1>-->
<div>
<p>{{model}}</p>
</div>
script.js :
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('statusViewer', function ($http , $interval) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
scope.isFinishedLoading = false;
$http.get(scope.url).success(function (data) {
scope.model = data;
});
}
};
});
myapp.filter('search', function($filter){
console.log('search param'+$scope.search)
$http.get('http-hello2.html').success(function (data) {
return $filter;
});
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello2.html',
,'test.html'
,'goob.html'];
}

First of all you need to inject $http Service into you filter to prevent error that you have:
myapp.filter('search', function($filter, $http){
and second thing:
you should not do
$http.get(scope.url)
not being sure that scope.url is already set.
I would suggest to do request by condition;
if(scope.url){
//$http.get(scope.url)...
}

Error comes out because initially you had no value in
$scope.search value and url has passed as undefined, other thing is you were tried to use $scope inside filter, which can't be accessible
As I can see that you don't wanted to load some template on change of input value. And from your current approach you wanted to load template from filter based on input(if I understood it correctly). But it seems like you are on wrong track.
Basically filtering is used to do some manipulation on binding while showing some value(like showing normal word in uppercase will use uppercase filter). Here you are using the filter for loading template on input change(basically that would fire a filter but not because of input change, it fires on digest cycle). Every time digest cycle will run it will make an ajax to fetch that template. Technically angular filter is not meant for what you are doing there.
Extending text filter turns out to be wrong approach as you don't need to extend it.
Better I'd say you should put ng-change event on input field with some function, that will fire up that function on each input change. and it will not make any change in filter working. By making this changes this would make template call only when input is changed
Markup
<input ng-model="search" ng-change="change()" />
Code
$scope.change = function() {
//code here.
};

Related

How to bind ng-click to a custom directive and call a parent function?

We are using Angular 1.4.2 and I am trying to take a count value from a directive using ng-click, pass it to a function, then pass it up to the parent controller. After some effort it is working in a plunker, but unfortunately when I tried to move this functionality back into the main code, I'm not able to get a controller to bind to the isolated scope.
Should be simple, but I've tried injecting the current controller into the directive and trying to create a new controller, but nothing happens when I press click on the button.
Here is the code:
TEMPLATE:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.4.2" data-semver="1.4.2" src="https://code.angularjs.org/1.4.2/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div id="app" ng-app="app">
<div ng-controller="mainCtrl">
<my-directive ctrl-fn="ctrlFn(count = count + 10)"></my-directive>
</div>
</div>
</body>
</html>
SCRIPT:
var app = angular.module('app', []);
app.controller('mainCtrl', function($scope){
$scope.count = 0;
$scope.ctrlFn = function() {
console.log('In mainCtrl ctrlFn!');
//$scope.count += 10; Old hardcoded value.
console.log("count is: " + JSON.stringify($scope.count));
//Call service here
};
});
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
'ctrlFn' : '&'
},
template: "<div><button ng-click='ctrlFn()'>Click Here</button></div>",
link: function(scope, element, attributes) {
scope.ctrlFn(count);
}
};
});
Here is the template code I'm trying to modify in the main code base:
<div>
<div layout="row">
<results-loader ctrl-fn="ctrlFn(count = count + 10)"></results-loader>
<md-button class="md-raised md-primary md-button" ng-click="ctrlFn()" flex>Click Me</md-button>
</div>
</div>
and here is where I use an existing controller in my directive as a parent controller. It's defined in a route, rather than ng-controller and is already used for this view.
myresultsCtrl.$inject = ['$scope', ' myService'];
/* #ngInject */
function myresultsCtrl($scope, myService) {
$scope.count = 0;
etc...
however, it apparently isn't bound properly as I never hit the directive or this function with ng-click.
Like I said I tried adding a new controller to the template, then I tried injecting an existing controller into the directive, but neither worked. I took out the template from the directive and tried to put the ng-click directly into the template with ctrl-fn, but I wasn't sure how to wire up the click with the call to the ctrl-fn attribute of the directive with both in the template? The idea here is to move the template into it's own html file and reference it from the directive, as in: template: "myFile.html. I'm trying to enscapsulate as much as possible to make this into a reusable component.
I haven't worked much with custom directives.
Here is the direct link to the plunker.
I don't know your exact assignment, but rethink your architecture. Why do you want to count the value in the directive? In your simple case it would be better to count the value in a service or in the controller, not a directive.
Injecting controller in the directive is anti-angular pattern. You need to rethink your intentions. It is better to pass the number to the directive, make your calculations there and send your number back to the controller. It would work without extra code, because of the two-way data binding. Here a fork of your plunker:
http://plnkr.co/edit/KpB6bv5tHvXSvhErLcEp?p=preview
Main part is the definiton of the directive:
<div><span>{{count}}</span><br /><button ng-click='myFunction()'>Calculate</button></div>
I prefer not to answer my own questions as that can have a negative appearance, but in this case, I don't know if I could have explained the question well enough to get the right answer. What was throwing me off was integrating my working plunker code into our existing code base.
Where I got stuck was how to setup the controller properly for this use case. Though I declared my controller, in the module and injected the controller as it was already bound to that view, I needed to define the controller in the directive itself. Once I did all three together, everything started working.
Here are some code snippets:
angular.module('directiveName', [])
.directive('directiveName', directiveName)
.controller('injectedCtrl', injectedCtrl)
etc...
var directive = {
restrict: 'E',
scope: {
'ctrlFn' : '&'
},
template: "<div><button ng-click='ctrlFn()'>Click Here</button></div>",
controller: "injectedCtrl",
link: function(scope, element, attributes) {
scope.ctrlFn(); //This will pass to the parent controller: injectedCtrl, where $scope resides.
}
}
return directive;
}
injectedCtrl.$inject = ['$scope', 'myService'];
/* #ngInject */
function injectedCtrl($scope, myService) {
$scope.ctrlFn = function() {
//do something
}
etc...
HTML CODE:
When the button is clicked from the directive, ctrlFn here is a reference that is under my directives isolated scope in the DDO. This takes the external function that was passed in, namely: ctrlFn() which can then be invoked from the directive to call the parent controller.
<div layout="row">
<results-loader ctrlFn="ctrlFn()"></results-loader>
</div>
I'm posting this to help someone else that might have this use case as it was not easy to figure this out.
Dah Wahlins post goes into this subject in greater detail: Creating Custom AngularJS Directives Part 3 - Isolate Scope and Function Parameters and what helped to get my thinking straightened out.

How to bind content to individual div with promise

This plnkr : https://plnkr.co/edit/BjETLN7rvQ1hNRIm51zG?p=preview binds content to three divs within loop : <div ng-repeat="id in ids">
src :
{ "content" : "divContent" , "id" : "r1" }
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<div ng-repeat="id in ids">
<div ng-bind="content" ></div>
</div>
</div>
</body>
</html>
// Example of how to call AngularJS $http service and
// process the returned promise
function FetchCtrl($scope, $http, $q) {
$scope.ids = ["r1", "r2", "r3"];
$scope.ids = $scope.ids.map(function(id){
var getString = 'http-hello1.html?id='+id
return $http.get(getString);
});
$scope.responses = [];
$q.all($scope.ids).then(function (values) {
var counter = 0;
values.map(function(m){
$scope.content = m.data.content;
})
})
}
But how bind the result of each get request to the specific div ?
Could add id : <div id="{{id}}" ng-bind="content" ></div> but this means I need to maintain a map of id,value entries ? Is there an idiomatic angularjs way to achieve this ?
I think a directive which dynamically fetches your content might be the answer for you.
angular.module('whateverYourModuleNameIs')
.directive('dynamicRow', ['$http', '$interval', dynamicRowDirectiveFn]);
function dynamicRowDirectiveFn($http, $interval) {
return {
restrict: "EA", // I guess this is your choice how it renders
scope: {
id: '=' // you could grab the id and use it in your request
},
link: function linkFn(scope, element, attrs) {
// Use $interval to repeatedly fetch content
var repeatFetchWhichReturnsAPromise = $interval(fetchNewContent, 60000 * 15) //Executes function every x milliseconds.
// Function that executes every time your interval occurs
function fetchNewContent() {
$http.get('urlYouNeedToFetch'+id).then(
fetchNewContentSucess, fetchNewContentError
);
}
function fetchNewContentSuccess(responseObject){
//This sets your new HTML based on promise success
element = responseObject.data;
}
function fetchNewContentError(responseObject){
//If its a bad request we probably either want to stop repeating
// You can choose to do something else
$interval.cancel(repeatFetchWhichReturnsAPromise);
}
}
}
}
So Instead of using $q.all(), Id recommend individually fetching the content based on a timer or specific trigger. The downside with $q.all() is that if one of the promises fail, they all fail.
In terms of knowing what specific URL the directive needs to fetch, you'll have to provide that information to the directive to be used.
This is a very rough example of a directive that you could write. The upside is that you don't have to worry about bind-unsafe-html or include ngSanitize, you are instead just resetting the value of element inside your link function.
As I don't have a better picture of what you are trying to accomplish from a feature/product standpoint I can only suggest this based on the info provided.

Angular script won't run when first loaded

On page load the console log prints but the toggleClass/click won't work I even use angular.element but it has the same result.I need to change state in order for the toggleClass to work.I dunno what's wrong in my code.
.run(['$rootScope', function ($rootScope) {
console.log('test');//this prints test and it's ok
//this part won't load at the first loading of page.
$('.toggle-mobile').click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
//....
}])
even doing it this way doesn't work.
$rootScope.$on('$viewContentLoaded', function () {
angular.element('.toggle-mobile').on('click', function (event) {
angular.element(this).toggleClass('toggle-click');
angular.element('.menu-mobile').toggle();
event.preventDefault();
});
});
The Angular way to render items is different from "On DOM Ready" that is why we need to treat these as 2 separate things.
Angular could render items later on even after DOM is ready, this could happen for example if there is an AJAX call($http.get) and that is why a directive may be the recommended approach.
Try something like this:
<body ng-controller="MainCtrl">
<div toggle-Me="" class="toggle-mobile"> Sample <div class="menu-mobile">Sample 2</div>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {}]);
myApp.directive("toggleMe", function() {
return {
restrict: "A", //A - means attribute
link: function(scope, element, attrs, ngModelCtrl) {
$(element).click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
}
};
});
...
By declaring the directive myApp.directive("toggleMe",... as an attribute toggle-Me="" every time angular generates the input element it will execute the link function in the directive.
Disclaimer: Since the post lacks from a sample html I made up something to give an idea how to implement the solution but of course the suggested html is not part of the solution.

AngularJS - how to override directive ngClick

I want to override directive ng-click: to some make some $rootscope changes before each execution of ng-click. How to do it?
Every directive is a special service inside AngularJS, you can override or modify any service in AngularJS, including directive
For example remove built-in ngClick
angular.module('yourmodule',[]).config(function($provide){
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
//$delegate is array of all ng-click directive
//in this case first one is angular buildin ng-click
//so we remove it.
$delegate.shift();
return $delegate;
}]);
});
angular support multiple directives to the same name so you can register you own ngClick Directive
angular.module('yourmodule',[]).directive('ngClick',function (){
return {
restrict : 'A',
replace : false,
link : function(scope,el,attrs){
el.bind('click',function(e){
alert('do you feeling lucky');
});
}
}
});
check out http://plnkr.co/edit/U2nlcA?p=preview
I wrote a sample that removed angular built-in ng-click and add a customized ngClick
You can't override AngularJS built-in directives. However, you can define multiple directives with the same name and have them executed against the same element. By assigning appropriate priority to your directive, you can then control whether your directive runs before or after a built-in directive.
This plunker shows how to build an ng-click directive that executes before the built-in ng-click does. The code is also shown below. When clicking the link, the custom ng-click will run first, then the built-in ng-click does.
index.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="jquery#1.9.0" data-semver="1.9.0" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.js"></script>
<script data-require="angular.js#1.0.7" data-semver="1.0.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="alert()">Click me</a>
</body>
</html>
script.js
angular.module('app', [])
.directive('ngClick', function($rootScope) {
return {
restrict: 'A',
priority: 100, // give it higher priority than built-in ng-click
link: function(scope, element, attr) {
element.bind('click', function() {
// do something with $rootScope here, as your question asks for that
alert('overridden');
})
}
}
})
.controller('MyCtrl', function($scope) {
$scope.alert = function() {
alert('built-in!')
}
})

Binding the placeholder to the model causes ng-change to execute on load in IE

Using angularjs, if I bind the placeholder of an input to its model, the change event is fired when the document loads in IE. This does not appear to be correct and I'm not seeing this behavior in other browsers.
JS Fiddle
Html:
<div ng-app="angularjs-starter" data-ng-controller="MainCtrl">
<div data-ui-view="viewMain">
<input
placeholder="{{theValue}}"
data-ng-model="theValue"
data-ng-change="valueChanged(theValue)" />
</div>
Javascript:
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.valueChanged = function(theValue) {
alert("Value Change Called On Load in IE.");
};
});
It's possible to use the built-in ng-attr-placeholder directive as well.
ng-attr-placeholder="{{theValue}}"
I know this is old but just in case anyone else runs in to this I created a small directive that goes around putting a dynamic value in the placeholder and instead have it assign when it changes:
.directive('dynamicPlaceholder',
function() {
return {
restrict: 'A',
link: function ($scope, element, attrs) {
attrs.$observe('dynamicPlaceholder', function(value) {
element.attr('placeholder', value);
});
}
};
});
Then to use this all you need to do is use dynamic-placeholder instead of placeholder:
<input ng-model='someValue' dynamic-placeholder='{{someDynamicPlaceholder}}' />
Not sure what is causing the problem in IE though

Resources