How to evaluate stored expression in AngularJS $scope - angularjs

Please help me in this issue. Following is the the code.
HTML:
<div ng-controller="ctrl">click here</div>
JS:
app.controller('ctrl', function($scope) {
$scope.true_link = "http://google.com";
$scope.link = "{{ true_link }}";
});
Result:
<div ng-controller="ctrl">click here</div>
Expectation:
<div ng-controller="ctrl">click here</div>
Replace {{ link }} by {{ true_link }} in HTML will solve this problem. But I have to use this way. How can I evaluate expression in $scope.link content again? Please help me. Thanks.
Update
Look like facebook, I have two wall pages: User page and Actor page. They have same template structure and process (append, remove element etc...) after bussiness function such as changeAvatar(), changeCover(), post() etc... So I create 'homepage' based directive:
JS
app.directive('homepage', function() {
return {
restrict: 'A',
templateUrl: 'homepage.html',
controller: 'homepageCtrl'
};
});
app.controller('homepageCtrl', function($scope) {
$scope.changeAvatar() = ...;
$scope.post() = ...;
});
and two extend controllers:
app.controller('userCtrl', function($scope, $http) {
$http.({...}).success((data){ $scope.username = data.username })
$scope.menu = [
{
title: "foo-user"
link: "/u/{{ username }}/foo-user"
}
{
title: "bar-user"
link: "/u/{{ username }}/bar-user"
}
]
});
app.controller('actorCtrl', function($scope) {
$http.({...}).success((data){ $scope.actorname = data.actorname })
$scope.menu = [
{
title: "foo-actor"
link: "/u/{{ actorname }}/foo-actor"
}
{
title: "bar-actor"
link: "/u/{{ actorname }}/bar-actor"
}
]
});
HTML
homepage.html
<section>
<header>
<ul class="menu">
<li ng-repeat="_menu in menu">
<a href="{{ _menu.link }}">
{{ _menu.title }}
</a>
</li>
</ul>
</header>
<main>
content...
</main>
</section>
User page:
<div homepage ng-controller="userCtrl"></div>
Actor page:
<div homepage ng-controller="actorCtrl"></div>
Two pages menu has same HTML structure & effect, but differ in items. I wanna define menu item in extended controller (userCtrl, actorCtrl) and print them by ng-repeat. The problem is evaluate $scope.menu.link content.
Solution
I found solution: using $scope.$eval (https://docs.angularjs.org/guide/expression).
In userCtrl, the $scope.menu[i].link is dynamic content because included username - received from ajax call. I can update $scope.menu[i].link in $http.success() by using foreach. But I think using $scope.$eval help me auto update wherever I want easier.
So, the code is:
JS
app.controller('userCtrl', function($scope, $http) {
$http.({...}).success((data){ $scope.username = data.username })
$scope.menu = [
{
title: "foo-user"
link: "'/u/' + username + '/foo-user'"
show: 'true'
}
{
title: "bar-user"
link: "'/u/' + {{ username }} + '/bar-user'"
show: 'username == "lorem"'
}
]
});
HTML
homepage.html
<section>
<header>
<ul class="menu">
<li
ng-repeat="_menu in menu"
>
<a
ng-href="{{$parent.$eval(_menu.link)}}"
ng-show="$parent.$eval(_menu.show)"
>
{{_menu.title}}
</a>
</li>
</ul>
</header>
<main>
content...
</main>
</section>

Create a directive, and use $eval to parse the expression:
app.directive('a', function (){
return {
restrict : 'E',
link: function(scope, element, attr){
element.attr('href',scope.$eval(attr.href));
}
}
});

app.controller('ctrl', function($scope) {
$scope.true_link = "http://google.com";
$scope.link = $scope.true_link; // you need a copy of `$scope.true_link` here
});
<div ng-controller="ctrl"><a ng-href="link">click here</a></div>

$scope.link = "{{ true_link }}"; this is just a string, nothing else, so it will render as such.
Don't be confused by the curly brackets.
You can complicate things by using $compile, eval, etc... or you can simply assign the true_link value to the link variable.
UPDATE
You have a problem in your ng-repeat directive. This:
<li ng-repeat="_menu in menu">
{{ menu.title }}
</li>
Should be this (note _menu):
<li ng-repeat="_menu in menu">
{{ _menu.title }}
</li>
Also, there's no need to use templating in your controller, so this:
$scope.menu = [
{
title: "foo-user"
link: "/u/{{ username }}/foo-user"
}
{
title: "bar-user"
link: "/u/{{ username }}/bar-user"
}
]
can be this:
$scope.menu = [
{
title: "foo-user"
link: "/u/" + $scope.username + "/foo-user"
}
{
title: "bar-user"
link: "/u/" + $scope.username + "/bar-user"
}
]

Why do you want to use {{link}} instead of {{true_link}}? What exactly the issue you've run into that "you have to use this way"? If you can explain this in details, may we can figure out a better solution.

Related

Setting $location and then filtering with AngularJS

For the purposes of navigation, I'm trying to have a button that triggers a function.
This function must first:
-Redirect to #/
And then:
-Filter an ng-repeat that exists in this view.
This is what I've tried but no luck.
<li><a ng-click="shopsingle()">Shop</a></li>
And JS:
$scope.shopsingle = function(){
$location.path("/");
setTimeout(function () {
$scope.$apply(function(){
$scope.filterExpr = {"shop" : true};
console.log($scope.filterExpr);
});
}, 300);
};
It does redirect to the desired path, where a full ng-repeat list appears, but it won't apply the filter, and console also comes empty.
About the views and routing, I'm using the same template for 2 different views, and showing/hidding parts of if while watching the $routeParams:
.config(function($routeProvider) {
$routeProvider
.when("/", {
templateUrl : "home.htm",
controller : "mainCtrl"
})
.when('/:name',
{
templateUrl:"home.htm",
controller:"mainCtrl"
})
.otherwise({redirectTo:'/'});
})
And the controller watch:
$scope.name = $routeParams.name;
$scope.$watch(function() { return $scope.name; }, function(newVal) {
if (!newVal){
$scope.single = false;
} else{
$scope.single = true
};
$scope.newname = newVal;
$scope.chosenexhibitor = $filter("filter")($scope.images, {'name':$scope.newname}, true);
}, true);
And the view:
<!-- MAIN PAGE -->
<div ng-hide="single" id="tiles" masonry='{ "transitionDuration" : "0.15s" , "itemSelector" : ".tile"}'>
<div masonry-tile ng-repeat="item in images | filter:filterExpr" class="tile col-xs-3 col-md-3">
<a href="#/{{item.name}}">
<img class="img-fluid" ng-src="img/{{item.link}}">
</a>
</div>
</div>
<!-- SINGLE PAGE -->
<div class="row flexing" ng-show="single">
<div class="col-md-12">
<h1>{{chosenexhibitor[0].name}}</h1>
<img ng-src="img/{{chosenexhibitor[0].link}}">
<a class="back" href="#/">Back</a>
</div>
</div>
What am I missing?

how to access controller dataset in custom directive

I was playing with angular js scope of custom directive and tried to use controller dataset in custom directive with different attribute name but did not succeed. Kindly see my code and suggest me what is the proper way to access the data and set in ng-repeat in custom directive.
Do I need to set ng-repeat on custom directive or inside the directive template ? I mean
like this
<movie-list ng-repeat="entry in movieData" > </movie-list>
or inside directive template
<movie-list movieArray = "movieData" ></movie-list>
and then
<div>
<ul >
<li ng-repeat="entry in movieArray"> {{...}} </li>
</ul>
</div>
here is my data
app.js
angular
.module('app')
.controller('homeController', function($scope) {
$scope.movieData = [{
name : 'PK',
star : 'Aamir Khan',
releaseYear : '2015'
},
{
name : 'PiKu',
star : 'Irrfan Khan',
releaseYear : '2015'
}
];
});
// custom directive
angular
.module('app')
.directive('movieList', function(){
// Runs during compile
return {
scope: { movieArray : '=movieArray' },
controller: function($scope, $element, $attrs, $transclude) {
},
require: '?ngModel',
restrict: 'E',
templateUrl: 'movie.html',
replace: true,
link: function($scope, element, attr, controller) {
// console.log($scope.$parent);
}
};
});
index.html
<div ng-controller="homeController" >
<div class="col-lg-6">
<movie-list movieArray="movieData"></movie-list>
</div>
movie.html
<div>
<ul class="list-group" >
<li ng-repeat="entry in movieArray" class="list-group-item" >
<h3 class="list-group-item-heading">{{ entry.name }}</h3>
<p class="list-group-item-text">
{{ entry.star }} - Release in {{ entry.releaseYear }}
</p>
</li>
</ul>
</div>
scope: { movieArray : '=movieArray' } >> not working
scope: { movieArray : '=movieData' } >> not working
even I changed the attribute
<movie-list movieArray="movieArray"></movie-list>
but not working
Update attribute name:
<movie-list movieArray="movieData"></movie-list>
To:
<movie-list movie-array="movieData"></movie-list>
Note: Directive or attribute name should be lower case.
If you want to skip scope (delete scope: { movieArray : '=movieArray' }, from your directive ) in your directive you can do that by below code in Index.html:
<div ng-controller="homeController" >
<div class="col-lg-6">
< div movie-list></div>
</div>
And in your controller you need to replace movieData by movieArray:
.controller('homeController', function($scope) {
$scope.movieArray= [{
name : 'PK',
star : 'Aamir Khan',
releaseYear : '2015'
},
{
name : 'PiKu',
star : 'Irrfan Khan',
releaseYear : '2015'
}
];

How to use HTML inside conditional bind in markup

I have markup like this
<p>{{ !job.AdditionalNotes ? "No additional notes." : job.AdditionalNotes }}</p>
Would like to emphasis No Additional notes using something like.
<p>{{ !job.AdditionalNotes ? <em>"No additional notes."</em> : job.AdditionalNotes }}</p>
Is there a way to do this without using ng-if and ng-show to do this retaining the ternary operator?
1st Option
I get this working in the following way (without ng-show or ng-if). I'm using ng-bind-html and $sce service to render the HTML. Since your "no additional notes" message is generic and common, we can easily define in the controller and get it from a method after sanitization.
var app = angular.module("sa", []);
app.controller("FooController", function($scope, $sce) {
$scope.jobs = [{
name: "Sr. Software Developer"
}, {
name: "Software Associates",
AdditionalNotes: "Remote location"
}, {
name: "Front-end developer"
}];
$scope.trust = function(text) {
return $sce.trustAsHtml(text);
};
$scope.noNotesMessage = function() {
return $scope.trust("<em>No additional notes.</em>")
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="sa" ng-controller="FooController">
<ol>
<li ng-repeat="job in jobs">
<strong>{{job.name}}</strong>
<p ng-bind-html="!job.AdditionalNotes ? noNotesMessage() : trust(job.AdditionalNotes)"></p>
</li>
</ol>
</div>
2nd Option
Alternatively, you can write a directive:
var app = angular.module("sa", []);
app.controller("FooController", function($scope, $sce) {
$scope.jobs = [{
name: "Sr. Software Developer"
}, {
name: "Software Associates",
AdditionalNotes: "Remote location"
}, {
name: "Front-end developer"
}];
});
app.directive('notes', function() {
return {
restrict: 'E',
scope: {
additional: '='
},
link: function($scope, element, attr) {
var html = "<p>" + ($scope.additional || "<em>No additional notes.</em>") + "</p>";
element.html(html);
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="sa" ng-controller="FooController">
<ol>
<li ng-repeat="job in jobs">
<strong>{{job.name}}</strong>
<notes additional="job.AdditionalNotes"></notes>
</li>
</ol>
</div>

Angular JS directive, calling another controller

I'm trying to implement a simple <pagination> directive in Angular JS, and having trouble getting it to communicate with another controller to get it to react on changes to the current page.
Here's what my code looks like:
paginationDirective.js
app.controller('paginationCtrl', function ($scope) {
this.init = function (attrs) {
$scope.items = [
{'page': '1', class: 'current'},
{'page': '2', class: ''},
{'page': '3', class: ''},
{'page': '4', class: ''},
{'page': '5', class: ''}
];
};
});
app.directive('pagination', function () {
return {
controller: 'paginationCtrl',
restrict: 'E',
scope: {
count: '#',
page: '#'
},
templateUrl: 'views/partials/pagination.html',
link: function (scope, element, attrs, paginationCtrl) {
paginationCtrl.init(attrs);
}
}
});
pagination.html
<div class="pagination-centered">
<ul class="pagination">
<li class="arrow unavailable">«</li>
<li ng-repeat="item in items" class="{{ item.class }}">
{{ item.page }}
</li>
<li class="arrow">»</li>
</ul>
</div>
somepage.html
<div class="content">
<table>...data for page x...</table>
<pagination onpage=" <!-- Run somepageCtrl.page(x) --> "></pagination>
</div>
somepageCtrl.js
app.controller('somepageCtrl', function($scope, myService){
$scope.currentPage = 1;
$scope.init = function () {
myService.loadData($scope);
};
$scope.navigate = function(page) {
console.log('Navigating to page ' + page);
}
$scope.init();
});
I'm still new to AngularJS, so I'm not entirely sure what I should do to get the pagination directive to tell somepageCtrl to navigate to the next page.
I would like to be able to pass a function that needs to be executed when the user clicks an item in the pagination directive as an attribute on the directive.
Can anyone help and nudge me in the right direction?
I'm going to answer my own question, thanks to Esteban Felix for pointing me into the right direction.
I defined the navigate() method on somepageCtrl:
$scope.navigate = function (page) {
console.log('Lets go to page ' + page);
};
then simply added the function to
<pagination on-change="navigate"></pagination>
The method then gets assigned in the app.directive definition like so:
scope: {
count: '#',
page: '#',
onChange: '&onChange'
},
On the app.controller for paginationCtrl, I created a callback function:
$scope.callback = function (page) {
console.log('Turn to page ' + page);
$scope.onChange()(page);
}
and updated the markup for pagination.html to call the above callback with ng-click like so:
{{ item.page }}
You can use the experession callback & to call your controller back from a directive.
pagination directive
app.directive('pagination', function () {
return {
controller: 'paginationCtrl',
restrict: 'E',
scope: {
count: '#',
page: '#',
onPageChange: '&'
},
templateUrl: 'views/partials/pagination.html',
link: function (scope, element, attrs, paginationCtrl) {
paginationCtrl.init(attrs);
// grab function pointer so it will be called correctly with ng-click
scope.onPageChange = scope.onPageChange();
}
}
});
somepage.html
<div class="content">
<table>...data for page x...</table>
<pagination on-page-change="sompageCtrl.navigate"></pagination>
</div>
pagination.html
<div class="pagination-centered">
<ul class="pagination">
<li class="arrow unavailable">«</li>
<li ng-repeat="item in items" class="{{ item.class }}">
{{ item.page }}
</li>
<li class="arrow">»</li>
</ul>
</div>

Adding directive dynamically using ng-click

I am working on a modal for a client. I have created a directive that works great, the problem is that one modal is made ahead each time it is used like..
What I have
<div ng-repeat="item in items">
<a data-toggle="modal" data-target="{{item.id}}">Click</a>
<my-dialog element-id="item.id">
<h1>This is the body of the modal</h1>
</my-dialog>
</div>
This works great for a small amount of modals but we are using a very large number of modals. So I would like to add the directive at runtime, something closer to...
What I want...
<div id="warning"></div>
<div ng-repeat="item in items">
<a data-toggle="modal" data-target="{{item.id}}" ng-click="showModal(item)">Click</a>
</div>
...
// inside controller
$scope.showModal = function(item){
$http.get('/someUrl').success(function(data){
var result = $compile('<my-dialog element-id="'+item.id+'">'+data+'</my-dialog>').($scope);
$("#warning").append(result);
});
}
$scope.hideModal = function(){
$( "#warning" ).empty();
}
This of course isn't working yet. Also it doesn't feel like the best way. This would allow me to remove the directive once it has been closed.
Please include a plunker or equivalent for the check.
One way you could do this is to use ng-repeat with your items, then call $scope.$apply() after you push a new item to the list. The HTML could look like this ...
<div ng-repeat="item in items">
<span dialog>
<a class="dialog-anchor">{{item.name}}</a>
<div class="dialog-body">{{item.id}}</div>
</span>
</div>
... and the directive like this
.directive('dialog', [function () {
return {
scope: {
id: '#elementId',
}
, link: function (scope, el, attrs) {
var body = $(el).find('.dialog-body').detach();
$(el).find('.dialog-anchor').on('click', function () {
$('body').append(body);
});
}};
}])
... and the controller like this
.controller('app', ['$scope', function ($scope) {
$scope.items = [
{name: 'first', id: 001},
{name: 'second', id: 002}
];
setTimeout(function () {
$scope.items.push({name: 'three', id: 003});
if (!$scope.$$phase) $scope.$apply();
}, 2000);
}])
Here's the plunker... http://plnkr.co/edit/2ETbeCKGcHW3CJCfD9d7?p=preview. You can see the $scope.$apply call in the setTimeout where I push a new item to the array.
Try this:
var result = $compile('<my-dialog element-id="'+item.id+'">'+data+'</my-dialog>')($scope);

Resources