appending html from another file to div using directive - angularjs

I am quite new to angularjs, I am trying to append html from another html file using my directive. I only want to do this only if I get a success callback from the server from my http request.
Currently I have my directive, I am not sure if this is the way to do it. I have also attached my directive to the div I want to append to.
host.directive('newHost', function(){
return {
restrict: 'E',
link: function(scope, element, attr){
scope.newBox = function(){
templateUrl: 'newHostTemplate.html';
};
}
}
});
I then call $scope.newBox() on my success callback which at that point I want to append to the div.
I have followed the answer below, and tried to adapt it to my scenario, however I am getting the error $scope.newBox is not a function, here is my current implementation.
host.directive('newHost', function(){
return {
restrict: 'E',
template: '<div ng-include="getTemplateUrl()"></div>',
link: function(scope, element, attr){
scope.newBox = function(){
console.log('newBox');
scope.getTemplateUrl = function(){
return 'newHostTemplate.html'
};
};
}
}
});
//controller, this does all the routing on client side
host.controller('hostsController', function($scope, $http, $window, $rootScope){
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
console.log("failed to change routes");
});
$scope.newHost = {};
$scope.addNewHost = function() {
$http({
method : 'POST',
url : 'http://192.168.0.99:5000/newHost',
data : JSON.stringify($scope.newHost), // pass in data as strings
})
.success(function(data) {
console.log(data);
$scope.newBox()
//on success we want to close the modal and reset the data
$scope.newHost = {}
$scope.dismiss()
});
};
});

Here are two examples:
Using ng-include and a function that points to the external file
I created a plunker showing you a working example. As soon as you click the 'process data' link, it will call NewBox() that will append the content from an external file. This link simulates your callback.
In the directive, the template is defined as template:
<div ng-include="getTemplateUrl()"></div>
And in the link function, I setup getTemplateUrl() once newBox() is called... the getTemplateUrl() function returns the name of the external file (e.g. template.html):
link: function(scope, element, attr) {
scope.newBox = function() {
console.log('new Box');
scope.getTemplateUrl = function() {
return 'template.html';
}
}
}
The full JS file is:
angular.module('components', [])
.directive('newHost', function() {
return {
restrict: 'E',
template: '<div ng-include="getTemplateUrl()"></div>',
link: function(scope, element, attr) {
scope.newBox = function() {
console.log('new Box');
scope.getTemplateUrl = function() {
return 'template.html';
}
}
}
}
});
angular.module('HelloApp', ['components'])
.controller('MyCtrl', ['$scope', function($scope) {
$scope.name = 'This is the controller';
$scope.go = function() {
console.log('Processing...');
$scope.newBox();
}
}]);
index.html is:
<!doctype html>
<html ng-app="HelloApp" >
<head>
<meta charset="utf-8">
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<new-host></new-host>
<br/>
<a ng-href='#here' ng-click='go()'>process data</a>
</div>
</body>
</html>
And template.html is a simple example:
<div>
This is some content from template.html
</div>
If you look in plunker, once you press 'process data', the template.html content is then added using the newBox() function. Now, you would replace that link with your callback.
Using ng-show and a boolean to hide/show the content
One way which is going in a slightly different direction than you is to use ng-show and hide the template until newBox() is called.
I created a JSFiddle that shows an example of how to do this.
The new-host tag is hidden at the start using ng-show:
<div ng-controller="MyCtrl">
<new-host ng-show='displayNewHost'></new-host>
<br/>
<a ng-href='#here' ng-click='go()' >process data</a>
</div>
The link process data is to simulate your success callback, so when you click on it, it will call $scope.newBox()
Here is the main JS file:
angular.module('components',[])
.directive('newHost', function() {
return {
restrict: 'E',
link: function(scope, element, attr) {
scope.newBox = function() {
console.log('new Box');
scope.displayNewHost = true;
};
}
}
})
angular.module('HelloApp', ['components'])
function MyCtrl($scope) {
$scope.displayNewHost = false;
$scope.name = 'This is the controller';
$scope.go = function() {
console.log('Processing...');
$scope.newBox();
}
}
angular.module('myApp', ['components'])
As you see, in the controller we set displayNewHost to false hiding the directive. Once one clicks on the process data link, the newBox function sets displayNewHost to true and then the content appears.
In your example, you would replace the 'template' by 'templateUrl' pointing to your file.
That is another solution.
Editing my answer to answer the follow-up question / newBox is not a function error
Just reading your code (without checking using plunker so I may be wrong), I am guessing that the problem is that once you are in the success function $scope points to another scope. Try to change your code to this... please note that I put $scope in a variable 'vm' and then use that in both the core function and the success callback.
host.controller('hostsController', function($scope, $http, $window, $rootScope){
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
console.log("failed to change routes");
});
var vm = $scope;
vm.newHost = {};
vm.addNewHost = function() {
$http({
method : 'POST',
url : 'http://192.168.0.99:5000/newHost',
data : JSON.stringify(vm.newHost), // pass in data as strings
})
.success(function(data) {
console.log(data);
vm.newBox()
//on success we want to close the modal and reset the data
vm.newHost = {}
vm.dismiss()
});
};
});'

Related

How can I replace link hrefs with ngClicks in dynamically generated HTML within AngularJS?

I'm consuming dynamically generated HTML from an API which may contain hyperlinks, and I'm wanting to replace the hrefs within them with ngClicks. The following directive appears to modify the HTML as intended when I check it in a DOM inspector, but clicking it does nothing. What am I doing wrong?
app.directive('replaceLinks', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function(scope) {
return scope.$eval(attrs.replaceLinks);
}, function(value) {
element.html(value);
angular.forEach(element.contents().find("a"), function(link) {
link.removeAttribute("href");
link.removeAttribute("target");
link.setAttribute("ng-click", "alert('test')");
});
$compile(element.contents())(scope);
});
}
};
}]);
Instead of removing the href please set it to blank (this will preserve the link css), also the ng-click calling the alert can be done by calling the alert('test') inside of a scope method, why the alert didn't fire, is explained in this SO Answer, please refer the below sample code!
// <body ng-app='myApp' binds to the app being created below.
var app = angular.module('myApp', []);
app.directive('replaceLinks', ['$compile', function($compile) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.forEach(element.find("a"), function(link) {
link.setAttribute("href", "");
link.removeAttribute("target");
link.setAttribute("ng-click", "scopeAlert('test')");
});
$compile(element.contents())(scope);
}
};
}]);
// Register MyController object to this app
app.controller('MyController', ['$scope', MyController]);
// We define a simple controller constructor.
function MyController($scope) {
// On controller load, assign 'World' to the "name" model
// in <input type="text" ng-model="name"></input>
$scope.name = 'World';
$scope.scopeAlert = function(name) {
window.alert(name);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller='MyController' ng-app="myApp">
<div replace-links>
test 1
test 2
test 3
</div>
</div>

Update href in AngularJS before navigating to the URL

In an AngularJS application I have the following code:
<a target="_blank" ng-href="{{someProperty.href}}" ng-click="someMethod($event)">Hello!</a>
Now, someMethod() and someProperty belong to the same service.
Initially, someProperty.href has a default value.
What I need to do is that when the user clicks on the link, some calculation is performed and someProperty.href gets a new value. This new value need to be reflected in the ng-href and the user should be redirected to that new href.
tried reconstructing it and it seems to work, clicking on the link opens a new tab with the new url.
https://plnkr.co/edit/gy4eIKn02uF0S8dLGNx2?p=preview
<a target="_blank" ng-href="{{someService.someProperty.href}}" ng-click="someService.someMethod()">
Hello!
<br/>
{{someService.someProperty.href}}
</a>
You can do it as like the below code
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
$timeout(function() {
scope.myExample();
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
}
};
}
})(angular);
In Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope'];
function HomeController($scope) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
$scope.url = 'http://google.com';
};
}
})(angular);
In HTML You can use like
<div ng-controller="HomeController">
<a ng-href="url" my-example="someFunction()" target="_blank">Click me to redirect</a>
</div>
Here instead of ng-click I have used custom directive which simulates the ng-click but not as exactly as ng-click
If the parent scope function is async you change your directive and someFunction in controller as like below
#Directive
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
scope.myExample().then(function() {
$timeout(function() {
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
});
}
};
}
})(angular);
#Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope', '$q'];
function HomeController($scope, $q) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
var deferred = $q.defer();
$scope.url = 'http://google.com';
deferred.resolve('');
return deferred.promise;
};
}
})(angular);
Here I just simulated the async, it may be your http call too
make sure the value in href tag is updated after you click on it. Try debugging the ng-click function. According to the official documentation:
Using AngularJS markup like {{hash}} in an href attribute will make the link go to the wrong URL if the user clicks it before AngularJS has a chance to replace the {{hash}} markup with its value. Until AngularJS replaces the markup the link will be broken and will most likely return a 404 error. The ngHref directive solves this problem.
In your case, i think the old link is not getting updated with the new values of the model. Hence, redirecting to old link.
Try calling a function on ui-sref or ng-href which will return the state name that you want to redirect. Something like this
html:
<a ui-href="{{GetUpdatedHref()}}" >Hello!</a>
controller.js
$scope.GetUpdatedHref = function(){
//perform your http call while it's being processed show a loader then finally return the desired state (page location)
return "main.home"
}
If this doesn't work for you use ng-click instead of ui-sref and then $state.go("main.home") inside function.
Hope this may resolve your problem.

How can I pass ng-click to the element my directive replaces?

Using the angular directive Max created on this post for easily importing SVGs, I've imported a handful of SVGs on my page. I now want to add a click event to an SVG, except the directive doesn't transfer the click method to the imported SVG. If I inspect the SVG in my browser I see that it is indeed missing the ng-click.
HTML
<svg-image class="svg foo" src="img/foo.svg" ng-click="bar()"></svg-image>
JS
$scope.bar = function() {
console.log("click");
};
If I move ng-click="bar()" to another element on my page it works just fine. I've also tried moving ng-click="bar()" to the svg file itself which didn't work, and I've tried doing what was suggested in this post which didn't work either.
plunker as requested: https://plnkr.co/edit/eqOZJO5Ar8oOmXCjg3Vs
One of possible solutions is to compile your new element and call resulting template function, passing in scope:
.directive('svgImage', ['$http', '$compile', function($http, $compile) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
var imgURL = element.attr('src');
// if you want to use ng-include, then
// instead of the above line write the bellow:
// var imgURL = element.attr('ng-include');
var request = $http.get(
imgURL,
{'Content-Type': 'application/xml'}
);
scope.manipulateImgNode = function(data, elem){
var $svg = angular.element(data)[4];
var imgClass = elem.attr('class');
if(typeof(imgClass) !== 'undefined') {
var classes = imgClass.split(' ');
for(var i = 0; i < classes.length; ++i){
$svg.classList.add(classes[i]);
}
}
$svg.removeAttribute('xmlns:a');
angular.element($svg).attr("ng-click", attrs.ngClick);
return $compile($svg)(scope);
};
request.success(function(data){
element.replaceWith(scope.manipulateImgNode(data, element));
});
}
};
}]);
Plunker
Try this
var jimApp = angular.module("mainApp", []);
jimApp.controller('mainCtrl', function($scope){
$scope.bar = function() {
console.log("click");
};
});
jimApp.directive('svgImage', function() {
return {
restrict: 'E',
replace: true,
scope: {
onClick: '&'
},
template: '<div ng-click="bar();">Hai</div>',
link: function(scope, element, attrs, fn) {
scope.bar = function(){
scope.onClick()();
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="mainApp" ng-controller="mainCtrl">
asas
<svg-image on-click="bar"></svg-image>
</div>

AngularJS: Controller not available in sub directives when using $compile

my parent directive is fed with a file containing informations about site structure and builds a template string dynamically. The content of this string refers to other directives. These might fetch as well data from a source to build (eg) tables.
As they are all using $http.get to fetch data I wanted to inform the parent directive when these sub directives are ready using a required controller.
Problem: The parent directive is using $compile to build the site and does not "forward" the controller which results in "Error: [$compile:ctreq] http://errors.angularjs.org/1.4.1/$compile/ctreq ..."
Found already this answer: "Angular $compile with required controller" which is not a big help, especially as transcludedControllers seems to be deprecated and doesn't work in my code.
Any help or just pointing to another already asked /answered question is highly appreciated.
angular.module('TestDirectives', [])
.directive('widgetBlock', function ($compile) {
return {
restrict: 'A',
replace: true,
controller: function ($scope) {
this.reportReady = function () {
$scope.widgetready = String(Number($scope.widgetready) + 1);
}
},
link: function (scope, elem, attr) {
// template data will be constructed dynamically based on a
// xml - file fetched with $http.get
var template = '<div><p >This isn\'t always fun</p>' +
'<div mini-widget></div>' +
'<div micro-widget></div></div>';
var content = $compile(template)(scope)
elem.append(content);
attr.$observe('widgetready', function (newValue) {
// quantity of widgets depends on widgets found in xml - file
if (newValue === "2") {
// all widgets on screen, translate some keys
console.info("Application is ready to translate!!");
}
});
}
};
})
.directive('miniWidget', function ($compile, $http) {
return {
restrict: 'A',
require: '^widgetBlock',
scope: {},
link: function (scope, elem, attr, ctrl) {
$http.get('json/daten.json').then(function (data) {
scope.person = data.data;
var test = '<p>hello {{person.name}}</p>';
var content = $compile(test)(scope)
elem.append(content);
ctrl.reportReady();
});
}
}
})
.directive('microWidget', function ($compile, $http) {
return {
restrict: 'A',
require: '^widgetBlock',
scope: {},
link: function (scope, elem, attr, ctrl) {
$http.get('json/daten2.json').then(function (data) {
scope.person = data.data;
var test = '<p>Whatever {{person.name}}</p>';
var content = $compile(test)(scope)
elem.append(content);
ctrl.reportReady();
});
}
}
});
Just to be complete, main app
angular.module('DirectiveTestApp', ['TestDirectives'])
.controller('MainController', function ($scope) {
$scope.widgetready = "0";
});
And the html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Directives Test</title>
</head>
<body ng-app="DirectiveTestApp">
<div ng-controller="MainController">
<div widget-block widgetready="{{widgetready}}">
</div>
</div>
<script src="bower_components/angular/angular.min.js"></script>
<script src="script/app.js"></script>
<script src="script/directives.js"></script>
</body>
</html>
Thanks a lot!
When you $compile and then link - which is what you are doing with $compile(template)(scope) - during the link-phase, the microWidget directive looks for the required controller up the DOM tree. The problem is that your template is not in the DOM at that time.
There are 2 ways to address this:
#1: using the cloneAttachFn:
The second parameter of the link function, which is the result of $compile(template), allows you to specify a cloneAttachFn - see documentation. This function is invoked prior to link-phase and it gets a cloned version of the to-be-linked node. Use that function to place the element where needed:
var content = $compile(template)(scope, function cloneAttachFn(clonedContent){
// this happens prior to link-phase of the content
elem.append(clonedContent);
});
// this happens after link-phase of the content, so it doesn't work
// elem.append(content)
(btw, content[0] === clonedContent[0])
#2: append content prior to $compile/link (as suggested in an answer to this question):
var content = angular.element(template);
elem.append(content);
$compile(content)(scope);
Demo

AngularJS: include not working from a custom directive

I have a custom directive and I would like to use it to include an html content to the document after clicking on it.
Plunker: http://plnkr.co/edit/u2KUKU3WgVf637PGA9A1?p=preview
JS:
angular.module("app", [])
.controller("MyController", function ($scope) {
})
.directive('addFooter', ['$compile', '$rootScope', function($compile, $rootScope){
return {
restrict: 'E',
template: '<button>add footer</button>',
controller: 'MyController',
link: function( scope, element, attrs, controller) {
element.bind( "click", function() {
scope.footer = "'footer.html'";
})}
};
}])
HTML:
<body ng-app="app">
<script type="text/ng-template" id="footer.html">
FOOTER
</script>
<div ng-controller="MyController">
<add-footer></add-footer>
<div ng-include="footer"></div>
</div>
</body>
Not sure why it is not working, as it worked fine before it was moved into the directive. Outside the directive, I was also referencing to $scope.footer with some link. I tried using $rootScope, but also no effect. Any tips please?
First. Remove unnecessary quote symbols:
element.bind( "click", function() {
scope.footer = "footer.html"; // not "'footer.html'"
});
Second. You should notify angularjs that you have asynchronously updated scope values:
element.bind("click", function() {
scope.$apply(function() {
scope.footer = "footer.html";
});
});
Or like that
element.bind("click", function() {
scope.footer = "footer.html";
scope.$apply();
});

Resources