AngularJS: Share data between different controllers - javascript

I have the following structures
<div ng-controller='ctrlA'>
<button ng-click='updateFactoryData(data)'></button>
<custom-dir>Custom directive with ctrlB</custom-dir>
<custom-dir>Custom directive with ctrlB</custom-dir>
<custom-dir>Custom directive with ctrlB</custom-dir>
...
<other-custom-dir>Custom directive with ctrlC</other-custom-dir>
<other-custom-dir>Custom directive with ctrlC</other-custom-dir>
<other-custom-dir>Custom directive with ctrlC</other-custom-dir>
...
</div>
I have a dataFactory, which when user clicks the button, the updated data will store in dataFactory, the dataFactory is injected to both ctrlB and ctrlC.
The problem is, when data is updated after clicking the button, the changes do not reflect on both custom-dir and other-custom-dir, is there any way ctrlB and ctrlC's scope value automatically reflect the changes made in scope under ctrlA?
Thanks very much for the help!
Thanks all for the kind help!! I finally figure out why the change cannot be reflected, I watch the object parameter to see if there are changes, but watch only monitor the object reference instead of the actual content, after deep watching the object parameter, I can finally get the watch work! Thanks for all the help
<custom-dir para="{data: data}"></custom-dir>
Then in directive's link function
scope.$watch('para.data', function(n,o){
alert('changed')
}, true); //>> The true is important!!

The fact that you need to share scope is a bit weird knowing that you have a parent controller that can contain your $scope item

Pass the value to attribute in the <custom-dir> and <other-custom-dir>
<custom-dir data="data"> </custom-dir>
Observe the change from the directive
attrs.$observe('data', function(value){
// call your directive controller here ...
})
The Benefit :
By passing data from attribute, the directive do not have to know about the controller/the source of the data, so, this reduce dependecy.

It's considered a best practice to use "controllerAs" and no longer use $scope.
By writing:
<div ng-controller='ctrlA as a'>
<button ng-click='a.updateFactoryData(data)'></button>
<!-- ... -->
</div>
and:
function ctrlA () {
this.data = [];
this.updateFactoryData = function (data) {
//...
}
}
You access your controller's data with a.data everywhere inside your div.

Basically what you require is here is share the factory data changes in your directives. So you can bind your desired factory data using different approaches based on your requirements(i.e. # attribute,= 2 way model, & expression bindings) in directives and than watch those variables in directives link functions (i.e. ideal place where you needs to handle those watch conditions) for changes so once anything will update on factory (through button click), it will automatically propagated in your directives. Here is the example to do different bindings.
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:'&'
}
};
})
<my-component attribute-foo="{{foo}}" binding-foo="foo"
isolated-expression-foo="updateFoo(newFoo)" >
<h2>Attribute</h2>
<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>
<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>
I have made sample demo to illustrate the approach please check this.

Related

$compile doesn't work inside Angular directive

with this Angular directive every time my model changes, new HTML item appended to the page:
app.directive('helloWorld', function($compile) {
return {
restrict: 'AE',
replace: true,
scope:{
arrayItem: '=ngModel'
},
link: function ($scope, ele, attrs) {
$scope.$watch( 'ngModel' , function(){
ele.html('<div ng-click="sendLike({{arrayItem.data.timeline_content}})" class="timeline-item"> Hello {{arrayItem2.data.timeline_content}} </div>');
$compile(ele.contents())($scope);
});
}
};
});
And this is HTML view:
<hello-world ng-repeat="arrayItem in arr" ng-model="arrayItem"></hello-world>
But ng-click inside dynamically generated HTML doesn't work. actually recompiling of this new added section does not works.
UPDATE:
this is what i want to achieve:
in fact i want to create a chat Application. messages stored inside an Array and i have to bind that array to the HTML view. if i click on every message, i need to an alert() fired inside the controller. my controller is like this:
app.controller("timelineCtrl", function ($scope) {
$scope.arr={};
$scope.sendLike = function (id) {
alert(id);
};
.
.
.
}
in the jQuery way, i simply use DOM manipulation methods and add new tags for each message but in Angular way i have to bind that array as a ng-model or something like that.
at first glance, i realize that designing a directive should be a good idea and inside module i can access to main scope and do needed thing with that directive and i expect that changes inside that directive should projected to HTML view but it fails and things like ng-click doesn't work for dynamically created tags.
There are two ways that you could accomplish this, with or without a directive.
Let's start without a directive; we'll assume that you have an array in the controller.
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<div ng-click="sendAlert(post)">
<span class="postnumber">{{::post.lineNumber}}:</span>
<span class="message">{{::post.message}}</span>
</div>
</div>
</div>
whenever an object is added to $scope.timeline, it can be assigned a lineNumber, and we can use angular OrderBy to sort the direction in reverse lineNumber order (using -). The $scope.sendAlert(post) will send the specific post to the function. in our bindings, we use :: to indicate that these are one time bindings, i.e. not values that need to be monitored independently of the array. This can improve performance on large lists.
using a Directive, we can accomplish this in a very similar manner, by making a Directive that renders a specific post, and passing the post in as a property.
app.directive('timelinePost', function() {
return {
restrict: 'AE',
scope:{
post: '='
},
template: '<div ng-click="postCtrl.sendAlert()">
<span class="postnumber">{{::postCtrl.post.lineNumber}}:</span>
<span class="message">{{::postCtrl.post.message}}</span>
</div>',
controller: 'postController',
controllerAs: 'postCtrl',
bindToController: true
};
app.controller("postController", function(){
var self = this; //save reference to this
self.sendAlert = function(){
//the specific post is available here as self.post, due to bindToController
};
};
//usage in HTML:
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<timeline-post post='post'></timeline-post>
</div>
</div>
you could further wrap this timeline in a directive in a similar manner, if you so desired. Either of these will accomplish the same task, of looping through your data, ordering it so that the newest post is at the top, and updating whenever the array changes. In the non-directive method, the timelineCtrl handles the $scope.sendAlert function; in the directive method, it is handled by the directive controller postController.
Please note, this is a rough draft based on what you have described and the information from various posts over the last 2 days. I haven't created a dataset to iterate through to test thoroughly, but the logic here should get you started.

ng-show nested in ng-repeat won't update

I've looked all over SO and Google and seen a whole bunch of reasons why ng-show wouldn't work inside of ng-repeat (scope issues, binding issues, etc.) but I can't quite pin down why mine isn't working. I was hoping that an extra pair of eyes could help me out. I'm very new to Angular, so hopefully my code makes some sense.
The goal: when $scope.current_set changes, the visible lmf-optionset changes. Currently, only the first optionset loads via Decision_Tree:loaded, and then when I try to load the next one via clickbox:clicked, $scope.current_set changes, but the view won't update.
JS
angular.module('lmf.option_set', [])
.controller('OptionsetCtrl', ['$scope', 'Optionsets', 'Decision_Tree',
function($scope, Optionsets, Decision_Tree) {
$scope.Optionsets = Optionsets;
$scope.current_set = {
name: null
};
$scope.$on('clickbox:clicked', function() {
$scope.current_set = Decision_Tree.get_next_optionset();
});
$scope.$on('Decision_Tree:loaded', function() {
$scope.current_set = Decision_Tree.get_next_optionset();
});
}
])
HTML
<div ng-controller='OptionsetCtrl'>
<div ng-repeat='set in Optionsets.option_sets'>
<div ng-show="set.name == current_set.name" lmf-optionset="{{set.name}}"></div>
</div>
</div>
Apparently lmf-optionset creates an isolated scope for each entry. These child scopes have link to parent's current_set, but they doesn't maintain a two-way binding. So when you are replacing the current_set value in you event handlers, child scopes are unaware that current_set was changed, and they are still referring to the old value.
You can either rewrite your event handlers like this:
$scope.current_set.name = Decision_Tree.get_next_optionset().name;
Or you can use controller as construction like this:
<div ng-controller='OptionsetCtrl as vm'>
<div ng-repeat='set in Optionsets.option_sets'>
<div ng-show="set.name == vm.current_set.name" lmf-optionset="{{set.name}}"></div>
</div>
</div>

Calling controller functions from the view in AngularJS

I have a function in my BlogController which changes the height of a div.
$scope.setTopBackgroundHeight = function (screenProportion, targetDiv) {
globalService.setTopBackgroundHeight(screenProportion, targetDiv);
};
I am using this controller on a few pages, but only want to call this function on one page. So I put the call in my view as follows.
<div id="primary"
class="content-area blog-page"
ng-controller="blogCtrl">
{{$scope.setTopBackgroundHeight("half", ".background-container");}}
</div>
Now, this works. But is calling a function from within curly braces in the view ok to do style wise? I've tried to find examples of doing something like this in the angular way, but can't see anything. Should it be in some ng directive?
Yes, all DOM manipulation should be done inside directives. So In this case it'd be better if you had a directive attached to the div that called that service method.
HTML:
<div id="primary"
class="content-area blog-page"
ng-controller="blogCtrl"
setBGHeight>
</div>
JS:
app.directive('setBGHeight', function(globalService) {
return {
link: function() {
globalService.setTopBackgroundHeight("half", ".background-container");
}
}
));
This is what directives are for. Make sure to prefix them like angular does with "ng-click" but don't use ng.

AngularJS- How to call controller's function on click of checkbox which is created in directive template

I am new to angularJS. I want to create checkboxes dynamically using templates in directives. I created controller and directives in separate files. I am creating checkbox in template in directive and want to invoke controller's function on ng-click of check box but I am unable to do so.
Here is my code sample.
Controller:
var app=angular.module('abc',[]);
app.controller('DemoCtrl', function($scope) {
$scope.ctrlFn = function(test) {
alert("hi "+test);
console.log(test);
}
});
I referred the https://github.com/iVantage/angular-ivh-treeview to create checkboxes tree view. I inlcuded all the css and js files in my sample. From the link I got the following js file which is creating the checkboxes in template in directive as shown below:
ivh-treeview.min.js:
angular.module("ivh.treeview",[]),
angular.module("ivh.treeview").directive("ivhTreeviewCheckbox",[function(){
"use strict";
return{restrict:"A",
scope:{node:"=ivhTreeviewCheckbox"},
require:"^ivhTreeview",
link:function(a,b,c,d){
var e=a.node,f=d.opts(),g=f.indeterminateAttribute,h=f.selectedAttribute;
a.isSelected=e[h],
a.ctrl=d,
a.$watch(function(){return e[h]},function(b){a.isSelected=b}),
a.$watch(function(){return e[g]},function(a){b.find("input").prop("indeterminate",a)})},
template:['<input type="checkbox"','ng-model="isSelected"','ng-change="ctrl.select(node, isSelected)" />'].join("\n")}
}]);
View:
<div class="col-sm-8" ng-controller="DemoCtrl as demo">
<div ivh-treeview="demo.bag"
ivh-treeview-selected-attribute="'isSelected'"
ivh-treeview-id-attribute="'uuid'"
ivh-treeview-expand-to-depth="0">
</div>
</div>
I want to call ctrlFn() on click of checkbox created in directive template. Please suggest a way to do the same.
Thanks In Advance
Sounds like you are looking for a two-way binding between directive and parent $scope.
// parent controller scope
$scope.person = { name:'coldstar', class:'sexy beast' };
// directive declaration
<div a-person='person'></div>
// directive code
scope: {
// the = sign is the key. could also be a function instead of object
innerPerson: "=aPerson"
},
link: function (scope, elm, attr){
// now this change will be reflect in the parent controller also
scope.innerPerson.name = "not coldstar anymore";
}
Edit:
I also noticed "'isSelected'" which should be "isSelected" if isSelected is a $scope.* entity

Use ng-show and etc. in directive "A"

I saw related asks, but dont understand anyway.
If I have directive: http://pastebin.com/QtAzGv62
And I need to add "ng-show" (or any other standart angular directive) functional to this directive (for related DOM element, that is ), that must depends on AuthService option (named "logged").
How?! :)
Since you are not binding to a particular 'element' you just need to make the methods available to your directive scope so change
elem.bind('click', function() {
AuthService.tryLogin();
});
to
scope.loggedIn = false;
scope.tryLogin = function(){
AuthService.tryLogin();
scope.loggedIn = true;
}
Then you can do in your directive html
<div ng-show="loggedIn">You dun logged in man!</div>

Categories

Resources