I'm implementing on-the-fly dashboards in AngularJS - angularjs

Can I implement something like:
$scope.showDashboard = function () {
$scope.dashboardPath = "/Widgets/Weather/View.htm";
$scope.widgetController = 30;
require(['/Widgets/Weather/Controller.js'], function (w) {
whatShouldIputHere = w;
});
};
<div ng-include src="dashboardPath" ng-controller="whatShouldIputHere?"></div>
Is it possible to assign a controller to ng-include dynamically?
There could be many widgets on the dashboard

There is a project in development that implements dashboard functionality with AngularJS.
Features:
Adding/removing widgets
Widgets drag and drop
Any directive can be a widget
Running Demo http://nickholub.github.io/angular-dashboard-app
Demo source code https://github.com/nickholub/angular-dashboard-app
Dashboard directive itself https://github.com/nickholub/angular-ui-dashboard

We created an angularjs based dashboard in the open source hawtio project. You can noodle the code here if you like:
https://github.com/hawtio/hawtio/tree/master/hawtio-web/src/main/webapp/app/dashboard
For each widget on the dashboard we compile the partial directly with a child scope
https://github.com/hawtio/hawtio/blob/master/hawtio-web/src/main/webapp/app/dashboard/js/dashboard.ts#L142
Though we had to patch angularjs to allow us to use custom injection on child scopes. e.g. so that we can use a different implementation of $location for each child widget (so it thinks its on its own real URL etc). Hopefully when custom injectors are supported we can move to that.

Instead of using dynamic controllers why not use a single controller(the one which has the showDashboard method). Adding dynamic controller with ng-include will result in nested controllers which is illegal i guess. And instead of using ng-include as an attribute use it as an element.
<ng-include src="dashboardPath"></ng-include>

Related

AngularJS setting the same ngapp more than once

The problem I am having is I need the root element as an anchor tag, then need a div under the anchor tag. They will both be using the same angular controller which belongs to the same app. The databind on vm.Open works find inside the anchor tag, but it is not working inside the div tag. How can I have the div tag also bootstrap as 'app' with the controller 'ordercontrol'?
Right now I have :
My HTML
<a data-ng-app="app" data-ng-controller="ordercontrol as vm" href="#" data-ng-click="vm.Open = !vm.Open">{{vm.Open}}</a>
<div data-ng-app="app" data-ng-controller="ordercontrol as vm" id="QuickOrderDiv">
<div class="row">
{{vm.Open}} //showing as '{{vm.Open}}' inside page
</div>
</div>
Per AngularJS documentation, only one application can be bootstrapped per HTML document. There is probably not a good reason for you to try and declare and use multiple apps in one document. Depending on your intention, there are a few ways to proceed.
First, remember: assigning a controller to an element (using the ng-controller directive) creates a new scope that inherits from the parent. All elements, directives, and additional controllers used within that scope can use that controller to share functionality. So in essence, a controller is used to centralize models and application logic that is specific to an application and usually to a view (think of any functional task in your application, such as an order form or log in page; those are serviced by controllers).
If you want to reuse a behavior multiple times throughout your application, and that behavior is not specific to a particular view in your application (think of a component, like a date picker, or perhaps a shopping cart status icon/link) you may wish to encapsulate your logic in a directive.
Now, there is some overlap (for example, directives can have a scope or controller of their own), and it may be confusing when to use one or the other. As I mentioned above, controllers are primarily intended to contain business logic for a view in your application. Directives are more orthogonal, encapsulated templates and logic that a) you can easily reuse across the views in your application, and b) extend existing HTML elements with richer mark-up and programmed behaviors. You can use a service (which is a single-instance object) to coordinate data between controllers and directives.
Another common issue new developers struggle with is maintaining or inheriting different states independently, considering that you can only have one ng-view element per document. In that case, consider ui-router.
My wild guess for your case, you may want some sort of QuickOrder directive that binds to a value on OrderController to determine whether or not it should display, and contains additional template mark-up for displaying the order or whatever and the logic to manage it.

Best way to modify html before binding to it in AngularJS directive

I'm writing an AngularJS app that gets a list of posts from a server, and then uses an ngRepeat and a custom post directive to output all the posts.
Part of the post object is a blob of html, which I currently add to the directive by first doing an $sce.trustAsHtml(blob), and then using the ng-bind-html directive and passing the trusted html blob to it. It works fine, but now I want to modify the html before adding it to the output. For instance, I want to find all link tags and add a target="_blank" to it. I also want to remove any content editable attributes from any element. etc.
What is the best way of doing this? I was thinking of just loading it up in a document fragment and then recursively iterating through all of the children doing what I need to do. But I assume there is a better AngularJS way to do this?
EDIT:
here is a codepen with an example of what I have:
http://codepen.io/niltz/pen/neqlC?editors=101
You can create a filter and pipe (|) your content through it. Something like:
<p ng-bind="myblob | myCleanupFilter">
Your myCleanupFilter would look something like this (not tested):
angular.module('myApp').filter('myCleanupFilter', function () {
return function cleanup (content) {
content.replace('......') // write your cleanup logic here...
};
});
If you want to add attributes that are themselves directives, then the best place to add them is in the compile function in a custom directive.
If they are just plain old attributes, then there's nothing wrong with hooking into DOM ready in your run block, and adding your attributes with jquery.
var app = app.module('app',[]);
app.run(function ($rootScope){
$(document).ready(function()
$rootScope.$apply(function(){
$('a').attr('title','cool');
});
})
});
If you want add the attributes after the compile phase but before the linking phase in the angular life cycle then a good place to do it is in the controller function for a directive that's placed on the body element.
<body ng-controller="bodyCtrl">
</body>
app.controller('bodyCtrl', function($element){
$('a', $element).attr('title','cool');
});
During the compile phase angular will walk the DOM tree, matching elements to directives, and transforming the HTML along the way. During the link phase, directives will typically set up watch handlers to update the view when the model changes. By placing a directive on the body element, it ensures that all directives have been compiled, but the linking phase hasn't started yet.

How to use angularjs with greasemonkey to modify web pages?

I want to modify web pages' behavior using angularjs and greasemonkey. I want to know, what's the best way to do it? Should I use jquery to inject attributes like "ng-*" to DOM elements before I can write some angular code? Or can I solely stick to angularjs?
Thanks.
There's a general answer about dynamically modifying AngularJS content in the DOM from JavaScript code here:
AngularJS + JQuery : How to get dynamic content working in angularjs
To sum up, when you put ng-* attributes into the DOM from JavaScript code, they won't automatically get hooked up; but AngularJS provides the $compile function for hooking up new HTML content with AngularJS attributes from JavaScript.
So what does this mean when it comes to Greasemonkey/Userscript?
For the purposes of this I'm assuming that your Greasemonkey script is modifying an existing page that already uses AngularJS, and the AngularJS content you want to add uses some of the variables or functions in AngularJS scopes already on that page.
For those purposes:
Get a reference to $compile from AngularJS' dynamic injection system
Get a reference to the AngularJS scope that you want your HTML code to be connected to
Put your HTML code with ng-* attributes in a string and call $compile on it and the scope.
Take the result of that and put it into the page using the usual jQuery-style ways.
To illustrate, here's a little script for CERN's Particle Clicker game, which adds a stat under the 'workers' section.
$(function () { // Once the page is done loading...
// Using jQuery, get the parts of the page we want to add the AngularJS content to
var mediaList = $('ul.media-list');
var medias = $('li.media', mediaList);
// A string with a fragment of HTML with AngularJS attributes that we want to add.
// w is an existing object in the AngularJS scope of the
// <li class="media"> tags that has properties rate and cost.
var content = '<p>dps/MJTN = <span ng-bind="w.rate / w.cost * 1000000 | number:2"></span></p>';
// Invoke a function through the injector so it gets access to $compile **
angular.element(document).injector().invoke(function($compile) {
angular.forEach(medias, function(media) {
// Get the AngularJS scope we want our fragment to see
var scope = angular.element(media).scope();
// Pass our fragment content to $compile,
// and call the function that $compile returns with the scope.
var compiledContent = $compile(content)(scope);
// Put the output of the compilation in to the page using jQuery
$('p', media).after(compiledContent);
});
});
});
** NB: Like any AngularJS function that uses its dependency injection,
.invoke uses the parameter names of the function you pass to it
determine what to inject, and this will break if you're using a minifier that changes the parameter names.
To avoid this you can replace
.invoke(function($compile) { ... });
with the form
.invoke(['$compile', function($compile) { ... }]);
which won't break if the minifier changes the parameter name to something other than $compile.

Dynamically Loading Controllers and ng-include

At the moment I have an app that has a sidebar, and the sidebar loads different html templates using ng-include based on what operation the user chooses to do. It's a map related app, so for example, if the user selects the 'Add Leg' button it will load the add_leg.html template into the sidebar using ng-include:
// The Javascript code:
$scope.addLeg = function() {
$scope.sidebar = true;
$scope.sidebar_template = '/partials/legs/add_leg.html';
}
// Simplified example of HTML:
<html>
<div ng-app="MyApp" ng-controller="MainCtrl">
<a ng-click="addLeg()">Add Leg</a>
<a ng-click="addRoute()">Add Route</a>
<a ng-click="editLeg()">Edit Leg</a>
<a ng-click="editRoute()">Edit Route</a>
...
<div id="sidebar" ng-class="{'sidebar-hidden': sidebar == false}">
<div ng-include src="sidebar_template"></div>
</div>
<google-map></google-map>
</div>
This is all well and good and works as desired, but at the moment my app is only using one controller (MainCtrl in js/controllers.js) and it's starting to get really cluttered. I've got a lot of methods now because the apps functionality is expanding. I'd like to split my controller up into multiple controllers whilst retaining this sidebar functionality.
I want to have MainCtrl as the main controller that controls the loading of the sidebar template (by changing the sidebar_template variable that points to the file destination), and I want it to control some of the global map related methods (like fetching suburb names from coordinates, etc).
I've tried to split it like so:
controllers/js/main.js - MainCtrl
controllers/js/legs.js - LegCtrl
controllers/js/routes.js - RouteCtrl
I want the LegCtrl and RouteCtrl to inherit the MainCtrl so I can access its scope and methods, that's all fine. But now the problem is how do I dynamically load the controller onto the sidebar div based on what functionality is required. Originally all of my methods were in MainCtrl, and that's on the wrapper div that surrounds the sidebar div (see above again for an example), so it wasn't a problem.
For example, say I press the 'Add Leg' button, it's going to need to call addLeg in LegCtrl, but LegCtrl isn't loaded on the app, so it doesn't have access to the method. I could keep addLeg() inside the MainCtrl, and have it change the sidebar_template variable to load the template, but nothing in the template will work because it is calling methods from inside the LegCtrl now.
I need to somehow dynamically load the controller on the sidebar's ng-include div, something like this perhaps:
// MainCtrl
$scope.addLeg = function() {
$scope.required_controller = LegCtrl;
$scope.sidebar = true;
$scope.sidebar_template = '/partials/legs/add_leg.html';
LegCtrl.addLeg();
}
<div id="sidebar" ng-class="{'sidebar-hidden': sidebar == false}">
<div ng-include src="sidebar_template" ng-controller="{{required_controller}}"></div>
</div>
In the non-working example above you can see a possible solution I've thought of, but I need LegCtrlto be the actual controller function, not an object (for ng-controller to work). I also need some way to call addLeg on the LegCtrl from the MainCtrl.addLeg (perhaps using broadcast?).
If anyone can point me in the right direction that'd be great. Sorry for the huge post, it needed a bit of explaining to make it coherent. Hopefully it makes sense. Thanks.
Update: I think I've found a solution using a service to act as the navigation control (it will load the relevant templates and broadcast an event to the new controller being dynamically loaded to tell it what function to execute):
http://plnkr.co/edit/Tjyn1OiVvToNntPBoH58?p=preview
Just trying to test this idea out now, but the broadcast .on doesn't work. I think it's because the broadcast fires before the template loads. How can I fix this? Is this a good solution for what I'm doing?
If i have understood you correctly what you can try would be to create a template view specifically to create a new leg.
From the main controller implement some logic to show the template
$scope.addLeg=function() {
$scope.showAddLeg=true;
}
The AddLeg template view would load the controller and hence provide a mechanism to actually add new leg. The template would look like
<script type="text/ng-template" class="template" id="addLegTemplate">
<div ng-controller='LegsController'>
<!--html for adding a new leg-->
</div>
</script>
You can include this template inside you main html using ng-if + ng-include.
<div ng-if='showAddLeg'><div ng-include='addLegTemplate'/></div>
Basically you can create multiple view and bind to same controller (but instance would differ). In this case the LegsController can be binded to multiple views to support the complete functionality.
I like this data driven scenario, just manipulate the rows in the templates:
$scope.templates = [
{ name: 'include1.html', url: 'partials/include1.html' }
];
$scope.addTemplate = function(name, url) {
$scope.templates.push({ name: name, url: url });
};
and the markup:
<div ng-repeat="template in templates" ng-include="template.url"></div>
To add or remove views, just modify the templates et voila! If you need more controller code, then you can include a link to the script in the include. I guess there may be complications with binding to data in the partial view itself, but $compile should resolve those.
I've forked your plunkr demo into one that I believe does what you're looking for: http://plnkr.co/edit/whsjBT?p=preview
This demonstrates an event being broadcast from one controller to another AFTER the 2nd controller (LegCtrl in our example here) is loaded via ng-include and passing it data.
I used $includeContentLoaded event from ng-include to delay broadcasting the event until angular reports that add_leg.html is loaded. Also I think there were some issues with how you were using $broadcast()... it's first parameter should be the same as the one used in $on() in LegCtrl
Another idea to consider is simply using your Navigation service itself to share state between the controllers if that's appropriate in your scenario.

Basic behaviour and structure of an angularjs app

I am trying to introduce myself into angularjs.
Although i have worked through the tutorial and watched the basic building videos, i am still struggling with the behaviour and architecture of a more-or-less large scale application.
My application has a menubar that includes an add-button. If the user clicks the button, i want a dialog to pop-up. That dialog is not part of the menu:
<!-- The menu -->
<header class="mod modHeader" ng-controller="HeaderCtrl">
<div class="modHeader__addProject" ng-click="openAddDialog()">
<i class="icon-plus icon-2x"></i>
</div>
</header>
<!-- the dialog -->
<div class="modNewProject" ng-show="properties.AddDialogVisibility" ng-controller="HeaderCtrl">
<!-- content stripped out -->
</div>
My intention was to create a properties object inside the scope of my HeaderCtrl controller, then change a boolean value on click of the said button.
// HeaderCtrl
function HeaderCtrl($scope){
$scope.properties = {
"AddDialogVisibility": false
};
$scope.openAddDialog = function () {
$scope.properties.AddDialogVisibility = true;
};
}
Now, there are multiple issues and questions:
I have to apply HeaderCtrl to my dialog in order to get access to the properties object. This is nasty to me, HeaderCtrl should control only my header module, shouldn't it?
The dialog won't show up on click. I found out that this is because the property gets checked only once, on page load, and that i would have to use a function. What is a proper way to achieve my goal?
Conclusion:
I would say that i can summarize my question to:
I use a Controller for each section of my page. How do they communicate?
In the sample code you provided, two HeaderCtrls will be created. Each use of ng-controller will create a controller.
To share data in Angular, use a service. Inject that service where needed – into controllers, directives, etc.
When designing an Angular app, try to think in terms of models (which are often contained in services, and those services then expose model APIs to the rest of the application) and views. A controller is just the glue that allows us to project the relevant parts of our models into a view.
Dialogs are a special/unique case, since they don't take up a specific place in the rest of the application. Listen to a few minutes of Misko regarding this subject.
I would also recommend looking at how the Angular-UI team implemented dialogs: http://angular-ui.github.io/bootstrap/#dialog
angular structure
app->
assets->
css -> all css file
js -> all js file
partials-> all html files
vender -> third party file (like angular.js , jquery.js)
router.js
services.js
filter.js
directives.js
controllers.js
index.html

Resources