Trouble creating search in an Angular directive - javascript

I'm trying to use Angular's search filter in the following way and cannot figure out what I am doing wrong.
This application should be able to use the search input to search all manufacturer names and items from the controller.
I know it is working somewhat correctly because my browser shows the image and manufacturer.name of both objects: manufacturer1 and manufacturer2, but it is not showing the items and the search function doesn't work.
Help is greatly appreciated! I'm pretty new to Angular.
<div ng-controller="SearchCtrl">
<input type="search" ng-model="search" class="form-control" placeholder="Search Manufacturers, Solutions, Equipment, Etc.">
<manufacturer info="manufacturer1"></manufacturer>
<manufacturer info="manufacturer2"></manufacturer>
</div>
Controller:
app.controller('SearchCtrl', ['$scope', function ($scope) {
$scope.manufacturer1 = {
name: 'Business Name',
items: [
'service1',
'service2',
'service3'
],
image: 'assets/images/image.png'
};
$scope.manufacturer2 = {
name: 'Other Business',
items: [
'product1',
'product2',
'product3'
],
image: 'assets/images/image.png'
};
}]);
Directive JS
app.directive('manufacturer', function() {
return {
restrict: 'E',
scope: {
info: '='
},
templateUrl: 'assets/js/directives/manufacturer.html'
};
});
Directive Template:
<div class="col-md-4">
<h2 class="business-title">{{ info.name }}</h2>
<img ng-src="{{ info.image }}" class="feature-img" alt="image"/>
<ul>
<li ng-repeat="item in items | filter:search">
{{ info.items }}
</li>
</ul>
</div>

As manufacturer directive has isolated scope, so you couldn't have access to search variable inside your directive you need to pass the search variable to the directive from the directive attribute & include that property inside directive isolated scope, as you have done for the info variable.
Basically you need to add serach: '=' inside your directive isolated scope, which will pass the search input box value to the directive by using attribute search="search". = is used for two way binding to update the search result as user will change the input the info will get filtered automatically.
Markup
<input type="search" ng-model="search" class="form-control" placeholder="Search Manufacturers, Solutions, Equipment, Etc.">
<manufacturer info="manufacturer1" search="search"></manufacturer>
<manufacturer info="manufacturer2" search="search"></manufacturer>
Directive
app.directive('manufacturer', function() {
return {
restrict: 'E',
scope: {
info: '=',
search: '='
},
templateUrl: 'assets/js/directives/manufacturer.html'
};
});
Update
You have wrong html inside your manufacturer.html, specifically the content which is showing items using ng-repeat
<ul>
<li ng-repeat="item in info.items | filter:search">
{{ item }}
</li>
</ul>
Demo Plunkr

Related

ngRepeat on custom directive tags

I have a list of custom directives which are different widgets on my dashboard. The directives are defined as follows:
angular.module('core').directive('graphCardWidget', function() {
return {
restrict: 'E',
replace: true,
scope: {
target: '=target'
},
templateUrl: 'modules/core/widgets/graph-card.client.widget.html'
};
});
angular.module('core').directive('pieChartWidget', function() {
return {
restrict: 'E',
replace: true,
scope: {
target: '=target'
},
templateUrl: 'modules/core/widgets/pie-chart.client.widget.html'
};
});
In my controller, I have a list of widgets to be displayed. The list is as follows:
$scope.dashboardWidgets = [
{
directive : 'graph-card-widget',
target : 'widgets.dashboards.activeDevicesCard'
},
{
directive : 'graph-card-widget',
target : 'widgets.dashboards.activeSessionsCard'
},
{
directive : 'pie-chart-widget',
target : 'widgets.dashboards.devices'
},
{
directive : 'pie-chart-widget',
target : 'widgets.dashboards.sessions'
}
];
Now in my view, I use ng-repeat to iterate this array and display the items. Here is the code of my view:
<div layout="row" layout-wrap layout-align="center" layout-xs="column" ng-drop="true">
<div ng-repeat='widget in dashboardWidgets'>
<{{widget.directive}} ng-drag="true" flex='45' target='{{widget.target}}'>
</{{widget.directive}}>
<span flex='5'></span>
</div>
</div>
But the browser renders this as text. Here is what I get in my DOM:
<div layout="row" layout-wrap="" layout-align="center" layout-xs="column" ng-drop="true" class="layout-wrap layout-xs-column layout-align-center-stretch layout-row">
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<graph-card-widget ng-drag="true" flex='45' target='widgets.dashboards.activeDevicesCard’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<graph-card-widget ng-drag="true" flex='45' target='widgets.dashboards.activeSessionsCard’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<pie-chart-widget ng-drag="true" flex='45' target='widgets.dashboards.devices’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<pie-chart-widget ng-drag="true" flex='45' target='widgets.dashboards.sessions’>”
<span flex="5" class="flex-5"></span>
</div>
</div>
So what can I do to make the directive rendered as a tag and not plain text?
I saw a couple of questions similar to these such as, question 1, question 2 but they all are adding the dynamic directives in standard HTML tags. What I need is a dynamic tag.
Update:
After following #cnexans answer, I got it working partially. The widget is drawn but the target attribute is not evaluated which leads to a blank widget.
Here's the plunkr with the issue: https://plnkr.co/edit/BGN6C4LAHguWthU4fGy0?p=preview
You can use ng-if inside ng-for in order to switch the name "directive" within each iteration to show one or another directive.
<div ng-repeat="data in dashboardWidgets">
<div ng-if="data.directive == 'graph-card-widget'">
<graph-card-widget ng-drag="true" flex='45' target=data.target>
</graph-card-widget>
<span flex='5'></span>
</div>
<div ng-if="data.directive == 'pie-chart-widget'">
<pie-chart-widget ng-drag="true" flex='45' target=data.target>
</pie-chart-widget>
<span flex='5'></span>
</div>
</div>
You can create a container directive to take care of this logic too, so you can share this functionality to other pages if needed.
<widgets-container widgets-list=data></widgets-container>
Working example: https://codepen.io/anon/pen/jBzEpe?editors=1010#0
Edit:
Checking the example you gave, you need to pass a Widget object to the directive, and you are passing a string. Here is a working example forked from the plunkr given
https://plnkr.co/edit/3Oxxmp?p=preview
It has a function namespaceToObject which transform the string into the desired object from $scope.
don't know whether this will match your requirement. but i did some modification to your array.
I did this using ng-bind-html and when your are binding custom element like directive you need to compile it again. for that i create(actually borrow) this directive.
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I changed the ng-repeat like this
<div ng-repeat='widget in dashboardWidgets' compile-template ng-bind-html="trust(widget.directive)">
<span flex='5'></span>
</div>
trust function will return trusted html
$scope.trust = function(someHTML){
return $sce.trustAsHtml(someHTML);
}
modify the array like this
$scope.dashboardWidgets = [
{
directive : '<graph-card-widget ng-drag="true" flex="45" target="widget.target"></ graph-card-widget>',
target : 'widgets.dashboards.activeDevicesCard'
},
{
directive : '<graph-card-widget ng-drag="true" flex="45" target="widget.target"></ graph-card-widget>',
target : 'widgets.dashboards.activeSessionsCard'
},
{
directive : '<pie-chart-widget ng-drag="true" flex="45" target="widget.target"></ pie-chart-widget>',
target : 'widgets.dashboards.devices'
},
{
directive : '<pie-chart-widget ng-drag="true" flex="45" target="widget.target"></ pie-chart-widget>',
target : 'widgets.dashboards.sessions'
}
];
Demo

Updating ngModel from Custom Directive

I've built a custom directive for generating and populating a dropdown from JSON Data.
However I want to react to a dropdown selection with a function. Therefore I want to make use of ngChange (and ngModel).
So I try to start by updating the ng-model from the directive.
The link function in the Directive should give me everything I need.
BUT the fourth parameter is undefined therefore I can't use it or even compile.
Directive TS:
module lsw{
lswApp.lswAppModule.directive("guiDropdown",
() => ({
restrict: "E",
require: "ngModel",
scope: {
data: "=",
ctrl: "="
},
link(scope, element, attrs, ctrls) {
element.bind("change", () => {
});
},
templateUrl: "../../App_Scripts/Directives/guiDropdown.html"
}));}
"ctrls:{}"
My Idea was to make use of ctrls.$setViewValue to update ngModel
Directive HTML
<div class="elements">
<select name="{{data.name}}" class="dropdown-box">
<option ng-repeat="option in ctrl" ng-value="option">{{option}}</option>
</select>
Main View HTML
<div ng-repeat="element in HomeController.wrapper" ng-if="HomeController.trigger">
<div ng-switch="element.type">
<div ng-switch-when="dropdown">
<gui-dropdown data="element" ctrl="HomeController.content" ng-model="element" ng-change="HomeController.test()"></gui-dropdown>
</div>
<div ng-switch-when="textfield">
<gui-textfield data="element"></gui-textfield>
</div>
<div ng-switch-when="checkbox">
<gui-checkbox data="element"></gui-checkbox>
</div>
</div>
</div>

Use directive with different controllers in AngularJS?

I have created a directive for a search box which i want to use with different views. Here is the directive -
angular.module('jobSeekerApp')
.directive('searchBoxDirective', function () {
return {
restrict: 'E',
templateUrl: 'templates/searchbox-template.html',
};
});
template for the directive -
<span class="searchButton"><i class="fa fa-search fa-2x"></i></span>
<input ng-change="search()" ng-model="searchTerm" ng-keydown="deleteTerm($event)" type="text" id="search-box" style="width: 0px; visibility:hidden;"/>
I want to use this directive on two views which look like this -
View 1 -
<div class="panel panel-default companies" ng-repeat="company in companies.companiesList">
<div class="panel-heading text-center"><a ng-href="/companies/{{company.id}}" class="hvr-sink"><h3 class="well">{{company.company_name}}</h3></a></div>
<div class="panel-body text-center flexcontainer">
<div>Location: {{company.location}}</div>
<div>Founded In: {{company.founded_year}}</div>
<div ng-if="company.opening">Opening: Yes</div>
<div ng-if="!company.opening">Opening: No</div>
<div>Number Of Openings: {{company.no_openings}}</div>
</div>
</div>
View 2 -
<div class="panel panel-default jobs" ng-repeat="job in jobs.jobsList">
<div class="panel-heading text-center"><h3 class="well">{{job.job_name}}</h3></div>
<div class="panel-body text-center flexcontainer">
<div>Company: {{job.company_name}}</div>
</div>
</div>
As you can see i am using aliases companies and jobs in my views, due to this my directive is not able to affect the view it is contained in. If i use the companies or jobs in my template , then it works fine. So for example if change the template to -
<span class="searchButton"><i class="fa fa-search fa-2x"></i></span>
<input ng-change="companies.search()" ng-model="companies.searchTerm" ng-keydown="companies.deleteTerm($event)" type="text" id="search-box" style="width: 0px; visibility:hidden;"/>
Then it works with the view associated with companies controller and similarly for jobs.
How can i use the directive with the respective controller instance?
Thank you.
Create a simple view for Search
Create it's own controller
Search record by this controller in service and put data in service variable
this.searchResult = [];
this.search = function(searchText){
// code to search
this.searchResult = Result;
}
Now where ever you want to use this result use the watch on this service variable in current controller, like:
$scope.$watch(function() { return servicename.searchResult ; }, function(newVal, oldval) {
if(newval != oldval){
$scope.data = servicename.searchResult;
/* Do the rest of stuff */
}
}, true);
Since the search function is asynchronous, I recommend avoiding the use of ng-change to invoke it. Instead use ng-click on the search icon.
<span class="searchButton" ng-click="$ctrl.searchFn()">
<i class="fa fa-search fa-2x"></i>
</span>
<input ng-model="$ctrl.searchTerm" type="text" id="search-box">
In the directive, use isolate scope and bindToController.
app.directive('searchBoxDirective', function () {
return {
restrict: 'E',
scope: { 'searchTerm': "=",
'searchFn': "&"
},
controller: function () {},
controllerAs: '$ctrl',
bindToController: true,
templateUrl: 'templates/searchbox-template.html'
};
});
Usage
<search-box-directive search-term="companies.searchTerm"
search-fn="companies.search()" >
</search-box-directive>
<search-box-directive search-term="jobs.searchTerm"
search-fn="jobs.search()" >
</search-box-directive>
The search-term attribute creates a bidirectional binding from parent scope to the directive isolate scope. The search-fn attribute creates an expression binding from parent scope to isolate scope.
Use isolate scope and explicitly pass the search term to your directive. Something like:
angular.module('jobSeekerApp')
.directive('searchBoxDirective', function () {
return {
restrict: 'E',
scope: {
searchTerm: '=' <-- you can use '#' if you don't need two-way binding
},
templateUrl: 'templates/searchbox-template.html',
};
});
You didn't show where you are actually using your directive, but you would pass the scope property through an attribute (this is using your directive on a <div>):
<div search-box-directive search-term="companies.searchTerm"></div>

Correct way to access the scope from a directive on multiple controllers

I have a directive named add-tags than I am planning to use in different places of the app, I am using it to add tags to an array then save from the main ctrl, then the user can edit the list when previewing from a different view/ctrl (modal), on main page I have:
<add-tags tags="tags"></add-tags>
and my directive is set up as follow:
'use strict';
angular.module('theApp')
.directive('addTags', function() {
return {
templateUrl: 'components/add-tags/add-tags.html',
//restrict: 'EA',
scope: {
tags:'='
},
link: function($scope, $element, attrs) {
} //link
};
});
From the edit controller, how can access the previous tags data? when I do,
<add-tags tags="workDetails.tags"></add-tags>
the entire data from is gone from the scope, but when I do:
<span ng-repeat="x in workDetails.tags">
<h1>{{x.name}}</h1>
</span>
I can see the list of tags, any help will be appreciated :)
I am adding the add-tags.html example:
<div ng-repeat="tag in tags" class="text-center new-item">
<div class="input-group">
<input type="text" name="" class="form-control" ng-model="tag.name">
<span class="input-group-btn">
<button
type="button"
class="btn btn-default"
ng-click="deleteTag($index)">
Delete
</button>
</span> <!-- /.input-group-btn -->
</div> <!-- /.input-group -->
</div>
<div class="form-group" ng-hide="tags.length == tags_max">
<input type="text" class="form-control" placeholder="Enter tag:" ng-model="tag">
</div>
<!-- /.form-group -->
<button
type="button"
class="btn btn-primary center-block"
ng-disabled="tags.length == tags_max"
ng-class="{ 'btn-danger': tags.length == tags_max}"
ng-click="addTag()">
Add tag
</button>
<hr>
There is nothing wrong with the code you've given. However - unless I am missing some information - is it possible you're assuming that your "workDetails" object should be available everywhere? The only way I could get your scenario to work is by adding a "workDetailsService" that holds the tag state.
Here's a working plunkr of the code bits you provided, but with a workDetailsService added to maintain state and some routing.
Basically I added a service to maintain the tags:
theApp.factory('workDetailsService', function() {
return {
tags: [{
name: "Tag 1"
}, {
name: "Tag 2"
}, {
name: "Tag 3"
}]
}
});
And injected that service into two directives, "listTags" and "editTags":
theApp.directive('editTags', ['workDetailsService', function(workDetailsService) {
return {
templateUrl: '/edit-tags.html',
link: function($scope, $element, attrs) {
$scope.tags_max = 4;
$scope.tags = workDetailsService.tags;
$scope.addTag = function() {
workDetailsService.tags.push({
name: $scope.tag
})
}
$scope.deleteTag = function(index) {
workDetailsService.tags.splice(index, 1)
}
}
};
}]);
theApp.directive('listTags', ['workDetailsService', function(workDetailsService) {
return {
templateUrl: '/list-tags.html',
link: function($scope, $element, attrs) {
$scope.tags = workDetailsService.tags;
}
};
}]);
This way you have your tags state in one place, and the directives wrap the tags up instead of controllers so you can reuse everywhere.

Custom search filter in angularjs

I am using the inbuilt search filter of angular js like this-
<!--header starts-->
<input class="form-control fs-mini search" ng-model="search.name" type="text" placeholder="&#xe80a Search">
<!--header ends-->
<!--content area-->
<div ng-repeat="user in users | filter:search">
{{ user.name }}
{{user.l}}
{{user.time}}
</div>
<!--content area ends-->
Now i removed the header template codes and created the header directive.
<!--header starts-->
<div site-header>
</div>
<!--header ends-->
<!--content area-->
<div ng-repeat="user in users | filter:search">
{{ user.name }}
{{user.l}}
{{user.time}}
</div>
<!--content area ends-->
site-header.js:
'use strict';
angular.module('myApp')
.directive('siteHeader', function () {
return {
templateUrl: 'views/header-view.html',
scope: {
},
restrict: 'A',
controller: [ '$scope', '$rootScope', '$location', function($scope, $rootScope, $location) {
console.log($scope.data);
}],
link: function postLink(scope, element, attrs) {
}
};
});
header-view.html
<input class="form-control fs-mini search" ng-model="search.name" type="text" placeholder="&#xe80a Search">
All html templates is loading correctly. but the search filter is not working. I dont understand how to bind search.name to the directive in order to work. I tried like this-
<div site-header="search.name">
</div>
but how to access this data in directive and bind it to ng-model?
Change your directive definition:
app.directive('myDirective', function(){
return {
restrict: 'A',
template: [
'<input class="form-control fs-mini search" ng-model="search.name" type="text" placeholder="&#xe80a Search"/>'
].join(''),
scope: true
}
});
here is a demo:http://plnkr.co/edit/JNCjzs?p=preview
Directives get their own scopes in angular. Scopes prototypically inherit from their parent scopes, which can be confusing and takes some time and knowledge of js inheritance to get your head around. If you can't be bothered with that, a quick solution to your problem would be to use an object to store the search string and instantiate it in the parent scope, which will make it available in both scopes. I think all you need is to put the line:
`var search = {};`
in your controller.

Categories