binding variables to compiled elements in angular Js - javascript

I am dynamically creating an element with angular. When the app initally runs using app.run() the element is created and I compile the element, after compiling the element I add an ng-show="active" so I can toggle the visibility of the element and possibly animate a transition later using that directive. The only problem is the compiled element only binds once it looks like to the variable active. Once its set to true it won't hide again. Do I need to use digest or some other method to get it the binding to work properly?
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<script src="lib/angular/angular.min.js"></script>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
<style>
.message-app { width:100px; height:30px; background-color:#005cab; color:white; text-align:center; line-height:30px; margin-bottom:5px; }
.app-console { width:300px; height:30px; border:1px solid red; }
</style>
</head>
<body ng-controller="AppCtrl">
<div class="message-app" val=''>click</div>
<div class="message-app" val='true'>click</div>
</body>
</html>
<script>
var app = angular.module("messageApp", []);
app.run(function($rootScope){
$rootScope.message = angular.element("<div class='app-console' ng-show='active'></div>");
angular.element(document.body).append($rootScope.message);
})
function AppCtrl($scope, $compile, $rootScope){
$scope.active = false;
$compile($rootScope.message)($scope);
$scope.setActive = function(val){
$scope.active = val;
$scope.$apply();
}
}
app.directive("messageApp", function(){
return {
restrict:"C",
controller:"AppCtrl",
scope:{
val:"#"
},
link:function(scope, element, attr){
element.bind("click", function(){
scope.setActive(Boolean(scope.val));
})
}
}
})
angular.element(document).ready(function(){
angular.bootstrap(document.querySelector("html"), ["messageApp"])
})
</script>
To make this simple I didn't put the js in a separate file. Is there something that I am doing wrong with this?

Glossing over the downsides of doing DOM manipulation in .run the issue you're having is related to your directive's isolated scopes.
When you call setActive() from the directive all the scope references inside setActive() are to the calling object's scope (which is the directive's isolate scope). So you're currently manipulating a property active on each directive's isolate scope. What you want, however, is to change active on the appCtrl scope.
To accomplish this I'd get rid of setActive() and do this entirely in your directive with these 3 steps:
1) Add active to your directive's scope:
scope:{
val:"#",
isactive: "="
},
2) Pass active to your directive:
<div class="message-app" val='' isactive="active">click</div>
<div class="message-app" val='true' isactive="active">click</div>
3) Change active directly in your click handler:
element.bind("click", function(){
scope.$apply(function() {
scope.isactive =Boolean(scope.val);
});
});
Now you don't need to worry about having your directive talk to your controller, or about which scope you're on. And you've got a clear interface to your directive.
Here's a working fiddle

Related

Forcing AngularJS directive to link outside digest cycle

This refers to an Angular 1 application.
If the DOM is modified outside the context of my angular application, I know I can use angular.element(document.body).scope().$apply() to force the whole app to re-render, including the newly injected content.
However my directives never seem to link.
So in the example below, the markup <message></message> should render Hello World, but when it is injected manually, then digest applied, the link method never appears to run.
https://jsbin.com/wecevogubu/edit?html,js,console,output
javascript
var app = angular.module('app', [])
app.directive('message', function() {
return {
template: 'Hello, World!',
link: function() {
console.log('message link')
}
}
})
document.getElementById('button').addEventListener('click', function() {
document.getElementById('content').innerHTML = '<message>default content</message>'
var scope = window.angular.element(document.body).scope()
scope.$apply()
})
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
</head>
<body ng-app="app">
inside app:
<message></message>
outside app:
<button id="button">Print another message</button>
<div id="content"></div>
</body>
</html>
According to the docs, you can do this with angular.injector
angular.injector allows you to inject and compile some markup after the application has been bootstrapped
So the code for your example could be:
document.getElementById('button').addEventListener('click', function() {
var $directive = $('<message>default message</message>');
$('#content').append($directive);
angular.element(document.body).injector().invoke(function($compile) {
var scope = angular.element($directive).scope();
$compile($directive)(scope);
});
})
Hope this is what you are looking for!

AngularJS - ng-show not displaying item

So I have this inside my controller:
var myApp = angular.module('app', [], function ($interpolateProvider) {
$interpolateProvider.startSymbol('[%');
$interpolateProvider.endSymbol('%]');
});
myApp.controller('MyController', function ($scope) {
$scope.show = [];
// confirmed this returns true when intended
$scope.showElement = function (id) {
return ($scope.show.indexOf(id) > -1);
};
});
And my HTML structured as below, with my-class using display: none; and other rules which are imperative to the display of this element. Because of the number of instances where it is being used I cannot simply remove the class or alter its rules. In all other instances in the application, this works as expected.
<div class="my-class" ng-show="showElement(obj.id)">
...
</div>
The element is not shown on page load, nor is its appearance updated if the underlying variable $scope.show is changed.
The previous development team was manually (by using deep and obfuscated Javascript) adding an additional CSS class in the other instances where it was being used.
My solution was to add a visible class using the ng-class directive:
<div class="my-class" ng-class="{'visible': showElement(obj.id)}">
....
</div>

AngularJS: Communication between dynamically added controllers

This is my first post/question here on StackOverflow so if you see any improvement I can made - please give me an advice :).
Now let me dive into the issue.
For the sake of simplicity I removed any irrelevant portions of code and presented only necessary files.
//app.modules.js
if (typeof window.app == "undefined") {
window.app = angular.module("AppModule", []);
}
//app.services.js
window.app
.service("settingsOverlaySvc", function($rootScope) {
this.broadcastToggle = function() {
$rootScope.$broadcast("toggle-conf");
};
});
//settings-ctrl.js
window.app.controller("SettingsController", ["$scope", "$window", "$sce", "settingsOverlaySvc",
function($scope, $window, $sce, settingsOverlaySvc) {
$scope.visible = false;
$scope.open = false;
$scope.toggleSettings = function() {
$scope.open = !$scope.open;
};
$scope.broadcastToggle = function() {
settingsOverlaySvc.broadcastToggle();
};
$scope.$on("toggle-conf", function() {
console.log("toggle-conf received");
$scope.visible = !$scope.visible;
});
}
]);
angular.bootstrap($("div[ng-controller='SettingsController']").parent(":not(.ng-scope)"), ["AppModule"]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Appended by JS - control1.html -->
<div>
<div ng-controller="SettingsController" ng-init="visible=true">
<span class="glyphicon glyphicon-cog" ng-class="{'settings-open': open}" ng-click="broadcastToggle();toggleSettings()">COG</span>
</div>
</div>
<!-- Appended by JS - control2.html-->
<div>
<div ng-controller="SettingsController" ng-cloak>
<div ng-if="visible">
<span class="glyphicon glyphicon-cog" ng-class="{'settings-open': open}" ng-click="toggleSettings()">COG</span>
<div ng-if="open">
<div class="divControl_2">Content_2</div>
</div>
</div>
</div>
</div>
The above snippet works as I expected - the $broadcast is called, all controllers receive the message and reacts. In my application the broadcast is received only by controller sending it. I think the problem is caused by dynamic HTML insertions.
The requirement is to render controls dynamically on page load. I'm using the following approach to generate content.
AddWidgets: function () {
var controlContainer1 = $("<section>", {
class: "sidebar-section",
id: "ctrl1-container"
});
var controlContainer2 = $("<section>", {
class: "sidebar-section",
id: "ctrl2-container"
});
$("aside.sidebar").append(controlContainer1);
$("aside.sidebar").append(controlContainer2);
$("#ctrl1-container").load("..\\assets\\ctrls\\control1.html");
$("#ctrl2-container").load("..\\assets\\ctrls\\control2.html");
}
I'm using the same controller because it shares the same logic for all controls.
I've read a lot materials about $broadcast and $emit functionality, guides on creating controllers, defining modules and services (that one gives me idea about creating service with $rootScope injected).
Now I'm thinking that generating angular content outside angular framework (AddWidgets function) can cause the problem (#stackoverflow/a/15676135/6710729).
What raised my spider sense alarm is when I've checked the JSFiddle example for the similar situation (http://jsfiddle.net/XqDxG/2342/) - no parent-child relation of controllers. When I peek at angulat scope of the controllers I can see that $$nextSibling and $$prevSibling properties are filled. In my case these are nulled [here].
Can you give me some guidelines how can I resolve my issue? I'm fairly new to AngularJS and learning as I'm developing the application.

How in one controller get value from another controller?

Have an angular application. Using ui-select.
<div class="col-md-12" ng-controller="FlatController as flat">
<form ng-submit="flat.createFlat()">
<ui-select ng-model="flat.flatData.typelocal" theme="bootstrap">
<ui-select-match placeholder="Type" id="localtype">
{{ $select.selected.type }}
</ui-select-match>
<ui-select-choices repeat="uitypelocal.type as uitypelocal in flat.typelocal1 track by $index | filter: $select.search">
<div ng-bind-html="uitypelocal.type | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<button class="btn btn-danger">Send</button>
</form>
</div>
In form i have some inputs and selects. Now, i want to have in the select data from another controller. How can i do it?
I have a service:
angular.module('flatService', [])
.factory('Flat', function($http){
var flatFactory = {};
flatFactory.allFlats = function(){
return $http.get('/api/flats');
};
flatFactory.create = function(flatData){
return $http.post('/api/addflat', flatData);
};
return flatFactory;
})
});
If you want to call one controller into another there are five methods available
$rootScope.$emit() and $rootScope.$broadcast()
If Second controller is child ,you can use Parent child communication .
Use Services
Kind of hack - with the help of angular.element()
inject '$controller'
1. $rootScope.$emit() and $rootScope.$broadcast()
Controller and its scope can get destroyed,
but the $rootScope remains across the application, that's why we are taking $rootScope because $rootScope is parent of all scopes .
If you are performing communication from parent to child and even child wants to communicate with its siblings, you can use $broadcast
If you are performing communication from child to parent ,no siblings invovled then you can use $rootScope.$emit
HTML
<body ng-app="myApp">
<div ng-controller="ParentCtrl" class="ng-scope">
// ParentCtrl
<div ng-controller="Sibling1" class="ng-scope">
// Sibling first controller
</div>
<div ng-controller="Sibling2" class="ng-scope">
// Sibling Second controller
<div ng-controller="Child" class="ng-scope">
// Child controller
</div>
</div>
</div>
</body>
Angularjs Code
var app = angular.module('myApp',[]);//We will use it throughout the example
app.controller('Child', function($rootScope) {
$rootScope.$emit('childEmit', 'Child calling parent');
$rootScope.$broadcast('siblingAndParent');
});
app.controller('Sibling1', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside Sibling one');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
app.controller('Sibling2', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside Sibling two');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
app.controller('ParentCtrl', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside parent controller');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
In above code console of $emit 'childEmit' will not call inside child siblings and It will call inside only parent, where $broadcast get called inside siblings and parent as well.This is the place where performance come into a action.$emit is preferrable, if you are using child to parent communication because it skips some dirty checks.
2. If Second controller is child, you can use Child Parent communication
Its one of the best method, If you want to do child parent communication where child wants to communicate with immediate parent then it would not need any kind $broadcast or $emit but if you want to do communication from parent to child then you have to use either service or $broadcast
For example HTML:-
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
</div>
</div>
Angularjs
app.controller('ParentCtrl', function($scope) {
$scope.value='Its parent';
});
app.controller('ChildCtrl', function($scope) {
console.log($scope.value);
});
Whenever you are using child to parent communication, Angularjs will search for a variable inside child, If it is not present inside then it will choose to see the values inside parent controller.
3.Use Services
AngularJS supports the concepts of "Seperation of Concerns" using services architecture. Services are javascript functions and are responsible to do a specific tasks only.This makes them an individual entity which is maintainable and testable.Services used to inject using Dependency Injection mecahnism of Angularjs.
Angularjs code:
app.service('communicate',function(){
this.communicateValue='Hello';
});
app.controller('ParentCtrl',function(communicate){//Dependency Injection
console.log(communicate.communicateValue+" Parent World");
});
app.controller('ChildCtrl',function(communicate){//Dependency Injection
console.log(communicate.communicateValue+" Child World");
});
It will give output Hello Child World and Hello Parent World . According to Angular docs of services Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.
4.Kind of hack - with the help of angular.element()
This method gets scope() from the element by its Id / unique class.angular.element() method returns element and scope() gives $scope variable of another variable using $scope variable of one controller inside another is not a good practice.
HTML:-
<div id='parent' ng-controller='ParentCtrl'>{{varParent}}
<span ng-click='getValueFromChild()'>Click to get ValueFormChild</span>
<div id='child' ng-controller='childCtrl'>{{varChild}}
<span ng-click='getValueFromParent()'>Click to get ValueFormParent </span>
</div>
</div>
Angularjs:-
app.controller('ParentCtrl',function($scope){
$scope.varParent="Hello Parent";
$scope.getValueFromChild=function(){
var childScope=angular.element('#child').scope();
console.log(childScope.varChild);
}
});
app.controller('CarentCtrl',function($scope){
$scope.varChild="Hello Child";
$scope.getValueFromParent=function(){
var parentScope=angular.element('#parent').scope();
console.log(parentScope.varParent);
}
});
In above code controllers are showing their own value on Html and when you will click on text you will get values in console accordingly.If you click on parent controllers span, browser will console value of child and viceversa.
5.inject '$controller'
You can inject '$controller' service in your parent controller(MessageCtrl) and then instantiate/inject the child controller(DateCtrl) using:
$scope.childController = $controller('childController', { $scope: $scope.$new() });
Now you can access data from your child controller by calling its methods as it is a service.
Let me know if any issue.
The view's scope is determine by it's controller. However, angular allows nested view scope inheritance, meaning that a view nested inside another view has access to the parent view's scope.. for example:
<div ng-controller="OuterCtrl">
<div ng-controller="InnerCtrl">
...
</div>
<div>
the inner div would have access to the InnerCtrl's scope as well as the OuterCtrl's scope, but the outer div would only have access to the OuterCtrl's scope.
If you need data shared between non-related controllers (those that are not related by nested views), then the data should be provided by a service that can be injected into the controllers like:
app.factory('service', function() {
...
return {
data: someData
};
});
app.controller('ctrl1', [... function(..., service) {
$scope.data = service.data;
}]);
app.controller('ctrl2', [... function(..., service) {
$scope.data = service.data;
}]);
use $broadcase or $emit event like:
app.controller('ctrl1', ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.$on("CustomEvent", function($event, data){
alert(data);
})
}]);
app.controller('ctrl2', ['$scope', function($scope) {
$scope.$emit("CustomEvent", "SecondControllerValue");
}]);

Is it possible to reference Angular custom directive inside itself?

Looking to implement folder hierarchy in Angular:
I'm implementing this via custom directive that references itself inside its template.
Currently it's going into infinite loop with this setup:
<!-- index.html -->
<subfolders folder="default_folder"></subfolders>
This is the <subfolders> directive:
//subfoldersDirective.js
angular.module('app').directive('subfolders', subfolders);
function subfolders() {
var directive = {
restrict: 'AE',
scope: {
folder: '=',
},
templateUrl: '/pathto/subfoldersDirective.html',
controller: DirCtrl,
controllerAs: 'vm'
};
return directive;
function DirCtrl($scope) {
var vm = this;
vm.folder = $scope.folder;
}
}
and its template:
<!-- folderDirective.html -->
<div ng-repeat="folder in vm.folder.subfolders">
{{ folder.name }}
<button ng-click="folder.is_open = true">Open folder</button>
<div ng-if="folder.is_open">
<!-- This is the problem line -->
<subfolders folder="folder"></subfolders>
</div>
</div>
In the template, <subfolders> should only get rendered after the button is clicked which triggers ng-if="folder.is_open". I guess Angular does not know this when it compiles the directive. It goes into infinite loop, even though it technically should not.
Is there a way to make it work with the directive? The logic is a bit more complex in the real app, this is why I'm looking to make it work with the directive.
I'm currently using Angular 1.2.26.
You can do this, but you need to override the compile behavior of the directive. You need to remove contents of the directive element during the compilation step, and then compile and reattach it during the post-link step. I have used the compile function below with great success:
function compile(element) {
var contents = element.contents().remove();
var contentsLinker;
return function (scope, iElement) {
if (angular.isUndefined(contentsLinker)) {
contentsLinker = $compile(contents);
}
contentsLinker(scope, function (clonedElement) {
iElement.append(clonedElement);
});
};
}
I based this function on this post, which is probably more comprehensive if you need a pre-link function.

Categories

Resources