$broadcast is not working in Angularjs - angularjs

I am using two controllers. When changes happen in one controllers it should get changed immediately in the other controller. I am using the $broadcast event to achive this.
My code:
My First controller
app.controller('configurationCtrl', function($scope, $http,Notification,$rootScope,$cookies) {
$scope.awssubmit=function(){
$scope.page_loader=true
$http.post("/insert_config_details",$scope.userdata).then(function(List){
if(List){
$scope.page_loader=false;
$cookies.put("bucket",$scope.userdata.bucket_name)
$scope.$broadcast('eventEmitedName', 'Some data');
Notification.success('<span class="glyphicon glyphicon-ok"></span> <strong>AWS Configuration details updated successfully.</strong>');
}
else{
$scope.page_loader=false;
Notification.error('<span class="glyphicon glyphicon-ok"></span> <strong>Error!!! Please try again later.</strong>');
}
$scope.awssave = false;
$scope.awstext=true;
})
}
});
My Second Controller:
app.controller('SidemenuController', function($scope, $http,$location,BucketService)
{
$scope.$on('eventEmitedName', function (event, data) {
console.log("Called"); //I am not getting this
value
console.log(data); // 'Some data' // I am not getting this
value
});
});
aws_submit() is called from my view and everything seems to work fine. But in SidemenuController I am not getting any data. Is there any mistake in my code?
Update:
My view :
<form id="awsform" method="post" name="awsform" class="form-horizontal" novalidate>
<div class="col-sm-6 four_module_config">
<div class="account_settings">
<div class="col-sm-12 heading_config" ng-hide="awssave">
<h4 class="sub_title col-sm-11" style="border-bottom:none">AWS S3 Configurations</h4>
<% if(valid_role[1]) { %>
<div class="action col-sm-1">
<span class="actico editrole" ng-click="editaws()">
<a href='javascript:void(0)' ></a>
</span>
</div>
<% } %>
</div>
<div class="col-sm-12 heading_config" ng-show="awssave">
<h4 class="sub_title col-sm-9" style="border-bottom:none">AWS S3 Configurations</h4>
<div class="action col-sm-3 close_config">
<button type="button" class="site_btn submit_btn save_config col-sm-2" id="submit" ng-show="awstest"
ng-click="verifyaws()">Test</button>
<button type="button" class="site_btn submit_btn save_config col-sm-2" id="submit" ng-show="submitawssave"
ng-click="awssubmit()">Submit</button>
<button type="button" class="site_btn submit_btn save_config col-sm-2" id="submit" ng-click="closeaws()">Cancel</button>
</div>
</div>
<div class="ipfield col-md-8 hint_txt_conf">
*Enter your AWS access Key, S3 Bucket name configured in your AWS Environment. Which is used to store your document in the
cloud.
</div>
<div class="ipfield first_ipfield">
<div class="col-md-8">
<label for="name" class="usrlabel">AWS access key <span class="mandat">*</span></label>
<input type="password" ng-disabled="awstext" ng-model="userdata.key" required name="key" class="txt_box" id="key" placeholder="Enter AWS access key">
<span toggle="#key" class="fa fa-fw fa-eye field_icon toggle-password"></span>
</div>
</div>
<div class="ipfield">
<div class="col-md-8">
<label for="name" class="usrlabel">AWS Secret Key <span class="mandat">*</span></label>
<input type="password" ng-disabled="awstext" ng-model="userdata.secretkey" required name="secretkey" class="txt_box" id="secretkey" placeholder="Enter AWS Secret Key">
<span toggle="#secretkey" class="fa fa-fw fa-eye field_icon toggle-password"></span>
</div>
</div>
<div class="ipfield">
<div class="col-md-8">
<label for="name" class="usrlabel">AWS Region Code <span class="mandat">*</span></label>
<input type="text" ng-disabled="awstext" ng-model="userdata.region" required name="region" class="txt_box" id="region" placeholder="Enter AWS Region Code">
</div>
</div>
<div class="ipfield">
<div class="col-md-8">
<label for="name" class="usrlabel">AWS Bucket Name <span class="mandat">*</span></label>
<input type="text" ng-disabled="awstext" ng-model="userdata.bucket_name" required name="bucket_name" class="txt_box" id="bucket"
placeholder="Enter AWS Bucket Name">
</div>
</div>
</div>
</div>
</form>

If you want to send data from one controller to another controller using $brodcast than use $rootscope.$broadcast
$rootScope.$broadcast('eventEmitedName', 'Some data');
Second Controller
app.controller('SidemenuController', function($scope, $http,$location,BucketService) {
$scope.$on('eventEmitedName', function (event, data) {
console.log("Called");
console.log(data); // 'Some data'
$scope.bucket = data;
});
});
Note: Do not use $rootscope.$on as listener because $rootscope
listener are not destroyed . Instead it will create listeners stack

If you want to call one controller event into another there are four methods available:
$rootScope.$broadcast() if your controller are not in a parent / child relation.
If your second controller (event fired here) is a parent you can use $scope.$broadcast();
If your second controller (event fired here) is a child you can use $scope.$emit();
The best way to solve this would be to use a service -> Example of using a service to share data between controllers.
Note: You need to destroy $rootScope.$on() listeners manually avoid stacking events. This Difference between $rootScope.$on vs $scope.$on and this Do you need to unbind $scope.$on in $scope $destroy event? will help you understand the basics of using events.
View
<div ng-controller="MyCtrl">
<button ng-click="broadcast()">
broadcast
</button>
</div>
<div ng-controller="MySecondCtrl">
{{ test }}
</div>
AngularJS application
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope, $rootScope) {
$scope.broadcast = function() {
$rootScope.$broadcast('test', 'testit');
}
});
myApp.controller('MySecondCtrl', function($scope, $rootScope) {
var registerScope = $rootScope.$on('test', function(test, args) {
console.log(args);
$scope.test = args;
});
// clean up, destroy event when controller get destroyed.
$scope.$on('$destroy', registerScope);
});
> demo fiddle

Related

Angular Two-Way data-binding not refreshing ng-repeat on $scope change

My functionality is very simple, and I have to imagine very common. I have a page that lists a collection of 'age groups', and provides functionality to add an 'age group'. I simply would like to have the page immediately reflect the change when a new 'age group' is added.
In early versions, I had simply used the $rootScope in my service to put all 'age groups' in the $rootScope when the $http request was completed. On this latest refactoring I am removing all use of $rootScope in my app, and it is becoming problematic.
The code for the HTML view is as follows: [NOTE: all code considered extraneous has been removed from all code snippets]
<div id="tabContent" class="tab-pane fade in active" ng-show="subIsSelected(1)">
<div class="row">
<div class="col-xs-12">
<form class="form-inline">
<button type="button" class="btn btn-success" ng-click="openAddAgeGroup()">Add Age Group</button>
</form>
</div>
</div>
<hr />
<div class="row row-content">
<div class="col-xs-12">
<h4 ng-show="!areAgeGroups()">No current age groups.</h4>
<ul class="media-list" ng-show="areAgeGroups()">
<li class="media" ng-repeat="ageGroup in ageGroups" style="padding:10px; background: lightGray;">
<div class="media-left media-top" >
<img class="media-object img-thumbnail" style="width: 75px;" ng-src="./images/trfc.png" alt="club logo">
</div>
<div class="media-body">
<h3 class="media-heading" style="padding-top: 20px;">{{ageGroup.name}}</h3>
<button type="button" class="btn btn-xs btn-primary" style="width: 50px;" ng-click="openEditAgeGroup(ageGroup)">Edit</button>
<button type="button" class="btn btn-xs btn-danger" style="width: 50px;" ng-click="deleteAgeGroup(ageGroup)">Delete</button>
</div>
</li>
</ul>
</div>
</div>
</div>
when first loaded, the page correctly shows all 'age groups' that are in the $scope.ageGroups array.
On clicking the button to add an 'age group', an ng-dialog is created as follows:
$scope.openAddAgeGroup = function() {
console.log("\n\nOpening dialog to add age group");
ngDialog.open({ template: 'views/addAgeGroup.html', scope: $scope, className: 'ngdialog-theme-default', controller:"HomeController" });
};
and that dialog is populated as such:
<div class="ngdialog-message">
<div class="">
<h3>Add a New Age Group</h3>
</div>
<div> </div>
<div>
<form ng-submit="addAgeGroup()">
<div class="form-group">
<label class="sr-only" for="name">Age Group Display Name</label>
<input type="text" class="form-control" id="name" placeholder="age group name" ng-model="ageGroupForm.name">
</div>
<div class="form-group">
<label class="sr-only" for="birthyear">Birth Year</label>
<input type="text" class="form-control" id="birthyear" placeholder="birth year" ng-model="ageGroupForm.birthyear">
</div>
<div class="form-group">
<label class="sr-only" for="socceryear">Soccer Year</label>
<div class="input-group">
<div class="input-group-addon">U</div>
<input type="text" class="form-control" id="socceryear" placeholder="soccer year" ng-model="ageGroupForm.socceryear">
</div>
</div>
<button type="submit" class="btn btn-info">Add</button>
<button type="button" class="btn btn-default" ng-click=closeThisDialog("Cancel")>Cancel</button>
</form>
</div>
</div>
When the form is submitted, the 'age group' is added to the database, from the controller:
'use strict';
angular.module('ma-app')
.controller('HomeController', ['$scope', 'ngDialog', '$state', 'authService', 'coreDataService', 'userService', '$rootScope', 'clubService', 'schedulingService', function($scope, ngDialog, $state, authService, coreDataService, userService, $rootScope, clubService, schedulingService) {
...
$scope.addAgeGroup = function() {
coreDataService.addAgeGroup($scope.ageGroupForm)
.then(function(response) {
coreDataService.refreshAgeGroups()
.then(function(response) {
coreDataService.setAgeGroups(response.data);
$scope.ageGroups = coreDataService.getAgeGroups();
console.log("\n\n\nretrieved age groups and put them in scope");
console.log($scope.ageGroups);
ngDialog.close();
});
}, function(errResponse) {
console.log("Failed on attempt to add age group:");
console.log(errResponse);
});
};
The coreDataService is defined as follows:
'use strict';
angular.module('ma-app')
.service('coreDataService', ['$http', 'baseURL', 'googleGeolocateBaseURL', 'googleGeocodeKey', 'googleMapsBaseURL', function($http, baseURL, googleGeolocateBaseURL, googleGeocodeKey, googleMapsBaseURL) {
var ageGroups = {};
var ageGroupsLoaded = false;
this.getAgeGroups = function() {
return ageGroups;
};
this.setAgeGroups = function(newAgeGroups) {
ageGroups = newAgeGroups;
ageGroupsLoaded = true;
};
this.addAgeGroup = function(formData) {
//post age group:
var postString = '{ "birth_year": "' + formData.birthyear + '", "soccer_year": "U' + formData.socceryear + '", "name": "' + formData.name + '" }';
console.log("Posting age group with string: " + postString);
return $http({
url: baseURL + 'age_groups/',
method: 'POST',
headers: {
'content-type': 'application/json'
},
data: postString
});
};
}]);
So, when an 'age group' is added, console logging indicates that the new 'age group' is in the collection now stored in the $scope.ageGroups array, but the HTML's ng-repeat does not reflect the new 'age group'. Only when I navigate to another tab in the interface, and then return to the tab containing the 'age groups' is the newly added 'age group' displayed.
Updated:
After you update your value you need to tell angular that its been updated. If you wrap your code in a $timeout. On the next digest cycle the view will get updated. See here for more information.
...
$timeout(function() {
$scope.ageGroups = coreDataService.getAgeGroups();
// anything you want can go here and will safely be run on the next digest.
})
...
The $timeout basically runs on the next $digest cycle thus updating your value in the view.
I would suggest wrapping your ng-repeat part in a directive, fire an event on add method and listen to it from the directive:
this.addAgeGroup = function(formData) {
// do stuff
$scope.$broadcast('itemadded');
}
Inside your directive link function:
$scope.$on('itemadded', function() {
$scope.ageGroups = coreDataService.getAgeGroups();
});
I would recommend using the ControllerAs syntax which will refresh the ng-repeat, if the array to which it is bound to gets updated.
Refer the answer given here - ng-repeat not updating on update of array

Save every scope from a Form in a service Angularjs

I would like to save my datas from my form to my service function setData(), but I am not sure how to do it. I have already done the service, the setData() function, and my form.
Form
<form id="eventForm" data-toggle="validator" class="text-left" role="form" name="form.eventForm">
<div class="form-group col-md-12">
<input type="text" ng-model="form.return" name="RT" id="return"> Return
<input type="text" ng-model="form.oneway" name="OW" id="oneway"> One way
</div>
<button name="submit" class="btn btn-default ng-click="form.$valid && submit()">Pay now</button>
</form>
AngularJS submit method
$scope.submit = function() {
FormData.setData($scope.form);
$location.path("/flights");
}
Service setData()
this.data = JSON.parse(sessionStorage.getItem("flights") || '{}');
this.setData = function(data) {
this.data = data;
sessionStorage.setItem("flights", JSON.stringify(data));
}
this.getData = function() {
return this.data;
}
I don't know how should I pass my whole data from my Form to setData() function.
Try changing the submit button to disable on an invalid form, and set the submit action to send the form object. Then, in the controller, set the function to accept an argument (the form data), and use that.
HTML
<form id="eventForm" data-toggle="validator" class="text-left" role="form" name="form.eventForm">
<div class="form-group col-md-12">
<input type="text" ng-model="form.return" name="RT" id="return"> Return
<input type="text" ng-model="form.oneway" name="OW" id="oneway"> One way
</div>
<button name="submit"
class="btn btn-default
ng-disabled="form.$invalid"
ng-click="submit(form)">
Pay no
</button>
</form>
JS
$scope.submit = function(submitData) {
FormData.setData(submitData);
$location.path("/flights");
}

Broadcast from directive to controller

I am trying to modify a form in a view by means of a button in my directive's template (template is in another file), this is for a basic CRUD where each item has a delete/edit button.
In order to avoid replicating the form I decided to that on edit's click a function would send the item to the controller in questions in order to be updated with the new information.
But I've been having troubles making the connection, so far I tried changing $root, to $rootScope back and forth and using , $broadcast or $emit.
So how can I send the function onChange my item's information based on the template's button click?
Template:
<strong>{{item.type}}</strong> {{item.description}}
<div class="material-switch pull-right">
<button type="button" class="btn btn-warning btn-circle" ng-show="item.editable" ng-click="onChange()">
<span class="glyphicon glyphicon-edit" ></span>
</button>
<button type="button" class="btn btn-danger btn-circle" ng-controller="View1Ctrl" ng-show="item.editable" ng-click="EliminarItem(item)">
<span class="glyphicon glyphicon-minus" ></span>
</button>
<input ng-model="item.isDone"
id="someSwitchOptionDefault{{itemIndex}}"
name="someSwitchOption001{{itemIndex}}"
type="checkbox" />
<label for="someSwitchOptionDefault{{itemIndex}}" class="label-info"></label>
</div>
Directive:
'use strict';
angular.module('myApp.items.directive', [])
.directive('itemSwitch', [ function() {
return {
restrict: 'A',
scope: {
item: '=',
itemIndex: "="
},
templateUrl: 'templates/itemSwitchTemplate.html',
link : function($scope){
$scope.$broadcast('onChange', item);
}
}
}]);
Controller
.controller('View1Ctrl', ['$scope','itemsService',function($scope,itemsService) {
$scope.items = itemsService.getItems();
$scope.classMap = {GROCERIES:"success",CAR:"danger",UNIVERSITY:"warning",PAYMENTS:"info"};
$scope.newItem = {};
$scope.$on('onChange', function(event, args) {
if ($scope.btnEdit) {
$scope.newItem = args;
} else {
$scope.newItem = {};
}
});
$scope.enableEdit = function (item) {
item.editable = true;
};
$scope.disableEdit = function (item) {
item.editable = false;
};
}]);
View
<div class="col-xs-12">
<div ng-model="currentItem" ng-repeat="item in items" item-switch item="item" item-index="$index" class="notice notice-{{classMap[item.type]}}" ng-mouseover="enableEdit(item)" ng-mouseleave="disableEdit(item)">
</div>
<!-- FORMULARIO -->
<form name = "myForm" class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Task</legend>
</div>
<div class="control-group">
<!-- Name-->
<label class="control-label">Name</label>
<div class="controls">
<input type="text" name="itemName" ng-model="newItem.name" placeholder="Task Name" class="input-xlarge" ng-required="true" >
<p class="help-block"></p>
</div>
</div>
<div class="control-group">
<!-- Description -->
<label class="control-label">Description</label>
<div class="controls" >
<input type="text" ng-model="newItem.description" placeholder="Task Description" class="input-xlarge">
<p class="help-block"></p>
</div>
</div>
<div class="control-group">
<!-- Button -->
<div class="controls">
<a class="btn icon-btn btn-success" ng-disabled="myForm.$invalid" ng-click="addOrSaveItem()">
<span class="glyphicon btn-glyphicon glyphicon-save img-circle text-success"></span>Save</a>
</div>
</div>
</fieldset>
</form>
</div>
FiddleJS
Look nd Feel
Using "onChange" as an event name is a poor choice as it is likely to conflict with other events with that name. My recommendation is to use the directive's name as part of the event name.
In your directive
angular.module('myApp.items.directive', [])
.directive('itemSwitch', function() {
return {
restrict: 'A',
scope: {
item: '=',
itemIndex: "="
},
template: '<button ng-click="doIt()">Do It</button>',
link : function(scope){
scope.doIt = function() {
scope.$emit('itemSwitch.doIt', scope.item, scope.itemIndex);
};
}
}
});
In your controller
$scope.doItItems = [];
$scope.$on("itemSwitch.doIt", function(item, itemIndex) {
doItItems.push(item);
});
In this example, on each click of the Do It button, an item is pushed to the doItItems list.
In your itemSwitch directive, you can do
$rootScope.$broadcast('onChange', item);
And then you can pick it up in any scope that is listening (in this case, your controller), with
$scope.$on('onChange', function(event, args) { ... }
This works because $broadcast moves downward from parent to children, while $emit moves upward from child to parents.
So for example, $rootScope.$emit would only be picked up by $rootScope.$on since $rootScope has no parents, while $scope.$broadcast would only be available to that scope's children and not to $rootScope.$on, since $rootScope is not a child of $scope

Convert user input into a label using angular.js

What I'am trying to do is convert every user input into a label using angular. I believe that I'am doing the right thing, but is not working. I will appreciate if somebody take a look at this code. Thank you
here is a plunker
<div class="row" ng-controller="tagForm">
<div ng-click="addEntry()">
<div class="col-xs-12 col-sm-12 col-md-10 ">
<input type="text" placeholder="What are your area of expertise" ng-model="newEntry.name" class="form-control border" />
</div>
<div class="col-xs-12 col-md-2 center form-button">
<input type="button" value="Add" class="btn btn-orange btn-add" />
</div>
<div class="col-md-8 col-sm-offset-2" id="up">
<br />
<span class="label label-primary" ng-repeat="entry in entries">{{entry.name}}</span>
</div>
</div>
</div>
app.controller('tagForm', ['$scope', function($scope) {
return $scope.addEntry = function() {
$scope.entries.push($scope.newEntry);
return $scope.newEntry = {};
};
}]);
You have a few things wrong in your Plunk, but here's some stuff to start with:
You need to wire up a click event on your Add button. Right now, its not doing anything when you click it
Bind to an ng-model on the scope just using 'newEntry'. All you're typing is a name so thats all you need to save on the scope.
Loop over entries, printing out just 'entry' instead of 'entry.name'
And your controller should look like this (no need for the returns)
app.controller('tagForm', ['$scope', function($scope) {
$scope.entries = [];
$scope.addEntry = function() {
$scope.entries.push($scope.newEntry);
};
}]);
See this fiddle:
http://jsfiddle.net/smaye81/anrv2qms/1/

How to validate form when the forms are inserted using ng-include

I have 3 different form pages which are inserted using ng-include into DOM within a bootstrap modal window. What is the best way to do validation in every form and do a complete form submit(for all the 3 forms) in scenario like this?
HTML
<div ng-switch on="page">
<div ng-switch-when="Games">
<div ng-include="'Games.html'"></div>
</div>
<div ng-switch-when="Music">
<div ng-include="'Music.html'"></div>
</div>
<div ng-switch-when="Videos">
<div ng-include="'Videos.html'"></div>
</div>
</div>
Demo : http://plnkr.co/edit/D1tMRpxVzn51g18Adnp8?p=preview
There is to find a way to validate data yet
(may be with jqueryvalidation) but can be a starting point.
I think there is no way to get the value of games.$valid
so I've thought of
var app = angular.module("myApp", [])
app.controller("FormsCtrl", function($scope) {
//console.log($scope);
// $scope.items = ['Games', 'Music', 'Videos'];
$scope.$on('someEvent',function(e,a){
console.log(a);
})
});
app.directive("myform", function() {
return {
restrict: "A",
link:function(scope,element,attrs){
element.bind('submit',function(e){
var isValid = false; // TO DO :)
scope.$emit('someEvent', [attrs.fname,isValid]);
});
}
}
});
<div ng-controller="FormsCtrl">
<div ng-switch on="page">
<div ng-switch-when="Games">
<div ng-include="'Games.html'"></div>
</div>
<div ng-switch-when="Music">
<div ng-include="'Music.html'"></div>
</div>
<div ng-switch-when="Videos">
<div ng-include="'Videos.html'"></div>
</div>
</div>
</div>
<form name="games" class="simple-form" myform fname="games">
<input type="text" ng-model="prefix.games.name" name="uName" required /><br>
</form>
EDIT
A smarter quicker way :)
app.controller("FormsCtrl", function($scope) {
$scope.mySubmit = function(isValid){
console.log(isValid);
}
});
<form name="games" class="simple-form" ng-submit="mySubmit(games.$valid)">
<input type="text" ng-model="prefix.games.name" name="uName" required /><br>
</form>

Resources