Embedding AngularJS in existing jquery/bootstrap based website - javascript

We've got an existing application where the client-side is jQuery / Bootstrap. It consists of many tabs where each tab is defined in a module imported via. require.js. The tab javascript is handed a parent DOM element and is in charge of drawing itself inside of that element.
We'd like to start building new functionality (tabs) in AngularJS and running into some problems doing that.
My thinking is that we could tag the body with ng-app and in the main page code conjur up an app module window.app = angular.module('ourApp', []); and later, as tabs are loaded, create and wire-up the controllers.
I've built a simple single-page example that exhibits the problem we are having (below or here http://jsfiddle.net/p4v3G/1/).
The only way I've been able to get the example to work is manually calling angular.bootstrap which I'm pretty sure is wrong. Also, that only works the first time so if I click the button twice (equivalent to navigating to the tab, away from it, and back again within our app), Angular isn't wired up properly.
I'd appreciate any help.
<body ng-app='testApp'>
<div id="main" style="border: 1px solid #000; background: #ffdddd;">Click button to replace template and wire up controller...</div>
<button id="button1">Load</button>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.14/angular.min.js"></script>
<script>
var app = angular.module('testApp', []);
jQuery(function() {
$("button").click(function() {
// controllers are wired up in click handler to simulate environment where we
// are looking to embed angular inside of an existing bootstrap/jquery application
// where new tabs (loaded as separate modules through require) are loaded on-demand.
app.controller('TestController', function($scope) {
$scope.message = 'Hello World, from Controller #1';
});
$("#main").html('<div ng-controller="TestController">{{message}}</div>');
// Bootstrap works the first click but not subsequent clicks
angular.bootstrap(document, ['testApp']);
});
});
</script>
</body>

To chunk up your appliation so that only the relevant parts are instantiated etc. what you need is angular ui-router. You would then set up a parent state for your tab-control with child states for each of your tabs. That way you both get deep linking and the performance you want with loading only the relevant tab.
As for requirejs, I encourage you to firstly consider if you really need it. In my opinion the javascript making up an angular application is usually much terser than a jquery application due to the declarative nature of the technology. Therefore loading all of the javascript at boot-time is ok. Your templates however may not be as simple, but by using templateUri references to templates they may be loaded as needed. (Personally I prefer compiling them to javascript and placing them in the $templateCahce at the cost of boot-time, but that's another matter.)
That being said, if my observations do not hold for your scenario/application/codebase, then others have had great success incorporating requirejs with angularjs. For a nice introductory talk on that subject see this nice ng-conf video.
Best of luck!

Could you be more precise, what type of errors appears.
You don't need use jquery. Check this code and compare
http://jsfiddle.net/pokaxperia/3w6pb/1/
HTML
<body ng-app='testApp'>
<div ng-controller="TestController">
<span id="main" style="border: 1px solid #000; background: #ffdddd;">{{message}}</span>
<button ng-click="loadMessage();" id="button1">Load</button>
</div>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
</body>
script
var app = angular.module('testApp', []);
app.controller('TestController', ['$scope',function($scope) {
$scope.message = "Click button to replace template and wire up controller...";
$scope.loadMessage = function(){
$scope.message = 'Hello World, from Controller #1';
};
}]);
Or check your code on jsfiddle, but with few variants
http://plnkr.co/edit/fUQDpO?p=preview
HTML
<body>
<example-tabs></example-tabs>
<div class="panel" ng-show="isSelected(1)">Panel One</div>
<div class="panel" ng-show="isSelected(2)">Panel Two</div>
<div class="panel" ng-show="isSelected(3)">Panel Three</div>
</body>
Main script:
var app = angular.module('tabsExample', ['tabDirectives']);
Directive to load Tabs
var app = angular.module('tabDirectives', []);
app.directive('exampleTabs', [
function() {
return {
restrict: 'E',
templateUrl: 'example-tabs.html',
controller: function($scope) {
$scope.tab = 1;
$scope.selectedTab = function(setTab) {
$scope.tab = setTab;
};
$scope.isSelected = function(checkTab) {
return $scope.tab === checkTab;
};
}
};
}
]);

Related

My page turns blank when I try to use a controller in angular

I have a problem with angular to integrate a controller to my page. The page becomes blank as soon as I try to integrate it.
<div class="container-fluid" ng-app="ods-widgets" ng-controller="myCtrl" >
<ods-dataset-context context="cont" cont-domain="https://data.rennesmetropole.fr" cont-dataset="{{dataset}}">
</ods-dataset-context>
</div>
<script>
var app = angular.module("ods-widgets", []);
app.controller("myCtrl", function($scope) {
$scope.dataset= "statistiques-de-frequentation-du-site-rennes-metropole-en-acces-libre";
});
</script>
Without the controller:
http://jsfiddle.net/5c0xr8f4/13/
With the controller:
http://jsfiddle.net/8796ueyL/
ods-dataset-context is a component (https://github.com/opendatasoft/ods-widgets).
it's a component that I import via CDN.
I just want to control what is inside the cont-dataset
I looked into the library that you mentioned in your comment. It seems that the issue is that ods-widgets is already an angular module that is being imported via the CDN. If you name your own angular module with the same name, you are effectively overwriting this existing module that you have imported. So what you want to do is declare your own angular module and import ods-widgets as a dependency. You can take a look at the Fiddle for a working sample, but the important part is this one:
angular.module("myApp", ['ods-widgets']);
And in your HTML update the ng-app reference:
<div class="container-fluid" ng-app="myApp" ng-controller="myCtrl" >

How can I create an array from the value of the same directive on different pages in Angular JS?

I am using Angular 1.5.7 and am trying to see if I can push the value of an attribute within a directive used on several different pages to an array that lives in the controller.
I am pretty sure that I need to used transclusion in order to do this but I am stuck. Below is a simplified version of what I have so far:
about.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="About">
<div>About Page</div>
</div>
</div>
contact.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="Contact">
<div>Contact Page</div>
</div>
</div>
header.html
<div>{{mypage}}
<div ng-transclude></div>
</div>
cd-header.js
var cdHeader = function() {
return {
scope: {
mypage: "#"
},
transclude: true,
templateUrl: 'header.html',
link: function(scope) {
// Not sure but I think I might need a function here
}
}
}
module.exports = cdHeader;
MainCtrl.js
var MainCtrl = function($scope) {
var nav = [];
// Not sure how items that are pushed to the nav get to this point
}
module.exports = MainCtrl;
main.js
var app = angular.module("myapp", [
'about',
'contact',
])
.controller('MainCtrl', MainCtrl)
.directive('cdHeader', cdHeader)
I am able to get the value of the mypage attribute in the directive as well as its transcluded <div> to appear in the header but only for the current page in view.
The part I am missing is how to get all of the mypage values from each page into the header regardless of the current page in view. I am somewhat new to Angular and have read a lot but have not come across anything that explains how this can be done. Maybe this is achieved with a service? If so, I'm not sure how to go about it.
To clarify with a visual. This is what I see:
But this is what I want to see:
As you rightly pointed out, there are several ways to do it.
Perhaps you can pass the array from the MainCtrl as an attribute to the directive. For instance, nav-array="nav". However, before that, you need to set the array nav as such
var $scope = this;
this.nav = [];
The second option is to consume a service. Create an angular service, pass it as a dependency in the directive, and consume it.
Lets create an array in MainCtrl as $scope.headers = ['about':'About','contact':'Contact','home':'Home'] or create a factory/service to share the headers data and in header.html use ng-repeat to display the header name according to myPage value like below
<div ng-repeat="page in headers[myPage]">{{page}}
<div ng-transclude></div>
</div>

Change AngularJS url/route from html - javascript code

Say I have a button that's within the ng-app scope, but outside the ng-controller/ng-view scope.
<html ng-app="myApp">
<!-- ... -->
<body>
<button id="myButton">Click me!</button>
<div ng-view> <!-- angular-view --> </div>
</body>
</html>
And I want to change the URL/Route when I click the button.
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
//code that changes path.
});
How do I do that? If I try window.location.href = '/'; the whole page gets reloaded, not just the view. $location isn't visible from html javascript, so that's not an option neitehr.
If anyone knows a way I can change the path/route/url from javascript, I'd be more than happy to accept any valid answer, or a redirect to some helpful documentary post, or a rederect to another SO question, because I'm trying to find an answer to this for a while and I've got nothing...
I'll note that I tried making a global scope function, but that didn't work, neither.
angular.module('myApp', [])
.config( ... )
.run(function($rootScope, $location) {
$rootScope.setUrl = function(url) {
$location.path(url);
};
})
.controller(
.....
and called with setUrl('/');, $root.setUrl('/'); and $rootScope.setUrl('/');, but still nothing ('undefined').
note: the button script is just an example, the real purpose why I'm asking this is a little bit larger, so I'm trying to simplify.
If your purpose is to change route from outside of the Angular app, then you need to change location hash part:
window.location.hash = 'new-route';
Inside Angular controller use $location.path method.

Stop Angular routing links inside ng-view

I'm using Angular JS to dynamically load content like so:
HTML
...
<div ng-controller="MainController">
<div ng-view></div>
</div>
</html>
Angular
(function(){
var app = angular.module('app', ['ngRoute', 'ngAnimate']);
app.config(function($routeProvider) {
$routeProvider.when('/test', {
templateUrl : 'views/test.html',
controller : 'MainController'
});
});
app.controller('MainController', function($scope){ });
})();
This works as expected. However, inside the file test.html I have some links with the href="#" that need to be handled with javascript to do various things. At the moment, Angular is interpreting them with it's routing method and treats them as links to the homepage. How do I stop this and treat the links the way I want?
Example test.html content:
Left
Right
<p>Test content</p>
In a JS file separate from Angular I tried:
$('.slideLeft').on('click',function(){
return false;
});
But it doesn't do the return false, it uses the Angular routing.
You should be using Angular for all your bindings including event bindings. Don't use .on('click'), use ng-click (or .bind if you really need it, but you probably don't).
You can also see from the docs that the <a> directive does nothing if href is empty. Use href="" rather than href="#"
Use href="javascript:void(0)" in anchor attribute and also you should use ngClick instead of binding element using jQuery.

Using Waypoints.js inside an AngularJS view

I am trying to use Waypoints inside a scrollable AngularJS view, however it's not working. I am trying to use ui.utils jQuery Passthrough but nothing happens. Here's what I have so far:
<body>
<div id="wrapper">
<div id="nav-menu">
<...>
</div>
<div id="main" class="main">
<div ng-view></div>
</div>
</div>
</body>
I am using the following template for the view:
<div class="fullScreenImage"></div>
<div ui-jq="waypoint" ui-options="test">test</div>
and my controller looks something like this:
app.controller('myController', ['$scope',
function ($scope) {
$scope.test = function(){
alert('You have scrolled to an entry.');
}}]);
My main element is scrollable but the window is not. Setting passthrough to the main div will trigger the test function, however I need it inside the template. Any suggestions?
Providing a JSFiddle or plunkr would be helpful.
But I suppose the issue is because the template is inside ngView.
As the content of the template is placed inside the ngView via XHR, the waypoints needs to be refreshed as discussed in the link http://imakewebthings.com/jquery-waypoints/#doc-refresh
An Angular directive will be more effective IMHO.
I had trouble with waypoints nested in ng-views as well.
My solution was just to wait till after the view had loaded to bind the waypoints, for this you can use $viewContentLoaded.
E.g.
myApp.run(['$rootScope', function($rootScope) {
$rootScope.$on('$viewContentLoaded', function(){
var waypoint = new Waypoint({
element: document.getElementById('waypoint'),
handler: function(direction) {
console.log('Scrolled to waypoint!')
}
})
});
}]);
I had the same problem. It's also possible to delay the binding with $timeout e.g $timeout(addWaypoints());.

Categories

Resources