directive with ng-if not rendering in Angular.js - angularjs

I'm trying to render a directive whenever I get the search results back. My plan was to keep a scope variable algoliaSearch initially false and whenever I get back search results I change it to true. Then in my view I tried to render the directive based on the value of my variable.
.directive('searchResults', function () {
return {
restrict: 'E',
templateUrl: 'app/search/directives/templates/searchResults.template.html',
controller: ['$scope', 'AlgoliaSearchService', function ($scope, AlgoliaSearchService) {
$scope.hits = [];
$scope.nbHits = null;
$scope.algoliaSearch = false;
AlgoliaSearchService.on('result', results => {
$scope.algoliaSearch = true;
$scope.hits = results.hits;
$scope.nbHits = results.nbHits;
console.log($scope.algoliaSearch)
});
}]
};
})
In my view I want to render the directive when I get the search results back
<search-results ng-if="algoliaSearch"></search-results>
But it doesn't seem to work. It doesn't render anything on searching and even console.log($scope.algoliaSearch) doesn't print anything on console.
However if i use like
<search-results></search-results>
or
<search-results ng-if="true"></search-results>
Then it renders and the console.log works but that is not the way I want to.
My problem is why it doesn't conditionally render the directive based on my scope variable.
I want to make it render conditionally once I get back the search results.
Any help is much appreciated.

Your directive html <search-results ng-if="algoliaSearch"></search-results> is not showing because it's looking for a variable in $scope algoliaSearch before it even renders and executes the directive code. Not only is that variable not in $scope, but it's further not available until that ng-if is true.
To fix this, your best bet is either to change your ng-if to be something in $scope or, better yet, just have the <search-results></search-results> directive, and for the html in ...searchResults.template.html to contain the boolean switch, something like
<div class='search-results' ng-if='algoliaSearch'>
<div ng-repeat='....'>
<!-- etc -->

Related

how to insert an angular 1.5 component with ng-bind-html

I have a component, and i would like to inject it dynamically into my html.
I have a component like this:
angular.module('test1', []);
angular.module('test1').component('test1', {
templateUrl: 'components/test1/test1.template.html',
controller: function test1Controller($scope) {
}
});
the test1.template.html file looks like this:
<p>TEST 1</p>
on my controller i have this:
angular.module('myApp')
.controller('ctrlCtrl', function ($scope, $sce) {
$scope.tag = "<test1/>";
});
on my index.html, i have this:
<ng-bind-html ng-bind-html="tag"></ng-bind-html>
but the tag will not show up. I have tried writing literaly "'<p>hi!</p>'" on the ng-bind-html field, and the text "hi!" shows up on a paragraph, so i don't think this error is because of a typo.
I also tried to use $sce.trustAsHtml, but it didn't work neither :(
$scope.tag = $sce.trustAsHtml("<test1/>");
when i insert an input field, the trustAsHtml method does work, but when i try to inject my components dynamically, it just won't let me, please help D:
Why ng-include won't work?
Components need to be compiled before you can use them on the markup. Try editing the html of the app with the developer tools from your browser, by artificially injecting your component on the markup: it won't work.
How to dynamically include components?
you'll need to use directives, this tutorial (thanks to #Artem K.) is friendly to follow, but you can also read the angular's official documentation, it is a little hard to understand though.
Following the logic of the final example of the angular's official documentation, you can create a directive that compiles everything that is passed to it, like this:
// source: https://docs.angularjs.org/api/ng/service/$compile
angular.module('myApp')
.directive('my-compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and then, on your index.html, you'll have to invoke the directive, sending the the string containing the component's tag as an argument.
<div compile="tag"></div>
As #charlietfl and #Artem K. said, you have to understand the angular's $compile so, thanks guys for pointing me in the right direction :)

Angularjs - Directive Two-way binding Isolated Scope Issue

I'm building a SPA based on AngularJS. In one component of the SPA I have a document upload system, which is built via a custom directive below called docmgmt. Within the component docmgmt I have an another custom directive component called modalCompanyQuery. It is a modal window that searches the company database and returns matching company results. Upon the finding the right company the user clicks on the company name which is then passed back to the parent directive docmgmt called modalOutput.
The issue I have is that despite using two way binding '=' a new scope for modalOutput (output) is created in modalCompanyQuery. How can I pass the modalCompanyQuery search result (modalOutput) back to the parent directive docmgmt? Any help on the simplest way to return the results would be great. Thank you in advance!
Here is my code simplified
modalCompanyQuery Template
<div modal-company-query dialog-show="modalCompanyQuery.isShow" dialog-name ="Select Company" dialog-class="modalSelectCompany" dialog-icon ="fa fa-building" dialog-header="modalSelectCompany-header" company-type = "srchCompanyTypeList" output-select="modalOutput">
</div>
Directive docmgmt
angular.module("docmgmt", [])
.directive("docmgmt",['$http','sessionService','Upload','docService', function($http,sessionService,Upload,docService){
return{
link: function(scope,element,attrs){
scope.docRecord = {};
scope.rightPane = {showAction:true, showInsert:false,showUpdate:false, showRead:false};
scope.progressBar = 0;
scope.submit =[{}];
//modal company search and linking search output results to docmgmt scope
scope.modalCompanyQuery = {isShow:false};
scope.modalOutput={};
scope.test=function(){
console.log(scope.modalOutput);
}
},//return
restrict:"A",
replace:true,
templateUrl:"partials/docmgmt/docmgmt.html",//template
transclude:true,
scope:{
}
}//return
}]);
Directive modalCompanyQuery
angular.module("company", [])
.directive("modalCompanyQuery",['$http','companyService', function($http,companyService){
return{
link: function(scope,element,attrs){ // normal variables rather than actual $scope, that is the scope data is passed into scope
//Read Company
scope.getRecord = function(result){
scope.output={id:result.cs_id, type:result.type,name:result.name, active: result.active};
console.log(scope.output);
scope.isShow = false;
}//getRecord
/*AJAX search functions go here*/
},//return
restrict:"A", //assign as attribute only ie <div my-modal> Content </div>
replace:true,//replaces div with element, note if this is the case must all template must be wrapped within one root element. eg button is within ul otherwise get an error.
templateUrl:"partials/company/tpl/desktop/modal-company-query-desktop.html",//template
transclude:true, //incorporate additional data within
scope:{
isShow:"=dialogShow",//two way binding
name:"#dialogName",//name to be in header
dialogClass:"#dialogClass",// style of the dialog
dialogHeader:"#dialogHeader",//color of the dialogHeader
dialogIcon:"#dialogIcon",//font awesome icon
output:"=outputSelect"
//selectCompany:"=selectCompany",//company to be selected from search and passed back to main window
} //If on this should mean the html input is not binded to custom directive
}//return
}]);
alright, in your docmgmt directive, I see you have made the scope of the directive empty doing:
scope: {}.
I think you should do it like:
scope: {
modalOutput: "="
}
btw doing above expects an attribute in your directive template with name modal-output which must be an object type.
Try it...
After some research I found the solution. The following two links really helped me understand the problem and solution.
Understanding $emit, $broadcast and $on in AngularJS
Communication between nested directives
So I end up using $emit and $on. Outcome as follows:
Directive modalCompanyQuery
scope.getRecord = function(result){
scope.output={id:result.cs_id, type:result.type,name:result.name, active: result.active};
scope.$emit('companyRecord', {record:scope.output});
scope.isShow = false;
}//getRecord
Directive docmgmt
scope.$on('companyRecord', function (event, args) {
scope.modalOutput = args.record;
console.log('Success');
console.log(scope.modalOutput);
});
Hope this helps other people that have come across the same brickwall!

ng-show directive takes too long to update the dom after trigger

The app has a controller, that uses a service to create an instance of video player. The video player triggers events to show progress every few seconds. When the video reaches to a certain point, I want to show a widget on top of the video player.
The view has the widget wrapped in ng-show directive.
It takes more then 60 seconds for the dom element to receive the signal to remove the ng-hide class after the event has been triggered and the values have been populated.
If I try to implement this using the plain dom menthod (like document.getElementById(eleId).innerHTML = newHTML), the update is instant.
What am I doing wrong? Here is the complete sequence in code:
Controller:
MyApp.controller('SectionController', ['$scope', 'PlayerService'], function($scope, PlayerService){
$scope.createPlayer = function() {
PlayerService.createPlayer($scope, wrapperId);
}});
Service:
MyApp.service('PlayerService', [], function(){
this.createPlayer=function(controllerScope, playerWrapper){
PLAYER_SCRIPT.create(playerWrapper) {
wrapper : playerWrapper,
otherParam : value,
onCreate : function(player) {
player.subscribe(PLAY_TIME_CHANGE, function(duration){
showWidget(controllerScope, duration);
})
}
}
}
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
}
}});
View:
<div ng-show="showWidget"> <div class="wdgt">{{widgetData.stuff}}</div> </div>
Solved it! $scope.$apply() did the trick.
My guess is, due to other complex logic ad bindings inside the app, there was a delay in computing the change by angular the default way.
#floribon Thanks for the subtle hint about "complex angular stuff".
The code inside the service function changed to:
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
$rootScope.$apply();
}}
Do you have complex angular stuff within your hidden view?
You should try to use ng-if instead of ng-show, the difference being that when the condition is false, ng-if will remove the element from the DOM instead of just hidding it (which is also what you do in vanilla JS).
When the view is simply hidden using ng-show however, all the watchers and bindings within it keep being computed by Angular. Let us know if ng-if solve your problem, otherwise I'll edit my answer.

Insert directive dynamically and compiling

I have a scenario when a user click a link, I would like to insert a custom element into the DOM for example
//user clicks
$scope.click = function () {
var el = $compile("<my-directive></my-directive>")($scope);
$element.after(el);
};
The my-directive.... directive has an html template.. say for example (template1.html)
<p>My Template for my-directive</p>
{{SomeProperty}}
my-directive is defined like this
module.directive('myDirective', ['$compile', function ($compile) {
return {
restrict: 'E',
replace: true,
templateUrl: '/template1.html',
scope: true
};
}]);
If we assume the scope in myDirective acutally has a value for SomeProperty after running this code I will indeed have the my-directive inserted into the DOM and replaced by the template - template1.html however the {{SomeProperty}} has not been replaced at all! How do I do this??
See Plunkr for more details
You did $compile("<my-directive></my-directive>")($scope.$parent); in your Plunkr. Remove .$parent
Chenge template to:
<p>My Template for my-directive</p>
{{d.SomeProperty}}
As you did d in data.
It works then :)
I'm not sure if you had any other errors, but I found that removing replace:true made it work for me.
I'm not sure exactly what is happening here, but somehow the interaction of you adding the directive to the dom + replacing it immediately causes it not to work.
Another thing I noticed was that by using $element.after(el); you are ending up with an element that is OUTSIDE the controller's scope. It's not in the div that the controller has scope for. Unfortunately, I only saw this in MY plunkr, so don't know if this affected you also.
Plunkr here

AngularJS setting model value from directive and calling a parent scope function holds on to the previous value inside that function

js fiddle http://jsfiddle.net/suras/JzaV9/4/
This is my directive
'use strict';
barterApp.directive('autosuggest', function($timeout, $http) {
return {
restrict: "E",
scope: {
modelupdate:"=",
suggestions:"=",
urlsend:"#"
},
template: '<ul><li ng-repeat="suggest in suggestions" ng-click="updateModel(suggest)">{{suggest}}</li></ul>',
link: function (scope, element) {
scope.$watch('modelupdate', function() {
$timeout(function(){
$http.post(scope.urlsend, {q:scope.modelupdate}).then(function(data){
scope.suggestions = data.data;
console.log(data.data);
});
}, 3000);
});
scope.updateModel = function(value){
scope.modelupdate = value;
scope.$parent.getBookInfo();
}
}
};
});
controller is
barterApp.controller('openLibraryCtrl', ['$scope','$http',function ($scope,$http) {
$scope.title = "";
$scope.getBookInfo = function(value){
if($scope.title == "" || $scope.title == " ") //here title is 'r'(previous value)
{
return;
}
$http.get('/book_info.json?q='+$scope.title).then(function(res){
if(Object.keys(res).length !== 0)
{
data = res.data
console.log(data);
}
});
}
//here title is 'rails' (updated value from directive).
//( used a watch function here on model update
// and checked it but inside getBookInfo it is still 'r' )
}]);
in the update model function i set the model value and call the getBookInfo function on parent scope. but the thing here is when (this is a autocomplete) i enter the value in a input field that contains ng-model say for example 'r' then triggers the watch and i get suggestions from a post url (lets say "rails", "rock") and show it through the template as in the directive. when i click one of the suggestions (say 'rails') it triggers the updatemodel function in directive and sets the model value. its fine upto this but when i call the getBookInfo function in parent scope then $scope.title is 'r' inside the function (i checked with console log outside the function the model value was updated correctly as 'rails' ). again when i click 'rock' the model value inside getBookInfo is 'rails'.
i have no clue whats going on. (i also tested with watch function in controller the model gets updated correctly but the function call to getBookInfo holds back to the previous value)
view
<form ng-controller="openLibraryController">
<input type="text" ng-model="title" id="title" name="book[title]" />
<autosuggest modelupdate = "title" suggestions = "book_suggestions" urlsend="/book_suggestions.json"> </autosuggest>
</form>
I didn't look deep into it, but I suspect (with a high degree of confidence) that the parent scope has not been updated at the time of calling getBookInfo() (since we are still in the middle of a $digest cycle).
Not-so-good Solution 1:
You could immediately update the parent scope as well (e.g. scope.$parent.title = ...), but this is clearly a bad idea (for the same reasons as nr 2, but even more so).
Not-so-good Solution 2:
You could pass the new title as a parameter to getBookInfo().
Both solutions result in mixing controller code with directive code and creating a tight coupling between your components, which as a result become less reusable and less testable.
Not-so-bad Solution:
You could watch over the title and call getBookInfo() whenever it changes:
$scope.$watch('title', function (newValue, oldValue) {
getBookInfo();
});
This would be fine, except for the fact that it is totally unnecessary.
Better Solution:
Angular is supposed to take care of all that keep-in-sync stuff for us and it actually does. You don't have given much context on what is the purpose of calling getBookInfo(), but I am guessing you intend to update the view with some info on the selected book.
In that case you could just bind it to an element (using ng-bind) and Angular will make sure it is executed properly and timely.
E.g.:
<div>Book info: <span ng-bind="getBookInfo()"></span></div>
Further more, the autosuggest directive doesn't have to know anything about it. It should only care about displaying suggestions, manipulating the DOM (if necessary) and updating the specified model property (e.g. title) whenever a suggestion is clicked. What you do with the updated value should be none of its business.
(BTW, ideally the suggestions should be provided by a service.)
Below is a modified example (based on your code) that solves the problem. As stated above there are several methods of solving the problem, I just feel this one tobe cleaner and more aligned to the "Angular way":
Book title: <input type="text" ng-model="book.title" />
<autosuggest modelupdate="book.title"
suggestions="book.suggest()"></autosuggest>
Book info: <span ng-bind="book.getInfo()"></span>
Just by looking at the HTML (without knowing what is in JS), one can easily tell what is going on:
There is a text-field bound to book.title.
There is a custom autosuggest thingy that offers suggestions provided by book.suggest() and updates book.title.
There is a span that displays info about the book.
The corresponding directive looks like this:
app.directive('autosuggest', function() {
return {
restrict: 'E',
scope: {
modelupdate: '=',
suggestions: '&'
},
template:
'<ul><li ng-repeat="suggest in suggestions()" ' +
'ng-click="modelupdated = suggest">' +
'{{suggest}}</li></ul>'
};
});
As you can see, all the directive knows about is how to retrieve suggestions and what to update.
Note that the same directive can be used with any type of "suggestables" (even ones that don't have getBookInfo()); just pass in the right attributes (modelupdated, suggestions).
Note also, that we could remove the autosuggest element and the app would continue to work as expected (no suggestions of cource) without any further modification in HTML or JS (while in your version the book info would have stopped updating).
You can find the full version of this short demo here.

Resources