Using a directive to add content to areas outside ng-view - angularjs

I'm attempting to port an existing Ruby on Rails frontend to Angular. So far I've managed to get a single page app in place that switches out the content of ng-view depending on your angular route. This is great, however - in my RoR layout I have several defined areas where content can be placed, all of these are contextual to the main view. For example: Sidebar and Heading.
In RoR I can do the following from within an action view to set sidebar content.
<p>Product page content</p>
<% content_for :sidebar do %>
<% render :partial => 'product_sidebar' %>
<% end %>
I am struggling to determine the best method to achieve this in Angular. I've got it working with two methods:
SidebarUrl added to route definitions, route change event updates a scope variable which an ng-include directive uses in the layout.
Custom directive that is served with the template loaded into ng-view, e.g.
<p>Main content for the view</p>
<sidebar>
Content for sidebar
</sidebar>
The directive basically copies its innerHTML to the sidebar element in the main layout and then removes itself. It could be written to place the content into a target element defined by an attribute to make it more generic and reusable.
This way is more natural to me as the result is closest to the Ruby on Rails way but I'm not sure if its a decent solution or something that I will run into problems with later on (I'm very new to Angular).
Any thoughts or suggestions welcome.
UPDATE 18/06
I've found the Angular UI Router project: https://github.com/angular-ui/ui-router which seems to cover my requirements in an official way. Leaning towards that as a solution at the moment.

Try this (not tested).
You can have another element outside of your <div ng-view> element: say, <div id="sidebar" ng-controller="sidebarContr">. Since you can inject as many dependencies as you like in to sidebarContr, you can use $location as one of its parameters and check the location there and add properties to $scope to make the div display what you need.
For example:
in your controllers JS:
(angular
.module('app.controllers', ['ng'])
.controller('sidebarContr', [
/******/ '$scope', '$location',
function ($scope, $location) {
if ($location.path() === '/') {
$scope.file = 'include_0.html';
else {
$scope.file = 'include_1.html';
}
}
])
);
in your HTML:
<div id="sidebar" ng-controller='sidebarContr'>
<div ng-include src='file'></div>
</div>
EDIT: The Angular-UI Router component that you mention seems to be what you need, it looks more powerful. But my solution can be useful when you just need something simple and do not want to have one more dependency.

Related

Open multiple instances of same partial view and controller in angularJS

I am developing single page application using partial views and angularJS.
I am having a scenario where I have to open same partial view multiple times with different data.
I tried to make controller name dynamic by adding app.directive and changing controller name both in js and html and then calls Angular-Complies method, but the issue is that it changes it for all previous tabs opened.
Then I tried to make multiple js files and changing controller name in partial view with Jquery but still does not helps. Here is my code.
HTML:
<div ng-app="myApp">
<!-- this is in view1.html -->
<div ng-controller="DashboardDesignerController">
<div ng-repeat="widget in workspace.widgets">stuff here
</div>
</div>
<!-- this is in view1.html -->
<div ng-controller="DashboardDesignerController">
<div ng-repeat="widget in workspace.widgets">stuff here
</div>
</div>
//Angular controller is in separate JS file.
app.controller("DashboardDesignerController" , function ($scope, $http,
$rootScope, $compile, $injector, $timeout) {
}
I expect to behave every tab separately according to its data but currently when I go to first opened tab, it's scope changed with the last opened tab.
Is there built in way of angularJS to doing it, Or some good approach will also appreciated.
Actually the issue was, I was calling chart drill down method in core JavaScript which was replacing $scope with last opened.
Resolved the issue by getting scope with JQuery
var scope= angular.element($('.k-state-active').find('.tempDiv')[0]).scope();
and called angular function with this scope variable.

Angular looping through items in controller

I have a unique scenario where I would like to be able to do angular ng-repeat type logic in the controller. I am using a slidebar on mobile sized screens, which requires the div that makes up the navigation to sit outside the containing div of the website.
This logic is inside of my index.ejs express file:
<body>
<div ng-view></div>
<div class="sb-slidebar sb-left"></div>
</body>
My standard desktop size sidebar does typical ng-repeat logic:
<li ng-repeat="items in service">{{item.title}}</li>
Since the slidebar logic is in the core index.ejs express file on the server side, I don't have access to the angular view logic. My solution at the moment is to do logic in the controller based on the url:
$scope.$on('$viewContentLoaded', function () {
var index = window.location.pathname.split('/')[1];
if(index == 'services'){
$(".sb-left ul").html("<li>service categories<li>");
}
}
This approach works; however, I'd the inner html to be updated dynamically, similar to the template tag instead of being hardcoded like it is now.

What's the best way for an angular controller to alter an ancestor HTML element?

I have an Angular controller which lives in a portion of the page however that application needs to get access to an HTML element that lives above it.
The best way to imagine the problem is that you have an embedded video which wants to request to be made full view-port:
<html ng-app="videoApp">
<body>
<div>
Other stuff that doesn't relate to the video player...
</div>
<div ng-controller="videoCtrl">
... stuff relating to the video controller
</div>
</body>
</html>
The exact problem is that the videoCtrl needs to be able to add a class to the body class such that it can switch the page layout to being full-page and dominated by the video.
Desired outcome, status of video app adds a "full-page" class to body tag:
<body ng-class="video.fullPage ? 'full-page', : ''">
What's would be the correct way for the video to add a class to an ancestor tag?
Under normal circumstances where the element we want to manipulate lies inside the controller that's doing the manipulation we can bind elements to variables in the scope:
<body> <!-- the videoCtrl scope is not available to <body> -->
<div ng-controller="videoCtrl">
<div ng-class="video.fullPage ? 'fullPage' : ''"></div>
</div>
</body>
However the body tag is not contained within the scope of the video controller and so has no access to the variables in its scope so we can't bind to them.
We could always reach out directly and change the class on the body using dom manipulation but that's not very Angular. What is the correct pattern for the video controller to alter the class of the body tag?
To meet a very similar requirement, I used Angular events. From the controller, $broadcast an event on $rootScope. Then have some sort of screen layout controller handle the event and toggle the possible screen configurations.
So:
child controller:
$rootScope.$broadcast('layout-action', { configuration: 'video' });
layout controller:
$scope.$on('layout-action', function(event, args) {
if (args.configuration == 'video') {
$scope.showVideo = true;
}
});
html:
<body ng-class="{'full-page': showVideo}">
*Note: this does slightly tie the child functionality to another part of the layout. However, perhaps try to generalize the video layout. For example, maybe you want a full screen mode instead.
If ng-app="videoApp" is the only parent, you could also do something on the button that would trigger the full-page class like this:
<body ng-class="{'full-page': showVideo}">
<div ng-controller="videoCtrl">
<button ng-click="$parent.showVideo = !$parent.showVideo">I'm in the video controller!</button>
</div>
</body>
In this case i'm just toggling showVideo to true or false. But like Davin said, this may not be as nice because it depends more on where it sits in the app.
See this plunkr for an example http://plnkr.co/edit/UxtA0YvCUckofAflsy9G

get ng-click, on injected element, to change the class of an element already in the view?

I have a <ul> that gets populated with the server. But in that controller there is also an iframe. When the <li>'s arrive there is some disconnect between them and the iframe even though they are in the same controller.
When you click one of the li's it should change the class on the iframe but it's not. However, If I move the iframe inside of the ng-repeat that injects the iframe it works.
View
<div class="content" ng-controller="FeedListCtrl">
<ul>
<li ng-repeat="item in items">
<div data-link="{{item.link}}" ng-click="articleShowHide='fade-in'">
<div ng-bind-html="item.title" style="font-weight:bold;"></div>
<div ng-bind-html="item.description"></div>
<!-- it works if i put the iframe here -->
</div>
</li>
</ul>
<!-- doesn't work when the iframe is here -->
<iframe id="article" ng-class="articleShowHide" src=""></iframe>
</div>
Here is the controller. It does an ajax call to get the data for each <li>
Controller
readerApp.controller('FeedListCtrl', ["$scope", "$http", "FeedListUpdate", function ($scope, $http, FeedListUpdate) {
$scope.setFeed = function (url) {
$http.get('feed?id=' + FeedListUpdate.GetCurrentFeedUrl()).success(function (data) {
$scope.items = data.currentFeed.items;
});
};
}]);
When inside of an ng-repeat you are in a different scope which means you are not setting the variable you think you are. Use $parent and that should work. The syntax is:
<div data-link="{{item.link}}" ng-click="$parent.articleShowHide='fade-in'">
Side note for others finding this - sometimes adding curly brackets helps as well. For more information on ng-class see here: http://docs.angularjs.org/api/ng/directive/ngClass
An Example
In case anyone wants to see this in action, I put together an example demonstrating a few ways to set the class as well as demonstrating the issue in scope (See: http://plnkr.co/edit/8gyZGzESWyi2aCL4mC9A?p=preview). It isn't very pretty but it should be pretty clear what is going on. By the way, the reason that methods work in this example is that the scope doesn't automatically redefine them the way it does variables so it is calling the method in the root scope rather than setting a variable in the repeater scope.
Best of luck!

Master Page Concept in AngularJS?

I would like to create master page, that is main page which will be used in all views of the application.
For example, Left navigation and top men navigation. This navigation should be displayed in all the views, whenever url changes in application.
As per ng-view, it only renders given partial view and replace previous view. In the image above all my left and top navigation should be displayed by using angular Controller.
Controller code
angular.module('modelDemo').controller("authCtrl", ['$scope', function($scope) {
$scope.list;
}]);
Please let me know, how can i achieve this
You can use angular-route or Angular-ui-router, and setting your master, following this steps:
Step 1. Make your index.html your master page.
Step 2. Add the <header>, <footer>, <aside>, <div>, etc. referencing your templates by using ng-include
NOTE: your left and top navigation will be part of it
Step 3. The content of the view will be rendered using the directive attribute ng-view or ui-view
Step 4. Use your module app.config() to configure the children pages
Source:
using Angular Route: https://docs.angularjs.org/tutorial/step_07
template for a brand-new app: https://github.com/angular/angular-seed
using Angular UI Router: Angular Tutorial 30 mins
ng view should be able to do that just fine. Keep your top navigation / left navigation html intact and use ng view for the various display area. http://docs.angularjs.org/api/ng.directive:ngView
To use the controller from the top navigation inside ng-view you can use $parent to get access to that scope : https://stackoverflow.com/a/14700326/390330
Fiddle for parent scope : http://jsfiddle.net/ezhrw/2/
<button ng:click="$parent.letter = greek">Assignment expression {{ greek }}</button>
I was trying to create the same concept, but needed a way to define placeholders. I started experimenting in Plnkr.co and thus far, I resorted to using a LayoutManager that drives itself from settings within the routeProvider object.
Here is an example: http://embed.plnkr.co/4GPDfTSQCuqukJE7AniZ/
You'll see an example of how multiple routes use the same header and footer, I did not include an example with a sidebar.
Let me explain the LayoutManager.
I wanted to have placeholders that could be overridden. In this example, I have a toolbar that contains a title and provides a space to the right of the title for additional toolbar items. This gives views an opportunity to throw in additional functionality.
All of this is driven by the LayoutManager. The LayoutManager is a service that reads layout properties set on the $routeProvider. I wanted to implement this in a way keep things clean and self contained, per route. The LayoutManager is injected into the toolbar directive. The toolbar directive drives it's scope properties of the LayoutManager.
In turn, the LayoutManager has a dependency on the routeProvider as well as the rootScope $routeChange event.
I'm very new to Angular, open to suggestions.
I could not see any problem, if you are using bootstrap then use can easily divide your screen as you want
<div class="row">
<div class="col-lg-3">
Left panel
</div>
<div class="col-lg-9" style="border:1px solid #999; overflow-y:auto">
<div> Top Banner </div>
<!-- Main view to render all the page -->
<div ui-view> </div>
</div>
</div>
If you want complete demo and code on then see this link
Edited: 3 Nov. 2016:
If you are using ui-router then we can user abstract state to create different master pages.
You don't need to play show/hide, ng-if but just define the routing properly with you master pages and child pages
Better to see the detail
I know this is an old thread, but thought it should be noted that as of Angular 1.5+ we have been introduced to components. Instead of dealing with routes with named views and all that nonsense or using ngInclude you should be using a header component and footer component. Simply add these to your index.html (or whatever you call your master html template) and voila.
For example (this is using Angular Material and is missing the layout module but hopefully you get the point)
1. Added to index.html
<layout-header></layout-header>
2. header.component.js (you don't need all of this but I think it's helpful)
(function () {
'use strict';
angular
.module('layout')
.component('layoutHeader', {
templateUrl: 'layout/header.html',
bindings: {},
controller: Controller
});
Controller.$inject = [];
function Controller() {
var ctrl = this;
initialize();
////////////////////
function initialize(){
}
}
}());
3. header.html
<md-toolbar>
<div class="md-toolbar-tools">
<h2>
<span>Really Awesome Title!!!!</span>
</h2>
<span flex></span>
<md-button class="md-icon-button" aria-label="More">
<md-icon class="material-icons">more_vert</md-icon>
</md-button>
</div>
</md-toolbar>

Resources