How to instantiate ng-controller based on a condition - javascript

I asked this question but the specific question I'm asking has changed dramatically.
I have a piece of code:
<div ng-attr-controller="{{pings || 'PingsCtrl as pings' }}">
<h1 ng-click="pings.press()">asdf</h1>
</div>
This code is injected into two html pages. One page already calls PingsCtrl. The other doesn't. I'm really trying to keep this code DRY and I only want to have one reference of the code above.
How can I write the code above to generate ng-controller if PingsCtrl hasn't already instantiated.
Here are the two html pages.
HTML
// First page
<html ng-app="coolApp">
<div ng-controller="PingsCtrl as pings">
<div ng-attr-controller="{{pings || 'PingsCtrl as pings' }}">
<h1 ng-click="pings.press()">asdf</h1>
</div>
</div>
</html>
// Second page
<html ng-app="coolApp">
<div ng-attr-controller="{{pings || 'PingsCtrl as pings' }}">
<h1 ng-click="pings.press()">asdf</h1>
</div>
</html>
Javascript is here:
angular.module('coolApp', [])
.controller('PingsCtrl', function() {
var vm = this;
vm.press = function() {alert(123)};
})
What's wrong and how do I fix this?

Just use a service. It's really the intended structure for having common data and functionality between pages.
Part of the problem with what you were attempting is, whether or not you manage to preserve the controller, Angular has its own management that won't follow you with that, and will be refreshing components without you. You'll run into things like a $scope that doesn't actually match the page you're looking at, and it ends up causing more problems than it's worth.

I do have a solution but I also echo other people's concerns about the approach. You may want to have a global controller that you drop on the body for things that can happen anywhere and in most of the other controllers and just call through that. Eg
<body ng-controller="GlobalCtrl as gc">
<h1 ng-click="gc.pingPress()"></h1>
</body>
Anyway here is what I came up with.
<div ng-if="pings">
<h1 ng-click="pings.press()">asdf</h1>
</div>
<div ng-if="!pings">
<div ng-controller="PingsCtrl as pings">
<h1 ng-click="pings.press()">asdf</h1>
</div>
</div>
This will work if it is dropped inside or outside of an existing PingsCtrl.
Here is a plunker.
https://plnkr.co/edit/4x0kSazcg0g0BsqPKN9C?p=preview

Please, check my solution to see how to share data between controllers
var app = angular.module('myApp', []);
app.controller("aCtrl", function ($scope, PingList) {
$scope.addPing = function() {
PingList.add('Ping A');
};
});
app.controller("bCtrl", function ($scope, PingList) {
$scope.addPing = function() {
PingList.add('Ping B');
};
});
app.factory('PingList', function () {
var pings = ['Ping1', 'Ping2'];
return {
add: function(ping) {
pings.push(ping);
},
get: function () {
return pings;
}
};
});
app.directive('pingList', function(PingList) {
return {
restrict: 'EA',
link: function($scope) {
$scope.pings = PingList.get();
$scope.press = function(ping) {
alert(ping);
}
},
template: '<ul><li ng-repeat="ping in pings" ng-click="press(ping)">{{ping}}</li></ul>'
};
});
a, li {
cursor: pointer;
}
a {
color: blue;
text-decoration: underline;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="aCtrl" style="float: left">
<a ng-click="addPing()">click to add A ping</a>
<ping-list></ping-list>
</div>
<div ng-controller="bCtrl" style="float: right">
<a ng-click="addPing()">click to add B ping</a>
<ping-list></ping-list>
</div>
<div style="clear: both"></div>
</div>

Related

AngularJS with jQuery for selecting parent element

I'm posting my html and directive code. I have two spans, which I have attached ng-click to. On ng-click I want to check the classes of their parents parent item (.parent-portlet), but what I have so far is not working correctly as all of the parent portlets get selected instead of only the current one.
<div class="parent-portlet {{portlet.class}} {{portlet.class2}}" ng-mouseenter="hoverIn($event)" ng-mouseleave="hoverOut($event)">
<div class="portlet-titlebar" ng-click="toggleCollapsed($event)">
<span class="remove" ng-click="removePortlet(portlet)">
Remove
</span>
<span class="add-back" ng-click="addPortlet(portlet)">
Add Back
</span>
</div>
</div>
this is what I have in my directive:
scope.removePortlet = function(portlet) {
var port = $('.remove').parent().parent();
port.addClass('removed');
port.addClass('edit-portlet');
};
scope.addPortlet = function(portlet) {
var port = $('.add-back').parent().parent();
if (portlet.hasClass('removed')) {
port.removeClass('removed');
port.removeClass('edit-portlet');
}
};
The problem with this code is that var portlet catches all of the portlets(parents) and I want to catch only the one related to the click action. How can I achieve that? I tried to pass this to my jquery select like so:
var portlet = $('.add-back', this).parent().parent();
but that didn't work. Any ideas how this can be achieved?
Thanks in advance.
Pass in the $event to the ng-click like this:
<span ng-click="removePortlet($event)"></span>
Then, just modify your code like this:
scope.removePortlet = function(ev) {
var port = $(ev.target).parent().parent();
port.addClass('removed');
port.addClass('edit-portlet');
};
scope.addPortlet = function(ev) {
var port = $(ev.target).parent().parent();
if ($(ev.target).hasClass('removed')) {
port.removeClass('removed');
port.removeClass('edit-portlet');
}
};
Grabbing the "event's target" will ensure that you are only addressing the item that was actually clicked. And note that angular uses $event to pass the event to a function.
I would also clean it up a bit combining the class modification lines and targeting the specific parent (in case you ever modify the DOM) using .closest():
scope.removePortlet = function(ev) {
var port = $(ev.target).closest('.parent-portlet');
port.addClass('removed, edit-portlet');
};
scope.addPortlet = function(ev) {
var port = $(ev.target).closest('.parent-portlet');
if (portlet.hasClass('removed')) {
port.removeClass('removed, edit-portlet');
}
};
You can inject the $element to your controller, then, use it as the context for the $ selector.
angular.module('app', []).controller('ctrl', function($scope, $element) {
$scope.addPortlet = function(portlet) {
console.log($('.add-back', $element));
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div class="parent-portlet">
<div class="portlet-titlebar" ng-click="toggleCollapsed($event)">
<span class="remove" ng-click="removePortlet(portlet)">
Remove
</span>
<span class="add-back" ng-click="addPortlet(portlet)">
Add Back
</span>
</div>
</div>
</div>
For more info about it
Use ng-class instead. Here's an example of changing background colors with classes:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.portlet = {
classes: {
"parent-portlet": true,
"edit-portlet": false,
"removed": false
}
}
$scope.removePortlet = function(portlet) {
portlet.classes.removed = true;
portlet.classes["edit-portlet"] = true;
};
$scope.addPortlet = function(portlet) {
portlet.classes.removed = false;
portlet.classes["edit-portlet"] = false;
};
});
/* Put your css in here */
.parent-portlet {
background-color: #ccc;
padding: 20px;
}
.parent-portlet.removed {
background-color: #c00;
}
.parent-portlet button {
padding: 10px;
margin: 0 10px;
}
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<script data-require="angular.js#1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-class="portlet.classes">
<div class="portlet-titlebar">
<button class="remove" ng-click="removePortlet(portlet)">
Remove
</button>
<button class="add-back" ng-click="addPortlet(portlet)">
Add Back
</button>
</div>
</div>
</body>
</html>

Invoke function only on the selected ng-repeat element

Currently I am trying to fetch some data using JSON and have structured it over my website using ng-repeat.
The problem is that on ng-click, on any newly created element the function invokes on each element as well. I have also tried using the this operator but it doesnt seem to work on angularjs.
I have created a dummy problem similar to mine.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<div ng-repeat="aplhabet in word">
<button style="width:50px; margin: 5px" ng-click="addDiv()">
<b>{{aplhabet}}</b>
</button>
<section ng-show="newdiv">functionInvoked</section>
</div>
</div>
<script>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope){
$scope.word = 'STRINGDEMO';
$scope.addDiv = function () {
$scope.newdiv = true;
}
}
</script>
</body>
</html>
As you might notice whenever you click on any button the function runs for each element. I need help to understand how to pass any identifier in this function so that the function is invoked only on the element clicked.
you need an object array to achieve this. simply use a for loop to convert this to an array of object.
for(key in $scope.word){
$scope.wordArr.push({
letter : $scope.word[key],
newDiv : false
})
}
use the new array as ng-repeat. to print letter use <b>{{aplhabet.letter}}</b>
<div ng-repeat="aplhabet in wordArr">
<button style="width:50px; margin: 5px" ng-click="addDiv(aplhabet)">
<b>{{aplhabet.letter}}</b>
</button>
<section ng-show="aplhabet.newdiv">functionInvoked
</section>
</div>
in the ng-click pass the whole object as a parameter and change the newDiv to true
Demo
var myApp = angular.module('myApp',[]);
function MyCtrl($scope){
$scope.word = 'STRINGDEMO';
$scope.wordArr = [];
for(key in $scope.word){
$scope.wordArr.push({
letter : $scope.word[key],
newDiv : false
})
}
$scope.addDiv = function (aplhabet) {
aplhabet.newdiv = true;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<div ng-repeat="aplhabet in wordArr">
<button style="width:50px; margin: 5px" ng-click="addDiv(aplhabet)">
<b>{{aplhabet.letter}}</b>
</button>
<section ng-show="aplhabet.newdiv">functionInvoked
</section>
</div>
</div>
You need to pass $index
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<div ng-repeat="aplhabet in word track by $index">
<button style="width:50px; margin: 5px" ng-click="addDiv($index)">
<b>{{aplhabet}}</b>
</button>
<section ng-show="newdiv">functionInvoked</section>
</div>
</div>
<script>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope){
$scope.word = 'STRINGDEMO';
$scope.addDiv = function (index) {
//do your stuff here
}
}
</script>
</body>
</html>

Second app is not working in angular js

Hi I am new to angular and I am trying to create 2 apps in a single HTML file but I am not getting the output for the second app and I am getting the proper output for the first app. Can anyone tell me where am I doing it wrong? The code is as below
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>The example for angular</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"></script>
<style>
input.ng-invalid {
background-color: lightcyan;
}
</style>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="hello">
<p>{{firstname}}</p>
</div>
<div ng-init="Akshay=[
{firstname:'Sandeep',place:'mangalore'},
{firstname:'sirish', place:'haridwar'},
{firstname:'krish', place:'mathura'}]">
<div ng-repeat="name in Akshay">
{{name.firstname + ' ' + name.place}}
</div>
</div>
<div class="ang"></div>
<form name="myForm">
<input type="email" name="emaild" ng-model="text">
<span ng-show="myForm.emaild.$error.email">Please enter the proper email</span>
</form>
<input type="text" ng-model="mytext" required>
</div>
<div ng-app="Appname" ng-controller="personCtrl">
<input type="text" ng-model="firstName">
<input type="text" ng-model="lastName">
<p>His firstname was{{firstName}}</p>
<p>His second name was {{lastName}} </p>
<h1> His name was {{fullname()}} </h1>
</div>
<script>
var app = angular.module("myApp", []);
app.controller("hello", function ($scope) {
$scope.firstname = "Akshay";
});
app.directive("ang", function () {
return {
restrict: "C",
template: "This is a great time to be alive"
};
});
var appsec = angular.module('Appname', []);
appsec.controller("second", function ($scope) {
$scope.firstName = "Bhagath";
$scope.lastName = "Singh";
$scope.fullname = function () {
return $scope.firstName + " " + $scope.lastName;
};
});
</script>
</body>
</html>
you can't bootstrap two module in single html file. According to Doc
Only one AngularJS application can be auto-bootstrapped per HTML document. The first ngApp found in the document will be used to define the root element to auto-bootstrap as an application. To run multiple applications in an HTML document you must manually bootstrap them using angular.bootstrap instead. AngularJS applications cannot be nested within each other
you can use manual bootstrap method to bootstrap both the modules simultaneously
Modify the two boostrap lines to happen when the document is ready:
angular.element(document).ready(function() {
angular.bootstrap(document.getElementById('app1'), ['app1']);
angular.bootstrap(document.getElementById('app2'), ['app2']);
});
When modifying your plnkr in this way, both apps started working properly.
Live Demo : https://jsfiddle.net/samirshah1187/w7gv56t5/
As #Samir said, we can use manual bootstrap method to bootstrap both the modules simultaneously we need to define id like this <div ng-app="myApp" id="myId"> <div ng-app="Appname" ng-controller="personCtrl" id="personId">,
and then add these lines at the end inside script
angular.bootstrap(document.getElementById('myId'), ['myApp']);
angular.bootstrap(document.getElementById('personId'), ['Appname']);
we need to bootstrap the modules to have multiple ng-app within the same page.

AngularJS creating a directive to extend ng-include with proper base url

So due to restrictions I have at work I am getting some pretty long includes. To avoid this I have tried creating the following directive:
app.directive('viewInclude', [function() {
var baseUrl = 'fams360frontend/views/default/glap';
return {
scope: {
viewInclude: '#'
},
template: '<div ng-include="link"></div>',
link: function($scope) {
$scope.link = baseUrl + $scope.viewInclude;
}
};
}]);
I then call it like this:
<view-include src="'/partials/ientry-header.html'"></view-include>
I am pretty new to Angular so this may be a simple issue but I can't seem to figure out where I am going wrong. I get this error on render:
Error: [$parse:syntax] <!-- ngInclude: fams360frontend/views/default/glap{{viewInclude}} -->
EDIT:
I have updated my code using the answer below but I now no longer get the bank bindings...any ideas?
The included file:
<div style="display: inline-block;">
<div style="display: inline-block;">
<span>Bank Account:</span>
</div>
<div style="display: inline-block; margin-left: 10px;">
<span>{{bank.bank_number}} - {{bank.account_name}}</span>
</div>
</div>
<div style="display: inline-block; margin-left: 75px;">
<div style="display: inline-block;">
<span>Company:</span>
</div>
<div style="display: inline-block; margin-left: 10px;">
<span>{{bank.company_number}} - {{bank.company_name}}</span>
</div>
</div>
Add a link function and concatenate.
app.directive('viewInclude', [function() {
var baseUrl = 'fams360frontend/views/default/glap';
return {
replace: true,
scope: {
viewInclude: '#'
},
template: '<div ng-include="link"></div>',
link: function($scope) {
$scope.link = baseUrl + $scope.viewInclude;
}
};
}]);
Additionally. I believe your html needs to be.
<div view-include="asdf"></div> <!-- view-include should be an attribute. And since you're using `#` you don't need to wrap the string in single quotes -->
Based on comment about losing bindings that is due to the isolated scope you are creating.
A simpler approach would be to not use isolated scope or even ng-include(which creates a child scope) and just use the templateUrl: fn() of directive:
directive('viewInclude', function() {
var baseUrl = 'fams360frontend/views/default/glap';
return {
templateUrl: function(elem, attr){
return baseUrl + attr.src;
}
};
});
Scope of directive will be whatever the scope of parent is wherever it is used this way. Directive is only being used to define source path of template
NOTE: remove extra quotes in your src shown in question
<view-include src="/partials/ientry-header.html"></view-include>

Angular "controller as syntax", passing variables, and isolate scoping

I was just doing this tutorial which makes a lot of sense - http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/. A fiddle provided is here: http://jsfiddle.net/simpulton/SPMfT/
It shows how to bind attributes to the parent controllers scope using #, =, &.
I wanted to change the fiddle to use "controller as syntax", but can't seem to get it to work, my fiddle is here - http://jsfiddle.net/SPMfT/304/
Any thoughts on why this wouldn't work?
View:
<div ng-controller="MyCtrl as ctrl">
<h2>Parent Scope</h2>
<input ng-model="ctrl.foo"> <i>// Update to see how parent scope interacts with component scope</i>
<br><br>
<!-- attribute-foo binds to a DOM attribute which is always
a string. That is why we are wrapping it in curly braces so
that it can be interpolated.
-->
<my-component attribute-foo="{{ctrl.foo}}" binding-foo="ctrl.foo"
isolated-expression-foo="ctrl.updateFoo(newFoo)" >
<h2>Attribute</h2>
<div>
<strong>get:</strong> {{isolatedAttributeFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedAttributeFoo">
<i>// This does not update the parent scope.</i>
</div>
<h2>Binding</h2>
<div>
<strong>get:</strong> {{isolatedBindingFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedBindingFoo">
<i>// This does update the parent scope.</i>
</div>
<h2>Expression</h2>
<div>
<input ng-model="isolatedFoo">
<button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>
<i>// And this calls a function on the parent scope.</i>
</div>
</my-component>
</div>
JS:
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'#attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})
.controller('MyCtrl', ['$scope', function ($scope) {
this.foo = 'Hello!';
var self = this;
this.updateFoo = function (newFoo) {
self.foo = newFoo;
}
}]);
Thanks JoseM for the heads up. I've rewritten this fiddle using angular 1.2 and "controller as" syntax here: - http://plnkr.co/edit/nUXWrj4yzypaQmtJShl9?p=preview
Not sure where to start with issue with the previous version:
In 1.2 isolated scope variables can't be used directly in the DOM,
they have to be in the template of the directive.
I made sure mydata was an object to avoid prototypical inheritance issues.
When evaluating an attribute with # you have to make sure you pass it
inside {{}}.
var app = angular.module("drinkApp", []);
app.controller("AppCtrl", function($scope) {
this.ctrlFlavor = {
data: "blackberry"
}
var self = this;
this.updateFoo = function(newFoo) {
self.ctrlFlavor.data = newFoo;
}
})
app.directive("drink", function() {
return {
scope: {
isolatedBindingFoo: "=",
isolatedAttributeFoo: "#",
isolatedExpressionFoo: '&'
},
template: '<h2>Isolated Binding</h2><div>{{isolatedBindingFoo}}</div><input ng-model="isolatedBindingFoo"></input><br><h2>Isolated Attribute</h2><div>{{isolatedAttributeFoo}}</div><input ng-model="isolatedAttributeFoo"></input><h2>Isolated Expression</h2><input ng-model="isolatedFoo"></input><button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>'
}
})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AngularJS Video Embed</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link href="style.css" rel="stylesheet" />
<script data-semver="1.2.4" src="http://code.angularjs.org/1.2.3/angular.js" data-require="angular.js#1.2.x"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-app="drinkApp">
<div ng-controller="AppCtrl as drinkCtrl">
<h2>AppCtrl Scope</h2>
{{drinkCtrl.ctrlFlavor.data}}
<br>
<input type="text" ng-model="drinkCtrl.ctrlFlavor.data">
<div drink isolated-binding-foo="drinkCtrl.ctrlFlavor.data" isolated-attribute-foo="{{drinkCtrl.ctrlFlavor.data}}" isolated-expression-foo="drinkCtrl.updateFoo(newFoo)">
</div>
</div>
</div>
</body>
</html>

Categories

Resources