Building a small search app using Elasticsearch and AngularJS. 2 pages, home and results page. Everything is working except... I have this custom search directive that I'm trying to pass the value of a service into. The service is a variable that is bound to ngModel in my controller.
How can I pass the value of searchTerms from home to the results page?
My service
.service('queryService', function() {
var searchTerms;
this.searchTerms = null;
I pass my service into the controller and set it to $scope
$scope.searchTerms = queryService.searchTerms;
I then $watch it for changes
$scope.$watch('searchTerms', function() {
queryService.searchTerms = $scope.searchTerms;
});
My directive looks like this
.directive('searchResults', ['queryService', function(queryService) {
return {
restrict: 'E',
replace: true,
//priority: 1001,
scope: {
searchTerms: "=",//ngModel
results: "=",
websiteUrls: "=",
suggestions: "&",
search: "&"
},
templateUrl: 'search/search-results.html',
link: function(scope, element, attrs) {
}
}
}]);
My search input:
<input type="text" name="q" ng-model="searchTerms" placeholder="Search" class="form-control input-lg" id="search-input" uib-typeahead="query for query in getSuggestions($viewValue)" typeahead-on-select="search($item)" autofocus>
I have 2 way data binding working, but no autocomplete(Angular UI Bootstrap Typeahead) or search functionality. I'm pretty sure something is suppose to go in the link function, just not sure what... still learning AngularJS directives.
NOTE: Everything works if I perform a search from the results page.
More Info So basically what I'm trying to do is a user enters a search Term on the home page. searchTerms is my ngModel. I'm using AngularJS UI Bootstrap Typeahead for autocomplete functionality(as can be seen on the input tag). I have a queryService that initiates searchTerms to null and the queryService is DI into the controller. I have a directive that has isolate scope (scope: {}), where I am passing searchTerms, the results object and both the autocomplete and search functions. I'm using ngRoute for now because I'm trying to keep this simple until I have it working - its only 2 pages.
HTML Snippet
<search-results ng-model="searchTerms" website-urls="page" results="results" uib-typeahead="query for query in getSuggestions($viewValue)" typeahead-on-select="search($item)"></search-results>
Light Bulb moment, maybe
As I continue to learn more about directives, I think I just solved the problem. I had all this working initially without the use of a directive. Using routes, templates and controllers. I should just be able to use my current controller in my directive, right?
The only reason why I want to use a directive is because it seems to be the best option when AngularJS is used in combination with a CMS.
Am I on the right track now?
After further reading (and understanding), it seems that I can use the controller I already have in my directive and that should solve this problem quite nicely. I will post results after completing and testing it.
Related
I've inherited another legacy system that a previous colleague was working on, he was asked to remove server side components from an app and replace them with AngularJS... it's an old JSP system and everything is a bit of a hack. Anyway, there is a jQuery library within the old system that produces Modal Windows, rather than have to add UI-Bootstrap my colleague converted this jQuery service to an AngularJS directive and it kind of works... but with one exception, here is the directive code...
.directive('popupWindow', ['$document', '$compile', function ($document, $compile, MessagesService) {
"use strict";
return {
restrict: 'A',
scope: {},
link: function (scope, element, attrs) {
var newPopup = new Basebox({
contentUrl: '/ng/xtemplate/widgets/CCC/' + scope.getType + '.html',
type: 'regular',
id: 'sf-basebox',
hideBackground: false,
overlay: true,
resolveTest: {getType: scope.getType, message: scope.message, config: scope.config }, // this is appended to the Modal Window as a ng-init and it renders correctly!
closeOnOverlay: false,
closeIcon: true,
checkOldIe: true,
afterContentLoad: function () {
$compile($document.find("#basebox"))(scope);
scope.$apply();
}
});
newPopup.create();
}
});
}
};
}]);
Now this works as it allows us to import HTML templates, it produces the Modal Window and allows us to set various properties, it also allows is to pass data objects (see the resolveTest property) to the controller of the model window. Everything works! Anything within the angularJS templating {{ }} is rendered and we can use angular directives such as ng-init, ng-repeat, etc, etc... however when trying to have two way binding between the view and the controller nothing seems to work, the data gets rendered from the controller to the view but not the other way around. The view never updates... here is the view and the controller
.controller('reportModelCtrl', function ($scope) {
$scope.val = {};
$scope.val.foo = 'blue';
})
Here is the view...
<div ng-controller="reportModelCtrl">
<input type="text" ng-model="val.foo"> {{ val.foo }} this is by default 'blue' but never updates
</div>
Like I say, I dynamically add an ng-init to the modal template and this works, but when I amend the text box nothing happens. Can anyone think of something I'm missing? Even if I try to update values using methods in the controller and ng-click in the view nothing is updated. Calling $scope.$apply() just results in an error. Do I need to add a form to my view? Sorry the jQuery Basebox library isn't included here but I just thought it would add to the confusion. I just really want suggestions or hints to getting the two way binding to work rather than a full answer.
I'm creating a product gallery directive in Angular, which will allow the user to scroll through images with left/right arrows.
What is the most appropriate angular approach to feed my directive with the array of image URLs?
You can assume that the parent controller has already made an API call to receive this the array of URLs
E.g.
<div data-ng-controller="MyController as myController">
<my-image-gallery></my-image-gallery>
</div>
Should I just have an attribute of that takes in the JSON array? Perhaps something like:
<my-image-gallery images="myController.ImageList"></my-image-gallery>
Although, I'm not even sure if the above is possible. It would mean the JSON would have to be converted into a string?
There must be a better way
Edit
As per comments, I've tried the above method, but I can't access the "images" field from within my controller.
Here is what I have defined in my directive:
scope: {
imageSource: '='
},
Then in my controller, I assume I should just be able to reference the variable imageSource, shouldn't I?
I think you're using a kinda weird tutorial or something to learn angular. You can use the MyController as MyController syntax, but the goal of that is to avoid using $scope. I personally don't agree with it and don't understand why people would want to do that.
When you attach a value to $scope it becomes available in your view directly (without needing $scope). For example, $scope.images would be passed in to your directive as just images.
To have the directive process that value as a variable instead of a string it must be defined using an = (as opposed to an #) you can read more about this in the angular directive docs
Here is an example of how this would work:
Javascript
angular.module('app',[])
.controller('myCtrl',['$scope',function($scope){
$scope.imageList=['img1','img2','img3','img...'];
}])
.directive('myImageGallery',function(){
return {
restrict: 'E',
scope:{
images:'='
},
controller: ['$scope',function($scope){
console.log($scope.images);
}],
replace: true,
template: '<ul><li ng-repeat="img in images">{{img}}</li></ul>'
}
})
HTML
<body ng-app="app">
<div ng-controller="myCtrl">
<my-image-gallery images="imageList"></my-image-gallery>
</div>
</body>
and here is a plunker of it in action.
I am trying to call (or use) few custom directives in ionic framework, dynamic is like <mydir-{{type}} where {{type}} will come from services and scope variable, having values radio, checkbox, select etc, and created my directives as mydirRadio, MydirCheckbox, mydirSelect, But its not working.
Is their any good approach to get the dynamic html as per {{type}} in scope?
Long story short; no you can't load directives dynamically in that way.
There are a few options for what you can do. You can, as other answers have mentioned, pass your context as an attribute (mydir type="checkbox"). You could make a directive that dynamically loads another directive, as also mentioned by others. Neither of these options are imo every good.
The first option only works if you write the directive yourself, not when using something like ionic. It also requires you to write multiple directives as one, which can get very messy very quickly. This mega directive will become hard to test and easy to mess up when maintaining it in the future. Note that this is the correct way to pass data to a directive from the view, it's just not good for this specific use case.
The second option is problematic because obfuscates things a bit too much. If someone reads your html and sees a directive called dynamic that is given dynamic data... they have no idea what is going to happen. If they see a directive called dropdown that is given a list they have a fair idea of what the result will be. Readability is important, don't skimp on it.
So I would suggest something simpler that requires much less work from you. Just use a switch:
<div ng-switch="type">
<mydir-select ng-switch-when="select"></mydir-select>
<mydir-checkbox ng-switch-when="checkbox"></mydir-checkbox>
</div>
I dont understand why do you need dynamic directives.
Simple use single directive and change the template accordingly.
For example -
angular.module('testApp')
.directive('dynamicDirective', function($compile,$templateCache,$http) {
return {
restrict: 'C',
link: function($scope,el) {
//get template
if(radio){
$http.get('radio.html', {cache: $templateCache}).success(function(html){
//do the things
el.replaceWith($compile(html)($scope));
});
} else if(checkbox){
//load checkbox template
} //vice-versa
}
};
});
You can inject service variable in directive also.
a bit more code would help. I don't know, if its possible to do dynamic directives like the ones in a tag
<{dyntag}></{dyntag}>
but you also can use an expression like
<your-tag dynamic_element="{type}">...</your-tag>
which should have exactly the same functionality. In your case it would be like:
Your JSObject ($scope.dynamics):
{"radio", "checkbox", "select"}
and your HTML:
<div ng-repeat="dyn in dynamics">
<your-tag dynamic_element="{dyn}"></your-tag>
</div>
Yes, that's not a problem. You can interpolate your data using {{}} and in your directive compile a new element using that data:
myApp.directive('dynamic', function($compile, $timeout) {
return {
restrict: "E",
scope: {
data: "#var" // say data is `my-directive`
},
template: '<div></div>',
link: function (scope, element, attr) {
var dynamicDirective = '<' + scope.data + ' var="this works!"><' + scope.data + '>';
var el = $compile(dynamicDirective)(scope);
element.parent().append( el );
}
}
});
HTML:
<div ng-controller="MyCtrl">
<dynamic var="{{test}}"></dynamic>
</div>
Fiddle
I'm new to JavaScript and Angular JS.
I have to write a custom directive to validate the content of an input field passed in an .aspx page; more precisely I have to check if an username with the value passed in the input already exists. In my directve I have to call a service that checks for duplicates in the db and afterwards depending on the result I have to perform a success callback/error callback and to return an object containing info about the validation check and a message to diplay. In particular I'm stuck on this last part regarding callbacks and returning an object with info (in my version I cannot use $.defer).
Unfortunaltely, I cannot take advantage of the already existing custom validation for forms provided by Angular (that is $asynchrounous validators and $q.defer) as I have to use an older version of Angular 1.2.26.
Could you provide me some hints/examples on how to achieve it? I'm trying hard but I still find custom directives a bit difficult and I cannot find examples for custom validation (except for those based on Angular 1.3 $asynch validators).
Thank you in advance
You can easily use $http service in your directive's link or controller function and do operations with response data. What is the problem exactly can you be more clear?
You can write your own directive for this: example
app.directive('asyncNameCheck', function($http){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attr, ngModel){
elm.bind('input', function(){
ngModel.$setValidity('asyncValid', false);
$http.get('http://graph.facebook.com/facebook')
.success(function(data){
ngModel.$setValidity('asyncValid', true);
});
})
}
}
})
and in the html
<form name="myform">
<input name="testfield" async-name-check required ng-model="test"/>
<p ng-show="myform.testfield.$error.asyncValid">This field is invalid</p>
</form>
Just note that this directive is not a reusable one but works for your requirements. Do not do this in the controller, I've spent a lot of time with angular validation and if you're controlling validation in the controller, it will cause you alot of issues later on.
I have the following simple base directive:
angular.module("base", [])
.directive("base", function() {
return {
restrict: "A",
scope: true,
controller: function($scope) {
this.setHeader = function(header) {
$scope.header = header;
}
this.setBody = function(body) {
$scope.body = body;
}
this.setFooter = function(footer) {
$scope.footer = footer;
}
},
templateUrl: "base.html"
}
});
I am passing data to this directive in the following way:
.directive("custom", function() {
return {
restrict: "E",
require: "^base",
scope: {
ngModel: "="
},
link: function($scope, $element, $attrs, baseCtrl) {
//Do something with the data or not...
baseCtrl.setHeader($scope.ngModel.header);
baseCtrl.setBody($scope.ngModel.body);
baseCtrl.setFooter($scope.ngModel.footer);
}
}
});
When I create a list of my custom directives, I notice the custom directives aren't rendering immediately. I have made a Plunker demonstrating this behavior. (You will see the list cells empty for a split second, then the directives will appear)
My goal behind this design is to reuse the template of the base directive and only pass in the data needed for display. In this simple example, $scope.data is exactly what I need to pass in but it may be the case some rules or manipulation need to happen first. Rather than have the controller which queried the data handle this, I wanted to pass it off into the directive, separating the concerns.
So my questions are:
Is there any way to make the directives render faster and avoid the flickering shown in the Plunker?
Is this a best practice for reusing directives with Angular?
The flickering is being caused by the async http request to the "base.html" file. Since the HTML for the base directive has to be loaded from the server, there will be a fraction of time where no content will be displayed.
In order to render the data, Angular will go though these 3 stages:
Fetch the HTML file/template from the server (no content will be displayed)
Compile the HTML template (the DOM is updated but the scope isn't yet linked)
Link the scope to the DOM/template (expected data is displayed)
Option 1 - Use the template attribute
Just replace the templateUrl: "base.html" for the direct HTML content:
//templateUrl: "base.html"
template: '<div class="base"><div class="header bottom-border"><h2>{{header}}</h2><div><div class="body bottom-border"><p>{{body}}</p></div><div class="footer">{{footer}}</div></div>',
You will notice that there won't be any flickering this time (check this plunker).
Option 2 - Pre-load template files
Angular has a built-in template cache ($templateCache) that it uses to check if any HTML template file/content has already been fetched from the server or not. If you populate that cache while the app is loading then Angular will not need to fetch the template to render the directive, it will read it directly from the cache.
You can use Angular's $templateRequest (if you are using the latest beta version of Angular) or $templateCache (for any version).
The difference is that $templateRequest automatically makes the HTTP GET request and stores the result in the $templateCache. On the other one you will have to do it manually, like this:
loadTemplate = function(tpl) {
$http.get(tpl, { cache : $templateCache })
.then(function(response) {
$templateCache.put(tpl, html);
return html;
});
};
loadTemplate('base.html');
Note however that this approach needs your app to have a "loading phase".
Regarding the best practices for reusing directives, you seem to be on the right path. The example is to simple to give any advices... Nevertheless, check the "Best practice" notes in this Angular "Creating Custom Directives" guide.
Edit
(my personal preferences regarding template vs templateUrl)
If the main goal is solely performance (i.e. page render speed) then template seems to be the best choice. Fetching one file will always be faster than fetching two... However, as the app grows, the need for a good structure is mandatory, and template files are one of the best practices when it comes to that.
Normally I follow this "rules":
If there are only a few lines of HTML in the template, then just use the template
If the HTML template will not be constantly changing over time (i.e. post structure, contact details, etc...), use template, otherwise (i.e. templates containing banners/ads) use templateUrl
If the app has a loading phase, use templateUrl with cache