Access the HTML page inside service in AngularJS - angularjs

I want to access particular HTML page inside a service. Currently, I have made the function inside a controller that can be called from its linked HTML page but I want to access that function from multiple pages. So, I'll want service or factory for it to make it available from anywhere.
In that function, I want the content of particular HTML page but that content is available only when I call the function from that page only.
Here is my function.
$scope.downloadReport = function(type) {
var pdf = new jsPDF('p', 'pt', 'a4');
var source;
var options = {
pagesplit: true,
background: '#ffffff',
letterRendering: true
};
pdf.internal.scaleFactor = 2;
source = document.getElementsByClassName('complete-report')[0];
pdf.addHTML(source, options, function () {
pdf.save('report.pdf');
});
};
document.getElementsByClassName('complete-report')[0]; It will get data from the HTML page linked with controller.
So, I need service which access the particular HTML page such that I use that function there to get the content of particular HTML page.

As pointed to wprzechodzen, you shouldn't access directly your HTML inside a service but, instead, you should pass only source variable.
However, if you want to access HTML in service you have to do something like this:
yourApp.controller('something', function($scope, $window, $document, yourServiceName){
yourServiceName.yourFunction($document)
})
yourApp.factory('yourServiceName', function(){
return {
yourFunction: function(doc){
//here you get $document
}
}
})
Here's the $document's docs

You have to make sure that the DOM element which you are trying to access must be loaded in the DOM. Till then you cant do anything.
You can try giving id tag (e.g. elementID)for that perticular dom element and try accessing like below.
var source = angular.element("#elementID");

Related

Redirect to another html in angularjs webpage reloads controller

In my html page, clicking a row sends you to another html page. ng-click calls the funtion showScripts and fills the variable $scope.Scripts using ajax call, which I am using in this new html page.
$scope.showScripts=function(event,item){
$http.get('/Scripts/'+item).success(
function(data) {
$scope.Scripts = data.responseData;
});
if (event.ctrlKey) {
window.open("/scripts.html","_blank"); // in new tab
} else {
window.open('/Scripts.html',"mywindow"); // in new tab
}
}
The File Scripts.html is using the same controller. So when the html loads, the angularjs file loads again and the values in $scope.Scripts go back to undefined. Can I prevent the controller from refreshing the values?
You have to use this logic:
var yourUrl = '/scripts.html';
$window.location.href = yourUrl;
OR:
$window.location.href = '/scripts.html';
$window.location.reload();
when you are using same controller for different views, you can create Service or factory method for storing and retrieving data. Another way is that you can store the data in $rootScope as well.

Marker eventListener fails to give me the object I request

I am doing an app where when I click on a marker on a google.map triggers some action, specifically I want to get an object out of an array of objects to display the details of such object.
I use the controller defs like so
(function() {
'use strict';
angular
.module('gulpAngular')
.controller('GeolocationController', GeolocationController);
/** #ngInject */
function GeolocationController(HouseList) {
....
}
})();
The HouseList is a service defined elsewhere and having a method called getHouses()
Inside my controller, I do besides other things this:
var vm = this;
vm.houses = HouseList.getHouses();
Then I define my marker on the map like
allMarkers = new google.maps.Marker({....});
and add an Listener to it like below. To make things simple, I assign vm.house = vm.houses[0]
allMarkers.addListener('click', function() {
vm.house = vm.houses[0]
}
Now I suppose I should be able to use the vm.house object to display in my html block the attributes of this house object in the fashion of
<h4>{{vm.house.bedrooms}}</h4>
HOWEVER, NOTHING GETS DISPLAYED. I should see my vm.house object be updated and this reflected on the DOM, correct? What do I miss?
Funny: When i add a simple button on the html and use a ng-click function on it without anything other than say console.log(vm.house), not only it does display the correct object, but also the refresh of the html is happening. I am lost
thanks
Peter
addListener is not an Angular function and will not trigger the digest loop. You need to do it manually.
For example:
allMarkers.addListener('click', function() {
$scope.$apply(function () {
vm.house = vm.houses[0]
});
});
Note that you need to inject $scope for this.
ng-click triggers the digest loop, which is why using it will update the HTML.

How to Lazyload controller and template in one request using angular-ui-router

I'm trying to lazy-load components. The component is an html fragment with an embedded script tag that contains the controller.
<script>
... controller code .....
</script>
<div>
... template ....
</div>
The fragment is generated in ONE html request so I cannot use templateUrl AND componentURL in the state definition.
I have tried to use the templateProvider to get the component, than extract the script code for the function and register it using a reference to the controllerProvider.
I'm sure there must be a better way to do this than the ugly solution I have come up with. I make a global reference to the controllerpovider, then I read the component thru the templateProvide using a getComponent service. Next I extract the script and evaluate it, which also registers the controller.
See the plunker for the way I'm trying to solve this.
.factory('getComponent', function($http, $q) {
return function (params) {
var d = $q.defer();
// optional parameters
$http.get('myComponent.html').then(function (html) {
// the component contains a script tag and the rest of the template.
// the script tags contain the controller code.
// first i extract the two parts
var parser = new window.DOMParser();
var doc = parser.parseFromString(html.data, 'text/html');
var script = doc.querySelector('script');
// Here is my problem. I now need to instantiate and register the controller.
// It is now done using an eval which of cours is not the way to go
eval(script.textContent);
// return the htm which contains the template
var html = doc.querySelector('body').innerHTML;
d.resolve(html);
});
return d.promise;
};
})
Maybe it could be done using a templateProvider AND a controllerProvider but I'm not sure how to resolve both with one http request. Any help / ideas would be greatly appreciated.
Here's a working plunkr
You do not have access to $controllerProvider at runtime, so you cannot register a named controller.
UI-Router doesn't require named/registered controller functions. { controller: function() {} } is perfectly valid in a state definition
However, if you need the controller to be registered, you could use a tool like ocLazyLoad to register it.
UI-Router links the controller to the template, so there's no need for ng-controllersprinkled in the html.
Here's how I hacked this together. getController factory now keeps a cache of "promises for components". I retained your eval and template parsing to split the response into the two pieces. I resolved the promise with an object containing the ctrl/template.
component factory
.factory('getComponent', function($http, $q) {
var components = {};
return function (name, params) {
if (components[name])
return components[name];
return components[name] = $http.get(name + '.html').then(extractComponent);
function extractComponent(html) {
var parser = new window.DOMParser();
var doc = parser.parseFromString(html.data, 'text/html');
var script = doc.querySelector('script');
// returns a function from the <script> tag
var ctrl = eval(script.textContent);
// return the htm which contains the template
var tpl = doc.querySelector('body').innerHTML;
// resolve the promise with this "component"
return {ctrl: ctrl, tpl: tpl};
}
};
})
myComponent.html
<script>
var controller = function($scope) {
$scope.sayHi = function() {
alert(' Hi from My controller')
}
};
// This statement is assigned to a variable after the eval()
controller;
</script>
// No ng-controller
<div>
Lazyloaded template with controller in one pass.
<button ng-click="sayHi()">Click me to see that the controller actually works.</button>
</div>
In the state definition:
I created a resolve called 'component'. That resolve asks the getComponent factory to fetch the component called 'myComponent' (hint: 'myComponent' could be a route parameter)
When it's ready, the resolve is exposed to the UI-Router state subtree.
The state is activated
The view is initialized
The controller provider and template provider inject the resolved component, and return the controller/template to UI-Router.
Smell test
I should mention that fetching a controller and template in a single html file and manually parsing smells wrong to me.
Some variations you could pursue:
Improve the getComponent factory; Split the component into html/js and fetch each separately. Use $q.all to wait for both fetches.
Use ocLazyLoad to register the controller globally with $controllerProvider
Fetch and store the template in the $templateCache.

Detecting and using a changed controller dependency during controller lifetime

For the sake of understanding suppose I have an AngularJS application that has similar data as Stackoverflow so that it:
is using the usual ngRoute/$routeProvider
has a userService that returns favourite and ignore tag lists of the logged in user - both lists are fetched at the same time and request for them is a promise that when resolved caches these lists
has a view that displays a list of questions with a QuestionsController that provides its model (similar to Stackoverflow)
QuestionsController makes a request for questions and then depends on cached tag lists to mark them appropriately
As the recommended guideline when controllers rely on other async data we should offload those to route resolve part so when controllers are being instantiated those promises are already resolved. Therefore I offload tag list fetching to it so both lists are ready and injected into the controller. This all works as expected.
The additional feature of my questions list view is that when a user clicks a tag displayed on questions it automatically adds this tag to favourite list (or off of it when that tag is already part of favourite list).
Route configuration
...
.when({
templateUrl: "...",
controller: "QuestionsController as context",
resolve: {
tags: ["userService", function(userService) {
return userService.getMyTags();
}]
}
})
.when(...)
...
Controller pseudo code
QuestionsController.prototype.markQuestions = function() {
this.model.questions.forEach(function(q, idx) {
// "myTags" is resolve injected dependency
q.isFavourite = q.tags.any(myTags.favourite);
q.isIgnored = q.tags.any(myTags.ignored);
});
};
QuestionsController.prototype.toggleTag = function(tag) {
var self = this;
// change tag subscription
tagService
.toggleFavourite(tag)
.then(function() {
// re-mark questions based on the new set of tags
self.markQuestions();
});
};
The problem
When the view displays, all questions are loaded and correctly marked as per provided tag lists. Now when a user clicks on a specific tag and that tag's favourite status gets changes my controller's dependency should get automatically updated.
How can I do that since my controller is already instantiated and had tag lists injected during instantiation?
I would like to avoid loading those lists manually within my controller because in that case I should do the same during instantiation and reuse the same functionality and not have it in two places (route resolve and inside controller).
So long as your "resolved" variable is referring to the same object used elsewhere, they are one and the same.
So, if your userService.getMyTags is conceptually like the following:
.factory("userService", function($timeout){
var tags = [/*...*/];
return {
getMyTags: function(){
return $timeout(function(){ return tags; }, 500);
},
addTag: function(newTag){
tags.push(newTag);
}
}
});
Then any reference to tags anywhere would get the changes:
.controller("ViewCtrl", function($scope, tags){
$scope.tags = tags; // tags is "resolved" with userService.getMyTags()
})
.controller("AddTagCtrl", function($scope, userService){
$scope.addTag = function(newTag){
userService.addTag(newTag); // changes will be reflected in ViewCtrl
}
}
plunker, to illustrate

How to execute AngularJS controller function on page load?

Currently I have an Angular.js page that allows searching and displays results. User clicks on a search result, then clicks back button. I want the search results to be displayed again but I can't work out how to trigger the search to execute. Here's the detail:
My Angular.js page is a search page, with a search field and a search
button. The user can manually type in a query and press a button and
and ajax query is fired and the results are displayed. I update the URL with the search term. That all works fine.
User clicks on a result of the search and is taken to a different page - that works fine too.
User clicks back button, and goes back to my angular search page, and the correct URL is displayed, including the search term. All works fine.
I have bound the search field value to the search term in the URL, so it contains the expected search term. All works fine.
How do I get the search function to execute again without the user having to press the "search button"? If it was jquery then I would execute a function in the documentready function. I can't see the Angular.js equivalent.
On the one hand as #Mark-Rajcok said you can just get away with private inner function:
// at the bottom of your controller
var init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
// and fire it after definition
init();
Also you can take a look at ng-init directive. Implementation will be much like:
// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>
// in controller
$scope.init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
But take care about it as angular documentation implies (since v1.2) to NOT use ng-init for that. However imo it depends on architecture of your app.
I used ng-init when I wanted to pass a value from back-end into angular app:
<div data-ng-controller="myCtrl" data-ng-init="init('%some_backend_value%')"></div>
Try this?
$scope.$on('$viewContentLoaded', function() {
//call it here
});
I could never get $viewContentLoaded to work for me, and ng-init should really only be used in an ng-repeat (according to the documentation), and also calling a function directly in a controller can cause errors if the code relies on an element that hasn't been defined yet.
This is what I do and it works for me:
$scope.$on('$routeChangeSuccess', function () {
// do something
});
Unless you're using ui-router. Then it's:
$scope.$on('$stateChangeSuccess', function () {
// do something
});
angular.element(document).ready(function () {
// your code here
});
Dimitri's/Mark's solution didn't work for me but using the $timeout function seems to work well to ensure your code only runs after the markup is rendered.
# Your controller, including $timeout
var $scope.init = function(){
//your code
}
$timeout($scope.init)
Hope it helps.
You can do this if you want to watch the viewContentLoaded DOM object to change and then do something. using $scope.$on works too but differently especially when you have one page mode on your routing.
$scope.$watch('$viewContentLoaded', function(){
// do something
});
You can use angular's $window object:
$window.onload = function(e) {
//your magic here
}
Another alternative:
var myInit = function () {
//...
};
angular.element(document).ready(myInit);
(via https://stackoverflow.com/a/30258904/148412)
Yet another alternative if you have a controller just specific to that page:
(function(){
//code to run
}());
When using $routeProvider you can resolve on .state and bootstrap your service. This is to say, you are going to load Controller and View, only after resolve your Service:
ui-routes
.state('nn', {
url: "/nn",
templateUrl: "views/home/n.html",
controller: 'nnCtrl',
resolve: {
initialised: function (ourBootstrapService, $q) {
var deferred = $q.defer();
ourBootstrapService.init().then(function(initialised) {
deferred.resolve(initialised);
});
return deferred.promise;
}
}
})
Service
function ourBootstrapService() {
function init(){
// this is what we need
}
}
Found Dmitry Evseev answer quite useful.
Case 1 : Using angularJs alone:
To execute a method on page load, you can use ng-init in the view and declare init method in controller, having said that use of heavier function is not recommended, as per the angular Docs on ng-init:
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
HTML:
<div ng-controller="searchController()">
<!-- renaming view code here, including the search box and the buttons -->
</div>
Controller:
app.controller('SearchCtrl', function(){
var doSearch = function(keyword){
//Search code here
}
doSearch($routeParams.searchKeyword);
})
Warning : Do not use this controller for another view meant for a different intention as it will cause the search method be executed there too.
Case 2 : Using Ionic:
The above code will work, just make sure the view cache is disabled in the route.js as:
route.js
.state('app', {
url : '/search',
cache : false, //disable caching of the view here
templateUrl : 'templates/search.html' ,
controller : 'SearchCtrl'
})
Hope this helps
I had the same problem and only this solution worked for me (it runs a function after a complete DOM has been loaded). I use this for scroll to anchor after page has been loaded:
angular.element(window.document.body).ready(function () {
// Your function that runs after all DOM is loaded
});
You can save the search results in a common service which can use from anywhere and doesn't clear when navigate to another page, and then you can set the search results with the saved data for the click of back button
function search(searchTerm) {
// retrieve the data here;
RetrievedData = CallService();
CommonFunctionalityService.saveSerachResults(RetrievedData);
}
For your backbutton
function Backbutton() {
RetrievedData = CommonFunctionalityService.retrieveResults();
}
call initial methods inside self initialize function.
(function initController() {
// do your initialize here
})();

Resources