How to register existing elements with Angular? - javascript

Fairly new to Angular and working inside of an existing code base.
Basically, there's an element that exists within the root document (index.html) that already exists in the html before the Angular library loads. Because of this, the ng-click directive isn't registered.
Is there an easy way that I can pass Angular a reference to the element in question and have it register that as one of its own?
Sample code (obviously missing parts, just to illustrate Angular loads after):
<html>
<body ng-app="allMyCookiesApp">
<a ng-click="giveGeuisACookie()">GIMME</a>
<script src="angular.js"></script>
</body>
</html>
I'd like to get a cookie when I click GIMME.

ng-app will bootstrap everything inside it once angular loads. This includes compiling and linking the ng-click in your example. So I think the real problem may be elsewhere.
The biggest omission from this example is any controller. I expect you are missing a controller that can place the giveGeuisACookie method on the correct scope to be used by ng-click. For example
angular.module('allMyCookiesApp', [])
.controller('geuisCtrl', function($scope) {
$scope.giveGeuisACookie = function() {
// Cookie time
};
});
would define your module for ng-app and register a controller for it. This controller will add the giveGeuisACookie function to its scope.
<html>
<body ng-app="allMyCookiesApp" ng-controller="geuisCtrl">
<a ng-click="giveGeuisACookie()">GIMME</a>
<script src="angular.js"></script>
</body>
</html>
tells angular to use the controller so that ng-click will have access to the correct method.
If this is not the problem it may be worth adding a jsfiddle with a working (or not) example of what you are doing.

Related

Load Angularjs controllers in ngView

I'm new to Angular and just started to build a test project to learn it, now have a problem with loading controllers OnDemand.
let's code a bit, I have the following HTML:
index.html
<body>
<div ng-app="MyApp">
CLICK ME
<div ng-view></div>
</div>
<script>
angular.module("MyApp",['ngRoute'])
.config(function($routeProvider) {
$routeProvider.when('/child', {
templateUrl: 'somwhere/child.html',
controller: 'childCtrl' <!-- will remove -->
});
}) <!-- will remove contoller defination below -->
.controller('childCtrl', function($scope) {
$scope.data = DoHeavyCalc();
});
</script>
</body>
what is obvious to me is that I should define my controller exactly where I config my module (MyApp), which doing this depends on DoHeavyCalc() which is not needed right now! (think this method does a big calculation, but it should be run only when the user clicks on the link, not at the beginning of the app).
Now I want to load the and define the controller inside child.html instead of my index.html. OK, so I removed the sections marked in above code and tried to write the child.html like this:
child.html
<div ng-controller="childCtrl">
{{data}}
</div>
<script>
angular.module("MyApp")
.controller('childCtrl', function($scope) {
$scope.data = DoHeavyCalc();
});
</script>
but is causes an error:
[ng:areg] `childCtrl` is not a function. got undefined.
also i tried to put script tag in child.html before the div tag, but it didn't affect anything.
Now my question is, how can i define and load the controller OnDemand and do my heavy work just when the user routes to a certain location not at the beginning of app?
You are asking about lazy loading ! By default angular doesn't support lazy loading, what you can do ? you can use any third party library like requirejs or others.
Currently angularjs doesn't execute any javascript inside templateUrl or template, more details
Here is a working example of lazy loading using requirejs.
Also there is some discussion regarding onDemand script loading
Lazy loading angularjs
In the child.html page do not include ng-controller attribute. Already child.html is associated with the childCtrl controller. Just remove the attribute ng-controller from the child.html page

Using multiple AngularJS directives in one div tag

I am unable to use multiple directives in the same div tag. The following html does not display the product names that the module supplies
<!DOCTYPE html>
<html ng-app="gemStore">
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" />
</head>
<body>
<div ng-controller="StoreController as store" ng-repeat="pro in store.products">
<h1>{{pro.name}}</h1>
</div>
<script src='scripts/angular.min.js'></script>
<script type="text/javascript" src="scripts/app.js"></script>
</body>
</html>
But if I add the ng-controller in a different div tag(see below) I am able to see the products. What is the reason behind this.
<!DOCTYPE html>
<html ng-app="gemStore">
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" />
</head>
<body>
<div ng-controller="StoreController as store" >
<div ng-repeat="pro in store.products">
<h1>{{pro.name}}</h1>
</div>
</div>
<script src='scripts/angular.min.js'></script>
<script type="text/javascript" src="scripts/app.js"></script>
</body>
</html>
I did search about using multiple directives in a single div tag but was unsuccessful in finding any information.
Is this a restriction imposed by AngularJS?
Following is the contents of app.js
(function() {
var app = angular.module('gemStore', []);
app.controller('StoreController', function() {
this.products = gems;
});
var gems = [
{ name: 'Alwin', price: 200, description: "Checking my name" },
{ name: 'Alwin', price: 200, description: "Checking my name" },
{ name: 'Alwin', price: 200, description: "Checking my name" },
];
})();
You can use multiple directives on the same DOM node.
The issue you are running into is related to $compile priority and what ng-repeat does.
First of all you must understand that there's a clear algorithm that defines the way in which directives on the same DOM node are compiled. There's a priority parameter that defaults to 0. Directives are compiled based on this parameter, highest to lowest. ng-repeat has a priority of 1000 while ng-controller has a priority of 500. So ng-repeat is compiled first.
Second you need to know how ng-repeat works. If you read the sources you see that it uses the terminal parameter. That parameter tells AngularJS that it should not compile any directives with lower priorities.
Thus, in your code, ng-controller is never compiled ("executed").
Further, if ng-controller is not compiled then ng-repeat does not have a $scope to work on, the $digest loop won't have anything to do.
You'd be tempted to think store is undefined, and so is store.products, but you'd be wrong. If that were the case you'd see a lot of "Trying to access property 'products' on undefined object 'store'", but you don't because the code in ng-repeat is never executed.
What you are trying to do is not possible. ng-repeat would normaly create several instance of the specific tag. In your example it would create several divs, each one creating a seperate controller. I have no source for this, but I guess that $scope is only valid inbetween the controller tags. Therefore your ng-repeat argument does not point to you products list, but onto a higher scope. Due to the fact that there is no entry in this scope ng-repeat hast nothing to show.
Putting multiple directives onto a div is not a problem. This is a snippet of code from an app of mine, using both ng-click and ng-class:
<a href="" class="listItems" ng-click="selectPost($index)" ng-class="{disabledLink: !post.available}">
Your problem is you did it with the Controller, I'm not sure exactly how the ng-controller directive works, but you were essentially declaring a different instance of the controller, for each row in a list from the first controller. How this didn't end up in some crazy infinite loop, I don't know. But essentially, don't declare your controller in an ng-repeat. unless your looping through a list of controllers!
Edit:
Daniel has hit the route of the problem much better than I did. The ng-repeat creates a html element for each item in the list. However the controller hadn't been initialised, so the was no scope to get the list from, so there were no elements to draw, and no controllers got created. By putting the ng-controller 1 element further up, the list was created, and there was a scope, containing a list, which the ng-repeat could use to draw your items.
Directives are compiled and linked in priority order with the higher priority directives executed before lower priority directives.
ng-controller has a priority of 0 and ng-repeat has a priority of 1000, so ng-repeat is compiled and linked first.
If both were compiled, this would not cause the issue. However, ng-repeat is also defined with a terminal attribute.
According to angular documentation:
terminal:
If set to true then the current priority will be the last set of
directives which will execute (any directives at the current priority
will still execute as the order of execution on same priority is undefined).
This means that once ng-repeat is compiled, no other directives with a lower priority (such as ng-controller) will be executed.
[EDIT]
Apparently, there is a discrepancy between the source code and documentation as to the correct priority of ng-controller. The code uses a priority of 500, but the documentation mentions it is defined with a priority of 0.

Gist-embed with Angular app

Trying to get gist-embed (https://github.com/blairvanderhoof/gist-embed) working within my Angular app but with no luck.
It works no problem on my homepage (which is not part of the Angular app) but when I use something like:
<code data-gist-id="<gist-id>"></code>
within the app it won't show. There are no messages in the console to explain why though.
Could someone explain why and offer a solution?
(function($) {
$(function() {
// find all elements containing "data-gist-id" attribute.
$('[data-gist-id]').each(function() {
var $elem = $(this),
id,
that lib is coded in such a way one cant really use it in angular,you'll have to look for a fork that offers a proper jquery plugin architecture you can use into a directive.That lib doesnt respect basic jQuery plugin architecture.
And no Error will show up because it's likely the .each will execute before your angular app runs.
As of June (version 1.8), the gist-embed project is now a jQuery plugin.
var $code = $('<code data-gist-id="474f6d7839fccffc4b2a"/>');
$code.appendTo('body').gist();
Basically you have to trigger "gist()" on the dom elements.
Try this, it worked for me very well.
// register trigger gist on every template include
$rootScope.$on('$includeContentLoaded', function() {
// initialize gist on new elements
angular.element(document).ready(function() {
if (typeof(angular.element(document).gist) === 'function') {
angular.element('[data-gist-id]').gist();
}
});
});
I put together a small library hoping to solve the problem.
Check it out : https://github.com/pasupulaphani/angular-gist-embed
Chekout this angular module : https://github.com/kiran3807/another-angular-gist-embed
This allows you to include the gists in your angular project in the form of a directive, one of the attributes for which is the gist-id :
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script src="path/to/another-angular-gist-embed.js"></script>
<script>
angular.module('example',['another-angular-gist-embed']);
angular.module.controller('exampleCtrl',['$scope',function($scope){
$scope.gistId = 'a85770344febb8e30935';
}]);
</script>
</head>
</head>
<body ng-app="example">
<div ng-controller="exampleCtrl">
<!-- This loads a gist with a specific id-->
<gist-embed data-gist-id="gistId"></gist-embed>
</div>
</body>
</html>
Declaration : I am the author of this module

Is there a callback for ng-bind-html-unsafe in AngularJS

I would like to remove some of the elements that are brought into the DOM by this...
<div ng-bind-html-unsafe="whatever"></div>
I wrote a function that will remove the elements, but I need a way to trigger the function after ng-bind-html-unsafe is complete. Is there a callback for ng-bind-html-unsafe or a better approach?
ng-bind-html-unsafe has been removed from the current version (1.2+) of angular. I would recommend using the $sanitize service in the new version of angular. You'll need to include the sanitize library and add it to your module.
That way you can do whatever you want once the sanitize operation is complete and not worry about a callback.
A quick and dirty implementation:
<!doctype html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.js"></script>
<script src="libs/angular/angular-sanitize.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="myController">
<div>{{sanitized}}</div>
</div>
<script>
angular.module('myApp', ['ngSanitize'])
.controller('myController', ['$scope', '$sanitize', function($scope, $sanitize) {
$scope.sanitized = $sanitize('someTextFromSomeSource');
// Do whatever you want here with the cleaned up text
}])
</script>
</body>
</html>
I would move the html in your demo to a directive:
<div ng-bind-html-unsafe="whatever"></div>
in the directive I would manipulate the html based on my needs, I am here basing this on an assumption since I am not sure how often or how whatever variable is being updated so the generic solution will be a $watch listener over this variable like this:
$scope.$watch('whatever',function(newValue,oldValue, scope){
//do something
});
All this code in my new directive, probably you will want to use the postLink function
Here is the Documentation from Angular about this.
Post-linking function
Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.

Javascript single page architecture with module autonomy

I just started this HTML5 project where we decided to make it a single page architecture by leveraging jQuery $.load() method. Unfortunately, as soon as the JS started to grow, we quickly started running into issues where the modules loaded into the master dashboard have no knowledge of their parent.
The architecture looks like this:
dashboard.html (master file)
moduleA.html
moduleA.js
moduleB.html
moduleB.js
moduleC.html
moduleC.js
Since we decided to also keep the JS as separate files, we are having to load all JS files through dashboard.html in order to invoke them individually when modulex is loaded.
So when loading moduleA.html into the dashboard we have to call its corresponding JS. To do this we simply wrote the JS using a Module Pattern so we can easily invoke it by doing a function call, like:
<script>
moduleA
</script>
or this if we want to access a specific property of this member.
<script>
moduleA.someMethod();
</script>
Now, I know there are is gotta be a nicer way of doing this, right? I hate having to have script tags in the HTML modules in order to load its corresponding JS file.
Another limitation of this is the fact that we no longer can work on modules individually, since the scripts and CSS invocation happens on the parent (dashboard.html) so certainly when moduleA.html is loaded directly, it is pure HTML with no script or CSS.
I looked through the other questions but I didn't see anyone with the same problem.
I looked at AngularJS, EmberJS, KO.JS and BoilerPlateJS but none of them addresses what we are trying to accomplish. The only one that has a similar single page concept is jQuery Mobile but I don't know if you can switch from jQuery to jQuery Mobile and everything remains working.
Has anyone face this issue yet? Is there a solution or would I have to go with a custom solution.
Thanks!
I could argue about AngularJS with you. It is exactly what you need
dashboard.html is layout with some directives attached, but power lies in AngularJs if you use ng-view directive
here is example:
dashboard.js
var app = angular.module("modularApp",[]);
app.config(['$routeProvider', "$locationProvider", function routes($routeProvider, $locationProvider) {
$routeProvider.when('/dashboard', {
controller:'HomeCtrl',
templateUrl:'templates/home.html'
});
$routeProvider.when('/moduleA', {
controller:'ModuleACtrl',
templateUrl:'templates/moduleA.html'
});
$routeProvider.when('/moduleB', {
controller:'ModuleBCtrl',
templateUrl:'templates/moduleB.html'
});
$routeProvider.otherwise({redirectTo: "/dashboard"});
}]);
templates/dashboard.html
<html ng-app="modularApp">
<head>
<!--.... include angular minified js file and what else you need...-->
<script type="text/javascript" src="dashboard.js"></script>
<script type="text/javascript" src="moduleACtrl.js"></script>
<script type="text/javascript" src="moduleBCtrl.js"></script>
</head>
<body>
<a ng-href="#/moduleA">Open Module A View</a>
<a ng-href="#/moduleB">Open Module B View</a>
<!-- Add widgets header menus .... -->
<ng-view></ng-view>
</body>
</html>
moduleACtrl.js
var app=angular.module("modularApp");
app.controller("ModuleACtrl",function($scope){
$scope.scopeValue="Hellow from view";
});
moduleBCtrl.js
var app=angular.module("modularApp");
app.controller("ModuleBCtrl",function($scope){
$scope.scopeValue="Hellow from another view";
});
templates/moduleA.html
<div>{{scopeValue}} in module A</div>
templates/moduleB.html
<div>{{scopeValue}} in module B</div>
You can do more complex things with angular then just this. All depends on your needs. Do you have any special requirements :)
Also, you could create your own directive, like ng-view and use your own $route service and $routeProvider so you can add css and javascript you want to dynamically load when some rute match url.
so instead of above routing table, you could have
app.config(['$myRouteProvider', "$locationProvider", function routes($routeProvider, $locationProvider) {
$routeProvider.when('/dashboard', {
javascript:'javascript/dashboard.js',
templateUrl:'templates/dashboard.html',
cssfile: 'css/dashboard.css'
});
$routeProvider.when('/moduleA', {
javascript:'javascript/moduleA.js',
templateUrl:'templates/moduleA.html',
cssfile: 'css/moduleA.css'
});
$routeProvider.when('/moduleB', {
javascript:'javascript/moduleB.js',
templateUrl:'templates/moduleB.html',
cssfile: 'css/moduleB.css'
});
$routeProvider.otherwise({redirectTo: "/dashboard"});
}]);
But that is, pardon on my French, stup. There are couple libs I tried in ruby on rails to acheive similar, but backend is rendering content, or just part of content. But I'm not sure which backend you are using and are you interested to switch to rails anyway.
DomController in BoilerplateJS does what you need, without using any custom HTML attributes. Your dashboard.html can just have place holders where you want to inject your components. I'm just pulling out some html below from BoilerplateJS index.html to show how it works:
<body>
<section id="page-content">
<header>
<section class="theme"></section>
<section class="language"></section>
</header>
<aside>
<section class="main-menu"></section>
</aside>
</section>
</body>
theme, language and main-menu sections above are just place holders in to which relavant components would be injected by the DomController. The DomController can be now used to register the components with appropriate selectors as below:
//scoped DomController that will be effective only on $('#page-content')
var controller = new Boiler.DomController($('#page-content'));
//add routes with DOM node selector queries and relavant components
controller.addRoutes({
".main-menu" : new MainMenuRouteHandler(context),
".language" : new LanguageRouteHandler(context),
".theme" : new ThemeRouteHandler(context)
});
controller.start();
Above code is extracted from "/boilerplatejs/src/modules/baseModule/module.js"

Categories