How do I leave static content if property is empty in angular? - javascript

I have a simple angular controller in my app. And I wish to enhance a page containing static content, with angular. This will allow me to have a good, content rich page as fall back for those without JS (who?!) and web crawlers and readers.
So far I have identified a few ways to do this, possibly using custom directives, or by doubling up content in 'ng-bind-template' directive (which is obviously not ideal).
I am fairly new to the world of angular so would like to try and garner the best practice for this scenario.
What is the best approach for this?
Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Markup:
(The problem here is that 'Static content' is replaced by '' if angular initializes)
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
EDIT
I should be clear that even though angular is bootstrapping, The aim is to leave the default content intact, and not replace it with dynamic content. That only wants to be achieved once the activate button is clicked. I have tried the following but it involves doubling up the content which could get bulky if it is a whole article.
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle || "Static Content"}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent || "Some more content"}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div> <a href ng-click="activate()" ng-cloak>click to activate</a>
</div>

You can create a simple directive to create the html structure that you want.
I propose this to be your new html:
<div ng-cloak sk-custom-bind="eTitle">Static content</div>
Notice how you don't have to specify the or clause with the static content.
Now you can create a directive that will build the html template for you:
app.directive('skCustomBind',function() {
return {
restrict: 'A',
template: function(elem, attrs) {
var templ = "<div ";
templ += "ng-bind-template='{{";
templ += attrs.skCustomBind;
templ += ' || "';
templ += elem.text();
templ += '"';
templ += "}}'></div>";
return templ;
},
replace: true
};
});
And as simple as that you have the functionality that you are looking for without the duplication.
See this plunker to see a working sample.

The thing here is that when you define the controller, angular will parse through the html to find all the variables and link them with the corresponding controller items using $scope.
So, when angular initializes, since eTitle and eContent are not having any values (since you initialize them in the function only), which is called on click of the last item(where on your application works fine).
So until you click the button, the static text is replaced by $scope.eTitle and $scope.eContent both of which are undefined. Hence no text inside the tags.
So the solution,
1. Either you initialize the variables inside the controller with the static text, like you did for 'This is angular injecting this'(reason only this came!):
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this";
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Or you can initialize the variables in the html using ng-init, it will initialize the variable in html only:
<div ng-controller="TestArticle">
<div ng-init="eTitle = 'Static Content'" ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-init="eContent = 'Some more content'" ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
The thing is when you start using angular, the html will get kind of hijacked and angular will start using its own variables.

Thanks to #JoseM for putting me on to right track and helping me find the following solution...
SOLUTION
My Markup:
NOTE: the custom directive and properties ('cg-dyn' and 'cg-content'). These will not initialise any values when angular bootstraps. And further down you will see how these map from controller scope via an isolated scope.
<div ng-controller="TestArticle">
<div ng-cloak cg-dyn cg-content="eTitle">Static content</div>
<div ng-cloak cg-dyn cg-content="eContent">Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="upDateTitle()" ng-cloak>Update title</a> | <a href ng-click="upDateContent()" ng-cloak>Update content</a>
</div>
My Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.upDateTitle = function(){
$scope.eTitle = "Dynamic title";
};
$scope.upDateContent = function(){
$scope.eContent = "Dynamic Content Lorem ipsum";
};
});
And finally the magic in My Directive
app.directive('cgDyn',function() {
return {
restrict: 'A',
scope:{
'content':'=cgContent'
// this is a two way binding to the property defined in 'cg-content
},
link: function(scope, elem, attrs) {
// below is a watcher keyed on a combination content
scope.$watch('content',function(){
if(scope.content){
elem.html(scope.content);
// and here is the bit, normally plumbed by default, but instead we only replace the html if the value has been set
}
});
},
controller: 'TestArticle',
replace: false
};
});
Thanks for everyone's help. I am sure there is an even better solution out there so keep the suggestions coming!

Related

How to get passed attributes from custom directive and collect them in an array to show into template

I have created a custom directive which should display a slider with the data entered to the custom directive. I need to be able to display the image Url and redirect Link via directive attributes for example:
<div class="sliderBanner" imgUrl="http://example.com/img1.jpg" imgLink="example.com"></div>
<div class="sliderBanner" imgUrl="http://example.com/img2.jpg" imgLink="example.com"></div>
<div class="sliderBanner" imgUrl="http://example.com/img3.jpg" imgLink="example.com"></div>
Now I want to collect those data and place them inside an array within directive scope and use ng-repeat inside directive template to show them.
e
PS: I'm using Swiper angular directive for slider purpose.
var app = angular.module('APP',['ksSwiper']);
app.directive('sliderBanner',function($http){
return{
scope:true,
restrict:'C',
link: function(scope,element,attr){
scope.data = [];
scope.data.push({
"id": attr.id,
"imgUrl": attr.imgUrl,
"imgRef": attr.imgRef
});
},
templateUrl: 'http://www.lajmislam.com/wp-content/themes/Newspaper/ng-templates/sliderBanner.html'
}
});
This is my directive template:
<ks-swiper-container autoplay="3000" show-nav-buttons="true" pagination-is-active="true" swiper="swiper">
<ks-swiper-slide ng-repeat="item in data">
{{item.id}},{{item.imgUrl}}
</ks-swiper-slide>
</ks-swiper-container>
<div ng-repeat="item in data">
{{item.id}},{{item.imgUrl}}
</div>
What errors are you seeing?
What you have here appears to be working. I tested it out here: https://github.com/styonsk/StackOverflowSolutions/tree/master/35997232

Render html tags from AngularJS object

I've looked up similar questions on how to render html tags within angularJS but none of them worked for me because I am using ng-repeat. Anyone have any advice.
<div ng-repeat="o in SurveyResults.SurveyQuestions">
{{o.SurveyQuestionId}}
{{o.QuestionText}}
</div>
My o.QuestionText has data such as <b>How well do we deliver on our promises?</b> <br />Consider our responsiveness to your requests, ability to meet deadlines and <i>accountability for the outcomes</i>.
It displays the tags without rendering the tags.
I did try putting this in my JS file
homePage.directive('htmlSafe', function($parse, $sce) {
return {
link: function(scope, elem, attr) {
var html = attr.ngBindHtml;
if(angular.isDefined(html)) {
var getter = $parse(html);
var setter = getter.assign;
setter(scope, $sce.trustAsHtml(getter(scope)));
}
}
};
});
Then setting html bind in the repeat as such
<div ng-repeat-html="o in SurveyResults.SurveyQuestions">
{{o.SurveyQuestionId}}
{{o.QuestionText}}
</div>
Any other advice?
Just use ng-bind-html, you don't need new directive
<div ng-repeat="o in SurveyResults.SurveyQuestions">
{{o.SurveyQuestionId}}
<span ng-bind-html="o.QuestionText"></span>
</div>

binding to controller object in Angular

I'm new to angular, trying to bind an an element's content into the controller's Scope to be able to use it within another function:
here is the scenario am working around:
I want the content of the <span> element {{y.facetName}} in
<span ng-model="columnFacetname">{{y.facetName}}</span>
to be sent to the controller an be put in the object $scope.columnFacetname in the controller
Here is a snippet of what I'm working on:
<div ng-repeat="y in x.facetArr|limitTo: limit track by $index ">
<div class="list_items panel-body ">
<button class="ButtonforAccordion" ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)">
<span>{{$index+1}}</span>
<span ng-model="columnFacetname">{{y.facetName}}</span>
<span>{{y.facetValue}}</span>
</button>
</div>
</div>
angular.module('mainModule').controller('MainCtrl', function($scope, $http) {
$scope.columnFacetname = "";
$scope.ListClicktnColumnFilter = "";
$scope.ListClicktnColumnFilterfunc = function() {
$scope.ListClicktnColumnFilter = "\":\'" + $scope.columnFacetname + "\'";
};
}
the problem is that the $scope.ListClicktnColumnFilter doesn't show the $scope.columnFacetname within it, meaning that the $scope.columnFacetname is not well-binded.
In your ng-click instead of calling two different function
ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)"
you can declare like this
ng-click="columnFacetname = y.facetName; onServerSideButtonItemsRequested(columnFacetname , myOrderBy)"
You are trying to pass that model to another function by assigning it to ListClicktnColumnFilter in your controller
By doing in this way, you can achieve the same thing.
I have done one plunker with sample array,
http://embed.plnkr.co/YIwRLWXEOeK8NmYmT6VK/preview
Hope this helps!

How to access functions of a controller from within another controller via scope?

I have the following problem, I want to call a function of another controller from within a controller I want to use for a guided tour (I'm using ngJoyRide for the tour). The function I want to call in the other controller is so to say a translator (LanguageController), which fetches a string from a database according to the key given as parameter. The LanguageController will, if the key is not found, return an error that the string could not be fetched from the database. In my index.html fetching the string works, but I want to use it in the overlay element of my guided tour, which does not work, but only shows the "not fetched yet"-error of the LanguageController.
My index.html looks like this:
<body>
<div class="container-fluid col-md-10 col-md-offset-1" ng-controller="LangCtrl as lc" >
<div ng-controller="UserCtrl as uc" mail='#email' firstname='#firstname'>
<div ng-controller="GuidedTourCtrl as gtc">
<div ng-joy-ride="startJoyRide" config="config" on-finish="onFinish()" on-skip="onFinish()">
...
{{lc.getTerm('system_lang_edit')}}
...
</div>
</div>
</div>
</div>
</body>
The controller I'm using for the guided Tour looks like this:
guidedTourModule.controller('GuidedTourCtrl',['$scope', function($scope) {
$scope.startJoyRide = false;
this.start = function () {
$scope.startJoyRide = true;
}
$scope.config = [
{
type: "title",
...
},
{
type: "element",
selector: "#groups",
heading: "heading",
text: " <div id='title-text' class='col-md-12'>\
<span class='main-text'>"\
+ $scope.lc.getTerm('system_navi_messages') + "\
text text text text\
</span>\
<br/>\
<br/>\
</div>",
placement: "right",
scroll: true,
attachToBody: true
}
];
...
}]);
And the output I ultimately get looks like this for the overlay element:
<div class="row">
<div id="pop-over-text" class="col-md-12">
<div id='title-text' class='col-md-12'>
<span class='main-text'>
not fetched yet: system_navi_messages
text text text text
</span>
<br/>
<br/>
</div>
</div>
</div>
...
I hope someone can see the error in my code. Thanks in advance!
Things needs clarity are,
How you defined the 'getTerm' function in your Language controller, either by using this.getTerm() or $scope.getTerm(). Since you are using alias name you will be having this.getTerm in Language controller.
Reason why you are able to access the getTerm function in your overlay element is, since this overlay element is inside the parent controller(Language Controller) and you are referencing it with alias name 'lc' while calling the getTerm function. Thats' why it is accessible.
But the string you pass as a parameter is not reachable to the parent controller. that's why the error message is rendered in the overlay HTML.
Please make a plunker of your app, so that will be helpful to answer your problem.

$digest rendering ng-repeat as a comment

I'm writing a test for a directive, when executing the test the template (which is loaded correctly) is rendered just as <!-- ng-repeat="foo in bar" -->
For starters the relevant parts of the code:
Test
...
beforeEach(inject(function ($compile, $rootScope, $templateCache) {
var scope = $rootScope;
scope.prop = [ 'element0', 'element1', 'element2' ];
// Template loading, in the real code this is done with html2js, in this example
// I'm gonna load just a string (already checked the problem persists)
var template = '<strong ng-repeat="foo in bar"> <p> {{ foo }} </p> </strong>';
$templateCache.put('/path/to/template', [200, template, {}]);
el = angular.element('<directive-name bar="prop"> </directive-name>');
$compile(el)(scope);
scope.$digest(); // <--- here is the problem
isolateScope = el.isolateScope();
// Here I obtain just the ng-repeat text as a comment
console.log(el); // <--- ng-repeat="foo in bar" -->
}));
...
Directive
The directive is fairly simple and it's not the problem (outside the test everything works just fine):
app.directive('directiveName', function () {
return {
restrict : 'E',
replace : true,
scope : {
bar : '='
},
templateUrl : '/path/to/template', // Not used in this question, but still...
});
A few more details:
The directive, outside the test, works fine
If I change the template to something far more simple like: <h3> {{ bar[0] }} </h3> the test works just fine
The rootScope is loaded correctly
The isolateScope results as undefined
If you have a look at the generated output that angular creates for ng-repeat you will find comments in your html. For example:
<!-- ngRepeat: foo in bars -->
These comments are created by the compile function - see the angular sources:
document.createComment(' ' + directiveName + ': '+templateAttrs[directiveName]+' ')
What you get if you call console.log(el); is that created comment. You may check this if you change the output in this way: console.log(el[0].parentNode). You will see that there are a lot of childNodes:
If you use the directive outside of a test you will not be aware of this problem, because your element directive-name will be replaced by the complete created DocumentFragment. Another way to solve the problem is using a wrapping element for your directive template:
<div><strong ng-repeat="foo in bar"> <p> {{ foo }} </p> </strong></div>
In this case you have access to the div element.

Categories

Resources