angular scope duplicate in dynamic directive - angularjs

I'm building Chrome extension that inject script and Html tags to page.
And I use directive to dynamically load template and controller
as below
<content-page content-url="{{IndexCtrl.partialPage}}">
</content-page>
in contactPage directive:
myApp.directive('contentPage',function($compile){
return {
restrict: "E",
replace:true,
link: function(scope, el, attrs){
attrs.$observe('contentUrl',function(pageUrl){
$.ajax({
url : pageUrl,
success : function(result){
el.html(result).show();
$compile(el.contents())(scope);
}
});
})
}
}
})
and I assign controller in html file, for example 'pageUrl' point to this html file:
<div ng-controller="infoCtrl">
......
</div>
then problem comes, the 'scope' of infoCtrl will exist forever unless I reload page; and my controller code like this
myApp.controller('infoCtrl',function($scope,$rootScope){
$scope.$on('get data',function(e,args){
console.log('get data',args);
//make request
}
}
as my code, while infoCtrl element be recreate, scope will be duplicated, and make multiple same request
right now I use a ugly fix to check $scope.$id and save it locally, check it before request, only enable the controller with newest id.
Is there other way to prevent scope be duplicate?

Related

Calling controller method from directive in template

I have a directive that is sitting in a template that is then included on a page.
If I place my directive directly onto my page, then on a button click I can call a method within my controller.
However, when I place the directive within a template, and then the template on the page, I can no longer call a method in my controller from the directive.
I've tried a number of things with the posted code below my latest attempt. However, this code produces the error
asking for new/isolated scope on:
So HTML first;
This is on my HTML page.
<session-list trackid='san'></session-list>
This is the template HTML;
<div class="container col-sm-12 col-xs-12">
<div>Session list template for {{trackid}}</div>
<session-calendar callback-fn="ctrlFn()"></session-calendar>
</div>
My primary controller looks like this with the "eventClick" method I want to call.
angular.module('GAP.viewsessions', ['ngRoute'])
.controller('viewsessionsCtrl', ['$scope', function($scope){
$scope.eventClick = function(eventData){
console.log(eventData);
}
}]);
Then the "SessionList" directive;
angular.module("GAP.sessionList", [])
.directive("sessionList", function(){
return {
restrict: 'E',
link: function(scope, element, attributes){
},
scope: {
trackid: '#'
},
templateUrl: '/templates/sessionlist.html', // or use a path to a html file like 'path_to/template.html'
replace: true,
};
})
The other directive is a FullCalendar and in the click event of the event I have this;
eventClick: function(calEvent, jsEvent, view) {
scope.someCtrlFn();
if (scope.eventClick){
scope.eventClick(calEvent.data);
}
},
And If I include this;
scope: { someCtrlFn: '&callbackFn' },
I get the previously quoted error. If I leave it out, then the page renders but the "eventClick" method is never run in my controller.
One possible workaround is use an angular event
Inject $rootScope in directive then something like:
$rootScope.$broadcast('cal-event-clicked', eventData)
In controller
$scope.$on('cal-event-clicked', function(evt, data){
$scope.eventClick(data)
})

Syntax highlighted code snippet wont display with AngularJs ngBind

I used some Syntax highlighting API for highlighting code snippet for my web application.To do that i have used highlightjs .I created popup model and inside model i have put <pre> tag and when model open it should display my highlighted xml string.
HTML Code snippet
<pre id="tepXml" ><code class="xml">{{tepXml}}</code></pre>
In AngularJs controller dynamically bind the value to tepXml from server.
AngularJs controller
...$promise.then(function(data){
$scope.tepXml=data.xml;
}
But the problem was that when i open popup model my xml content is empty.nothing display anything.But when i removed <code class="xml"></code> from <pre> xml content would display with out highlighting.I referred some posts and used $compile in angularJs controller but the problem was still the same.
AngularJs controller with $compile
var target = angular.element($window.document.querySelector('#tepXml'));
var myHTML = data.xml;
target.append( $compile( myHTML )($scope) );
If someone knows where i went wrong please point me out.
Plunker
The quick answer is to do:
$promise.then(function(data){
$scope.tepXml=data.xml;
// Call highlight api
$timeout(function() {
$('pre#tepXml code').each(function(i, block) {
hljs.highlightBlock(block); //or whatever the correct highlightjs call is.
});
});
The more Angular way of doing things is to call a jQuery function from Angular is to write a Directive. Something like this:
.directive("highlightCode", function($interval) {
return {
restrict: "A",
scope: { highlightCode: "=" },
link: function(scope, elem, attrs) {
$scope.$watch('highlightCode', function() {
$(elem).find('code').each(function(i, block) {
hljs.highlightBlock(block); //or whatever the correct highlightjs call is.
});
}
}
});
Used like this:
<pre id="tepXml" highlight-code="tepXml"><code class="xml">{{tepXml}}</code></pre>

How do you require angular template html?

When creating custom directives, if you want to put your view/template html in separate files, Angular seems to load the template from a public URL, making an HTTP request for it.
How do you include this template HTML inline while keeping it in a separate file?
With ES6 nothing is impossible:
import yourTemplate from 'path/to/file';
// inject $templateProvider in start point of your application
$templateProvider.put('path/to/file', yourTemplate);
$templateProvider at its own is simple $cacheFactory instance, where you can put any html, by any key, that can be used in ng-include or simply used in your directive as shown below:
//Directive
import yourTemplate from 'path/to/file';
that is used within directive configuration:
...,
controller: xxx,
template: yourTemplate,
link: () => { ... }
...
Use directive to fix this
HTML
<div custom-include url="{{url}}"></div>
Directive
app.directive('customInclude', ['$http', '$compile', '$timeout', customInclude]);
function customInclude($http, $compile, $timeout) {
return {
restrict: 'A',
link: function link($scope, elem, attrs) {
//if url is not empty
if (attrs.url) {
$http({ method: 'GET', url: attrs.url, cache: true }).then(function (result) {
elem.append($compile(angular.element(result.data))($scope));
//after sometime we add width and height of modal
$timeout(function () {
//write your own code
}, 1, false);
});
}
}
};
}
"inline" in a separate file is contradicting itself.
Since angular is client side code, loading a template that is stored in it's own separate file will always require a http request.
The only way to avoid these requests is to add them inline with your other code, so not in a separate file.

Change templateURL of directive dynamically after $http.get()

I'm working on 'skeleton' loading the UI in different components. I have a directive that I'm loading a template initially (this template has low opacity and looks like a mock table). Once I get the data I need in an http.get() then I want to change the templateUrl of the directive. Below is what I've tried so far.
function registers($log, $state, $templateCache, currentCountData, storeFactory) {
var directive = {
restrict: 'A',
scope: true,
templateUrl: '/app/shared/tableMock/tableMock.html',
link: link
};
return directive;
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$templateCache.put('/app/dashboard/registers/registers.html');
});
}
}
I'm not sure I'm on the right track. I can step through and see the storeFactory return the correct data from my factory. How can I then change the templateUrl of the directive?
For cases like this I usually do something like this in my directive template:
<div ng-switch="viewState">
<div ng-switch-when="gotRegisters">
Content for when you get the registers
</div>
<div ng-switch-default>
For when you don't have the registers
</div>
</div>
This way you could just change a variable to show your content ie scope.viewState = 'gotRegisters'; instead of waiting for it to download after you already downloaded your registers.
With a help from this question I was able to come up with this
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$http.get('/app/dashboard/registers/registers.html', { cache: $templateCache }).success(function (tplContent) {
element.replaceWith($compile(tplContent)(scope));
});
});
}
tplContent correlates to the response of the $http.get(). It's the html in the registers.html file. element represents the directive itself.

Passing controller scope from directive to partial

I am new to AngularJs. I have a route configured to a controller and a template. In the template I am calling a custom directive. The custom directive loads a partial file in which I need to fetch the scope which is set by the controller. How can I pass the scope from the directive to the partial so that the partial file can consume the scope data.
Kindly let me know how to get this implemented in AngularJs
Code snippet inside the link function of the directive:
myApp.directive('basicSummary', function() {
return {
restrict: 'E',
replace:'true',
templateUrl: "partials/basicSummary.html",
link: function(scope, elem, attrs) {
console.log(scope.testURL);
}
}
});
Output on the console is : undefined
Update: I found the root cause of why the variable was not getting initialized. The issue, is that the variable is being fetched by making an ajax call in the controller and by the time the ajax call is completed and the variable is put inside the scope inside the controller, the partial file is already loaded and hence I am getting the value of the variable inside the directive as undefined.
How can I make sure that only on success of the http call, I load the partial and the directive?
Adding the jsfiddle link: http://jsfiddle.net/prashdeep/VKkGz/
You could add a new variable to your scope in the definition of your directive to create a two-way binding, that you could safely watch for changes (for use in Javascript once the variable has been populated via ajax), and in your template use ng-show to show/hide based on whether or not the variable is defined. See this JSFiddle for an example of how that would work: http://jsfiddle.net/HB7LU/3588/
Default Template
<div ng-controller="MyCtrl">
<my-test my-test-url="myAjaxProperty"></my-test>
</div>
App Definition
var myApp = angular.module('myApp',[]);
myApp.directive('myTest', function(){
return {
restrict: 'E',
repalce: 'true',
template: '<div ng-show="myTestUrl">{{myTestUrl}}</div>',
scope: { myTestUrl: '=' },
link: function(scope, elem, attrs){
scope.$watch('myTestUrl', function(newVal, oldVal){
if(newVal){
console.log("Watched value is defined as: "+scope.myTestUrl);
}
})
}
}
});
function MyCtrl($scope, $timeout) {
$timeout(function(){
$scope.myAjaxProperty = "My Test Url";
console.log("Ajax returned");
}, 3000, true)
console.log("Default Controller Initialized");
}
as long as you don't isolate your scope with,
scope: {}
in your directive, your scope has access to its parent controller's scope directly.

Resources