I have a an angularJS application and would try to simulate a Asana.com feature.
The scenario is the following:
I have a MainController for my application in the body tag, and inside this controller I populate my names in my sidebar:
.controller('MainController', ['$scope', 'NamesService', function($scope, NamesService) {
$scope.names = NamesService.query();
...
};
When I click on any name (for example, Anna), my application change the route and in inject the name-edit.html template in my ng-view, represented by the content area on the picture above. I have a input used to change from Anna to Carol and a update button. When I hit the update button, It fires a function that updates in my database, changes Anna to Carol in my content area (represented by the yellow arrow position) but doesn't change the red arrow position in my sidebar.
I tried to call the following code again inside my success update callback, but doesn't work
$scope.names = NamesService.query();
I'd like to know how to propagate the child controller to the parent controller, changing Anna to Carol inside $scope.names. Is it possible to do this, without reloading $scope.names?
You could use event system of Angular using $scope.$emit('eventName', eventData) un your child controller to pass data up on the hierarchy.
//child, assuming you have promise callback
NamesService.query().then(function(data){
$scope.$emit('eventName', data)
})
And in your parent controller have the following
//parent
$scope.$on('eventName', function(event, data){
$scope.names = data;
})
Related
I've two controllers and two views in ASP.NET MVC project. My requirement is to pass data from one controller to another on ng-click that should reflect in another view (As well from another controller). Simple! I know, it could be done using service but I was preferring for testing purpose $broadcast and $on. So I tried the following:
app.controller('FirstController', function ($rootScope, $scope, productService) {
$scope.showData = function (m) { //This is the event on which I'll get data in another controller as well in another view
alert(m); //This works and gets a name from the first view
$rootScope.$broadcast('sample', $scope.m); //This is what I am using to deliver in another controller
}
});
app.controller('SecondController', function ($scope) {
$scope.$on('sample', function (events, d) {
alert(d);
})
In another view, I used something like this:
<div ng-app="app" ng-controller="SecondController">
<ul class="nav navbar-nav">
<li> Product {{ m }}</li>
</ul>
</div>
Actually I am doing this all for demo purpose. But unfortunately, the above doesn't work. Am I missing something?
Update 1 - See the updated code:
app.controller('FirstController', function ($rootScope, $scope, productService) {
$scope.showData = function (m) { //This is the event on which I'll get data in another controller as well in another view
alert(m); //This works and gets a name from the first view
$timeout(function () {
$scope.$broadcast('sample', m);
});
}
});
app.controller('SecondController', function ($scope) {
$scope.$on('sample', function (events, d) {
alert(d);
})
In your scenario it will not work in one case:
You call $rootScope.$broadcast('sample', $scope.m); before
$scope.$on() is registered to listen on 'sample event a.e. before SecondController is created.
If you know that SecondController is created , you can wrap $rootScope.$broadcast('sample', $scope.m); with $timeout. a.e.:
$timeout(function(){
$rootScope.$broadcast('sample', $scope.m);
});
In this case $broadcast execution will be moved to end of events queue a.e. before next digest cycle that will be guarantee that Second Controller has been created and $scope.$on() is registered.
It's not entirely clear how you are using the second view & controller. Is it somewhere within the template that FirstController is assigned to? Seeing the template assigned to FirstController would help clarify. In any case, I've attached a simple plunker which shows how you can broadcast an event from a button click to a second controller.
https://plnkr.co/edit/KzNftVAYwPuCvsnflIz
Im using the same controller for 2 views in Ionic, because, its the same information I need.
Let me explain, at the view "config" I use ConfigCtrl, at this view I had a language option, when ng-clicked go to "languages", choose a lang and back to "config" with the same controller ConfigCtrl.
I use radio button component, and if I spec the value on controller, Its ok, the data has change, but at the "config" view its not updating!
Check the code.
<span class="item-note">
{{data.lang| translate}}
</span>
/\ Config.html
<ion-radio ng-model="data.lang" ng-click="saveLanguage()" ng-value="'pt_br'">{{'pt_br'| translate}}</ion-radio>
<ion-radio ng-model="data.lang" ng-click="saveLanguage()" ng-value="'en'">{{'en'| translate}}</ion-radio>
/\ Languages.html
.controller('configurationCtrl', ['$scope', '$rootScope', '$stateParams', 'UtilsFactory', // TIP: Access Route Parameters for your page via $stateParams.parameterName
function ($scope, $rootScope, $stateParams, UtilsFactory) {
$scope.data = {};
//Language scope
$scope.data.lang = $rootScope.language;
$scope.saveLanguage = function () {
$rootScope.language = $scope.data.lang;
setAnimation('right-animation', '/side-menu/configuration')
}
}])
/\ ConfigCtrl
If I debug the saveLanguage() when clicked its saves, change the value of data.lang, back to the Config.html view, but the value didnt change at the view of 'Config.html", I need to 'refresh' this field on a view, I cant refresh all the page because it will reload the $scope.data.lang
I am trying to open an Angular accordian in the header.html by clicking a button which is in the body.html. Essentially triggering an event in one view from a completely different view. Does anyone have any idea how to do this in Angular?
What you can do is using events to let your accordion directive know that something happend or use a shared service. Considering the performance, it does not make a huge difference, but only if you use $emit instead of $broadcast since the event fired via $emit bubbles up your scope hierarchy and $broadcast sends the event down. Also make sure to fire the event on the $rootScope, so it won't event bubble up anymore.
So you in case you want to use events for you could have a method on your component that fires the event via $emit on the $rootScope as follows:
function openAccordion() {
$rootScope.$emit('on-accordion-open', null);
}
You could then use this in your view, e.g. in body.html. Remember that function above is part of another directive / component or controller.
<button ng-click="vm.openAccordion()">Open Accordion</button>
Also note that I assume you are using controllerAs syntax (set to vm).
In your accordion directive you can then hook up listeners to several events for example the on-accordion-open:
$rootScope.$on('on-accordion-open', function() {
// Open the accordion
});
The other soltuion is to use a shared service. In this case I would create a AccordionServce that is aware of all instances of accordions. The service could look like this:
angular.module('myApp').service('AccordionService', function() {
var accordions = {};
this.addAccordion = function(name, accordion) {
accordions[name] = accordion;
};
this.removeAccordion = function(name) {
delete accordions[name];
};
this.getAccordion = function(name) {
return accordions[name];
};
});
In your accordion's controller you then add the accordion to the AccordionService via
accordionService.addAccordion('myAccordion', this);
The this in the snippet above is refering to the accordion controller. Thats important because if you then get an accordion in your component in the body.html, you'll get the controller instance and can call methods like open.
So in your body component you can then inject the AccordionService and get the accordion to call a method:
accordionService.getAccordion('myAccordion').open();
Make sure to define open on the accordion's controller.
I am new to angular Js.
My application flow is as below:
1) I have a view controller wherein, each view controller sets the breadcrumb data with the help of Breadcrumbs factory.
2) Breadcrumbs factory takes data from view controller and attaches data to $location.$$state object.(reason for storing in state object is if back button is pressed, view controller doesn't instantiate so I can refer history data for breadcrumbs ) below is code to attach data to state object:
var state = $location.state();
state.breadcrumb = breadcrumbData;
$location.replace().state(state);
3) I have also created breadcrumb directive on global header which will display breadcrumbs on $locationChangeSuccess event. Directive will take data from $location.state(); which was set in factory.
My problem is when location is changed, $locationChangeSuccess event callback function executes four times.
below is my directive code:
angular.module('cw-ui')
.directive('cwBreadcrumbs', function($location, Breadcrumbs, $rootScope) {
return {
restrict: 'E',
replace: true,
templateUrl: 'UI/Directives/breadcrumb',
link: function($scope, element){
//some code for element...
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
// get data from history of location state
var data = $location.state();
console.log(data);
});
}
};
});
output is as below:
Object {}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0], breadcrumb: Array[2]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
breadcrumb: Array[2] disappears 1st, 3rd and 4th times. I really don't know what is causing this callback function execute four times, and I have no clue about an issue and don't know how to debug. Please help guys!
After running into this myself, the problem lies in the fact you are using the root scope to bind the locationChangeSuccess event from within a directive that is either encountered multiple times on a single page, or encountered multiple times as you revisit the page:
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
Since you are binding to the rootScope, and the rootScope does not go out of scope, the event binding is not cleaned up for you.
Inside your link function, you should add a listener for the element $destroy, as well as capture the return value from the original bind, so you can later unbind it.
First: capture return value:
var unbindChangeSuccess = $rootScope.$on('$locationChangeSuccess' ...
Next, unbind that value in your destroy method:
element.on('$destroy', function() {
unbindChangeSuccess();
});
That should solve the multiple calls to your locationChangeSuccess! :)
I'm trying to design a single page pagination app that displays the different pages of a document beneath each other in the same window. It has to meet the following requirements:
A pagination toolbar where the user can click next/previous/... and submit a page to go to.
The window scrolls to the right page of the document after a page has been submitted
If the user scrolls manually, the current page should update automatically
I tried some different stuff but was never really satisfied with the result. This is how I see the solution:
The app consists of 1 factory:
DocumentFactory: Stores the current page of the document and has the following methods:
setPage(page): sets the page in a factory so different controllers/directives can use this page
broadcast(pageChanged): broadcasts an event after the page has changed so the controllers/directives can listen to this even and react approprialty
2 controllers:
PaginationCtrl[DocumentFactory]: The pagination toolbar controller, updates the page by calling the setPage(method) of the DocumentFactory and listens to the pageChange event to update it's own scope when the page changes in an other controller/directive
DocumentCtrl: The controller of the document
1 Directive:
Page[DocumentFactory]: Resembles a page in the document and has the following methods/listeners
scrollToPage(): If the currentPage equals this pages number (added to the directive as an attribute, scroll to this page)
If this page is visible and the highest in the window of all visible pages, change the current page to this page's number by calling the DocumentFactory setPage(page) method.
Is this the right approach to store the page in a service and use events for the other controllers/directives to listen to it?
Should I create a controller in the directive to listen to the event or add a $watch in the link function to watch for changes in the current page (inherited from the parent Ctrl scope)?
Should I let each directive check if it's page number equals the current page on page change or should I let the DocumentCtrl scroll to the right element?
AS you already have the methods in the controller calling the factory, what you need is to use the '&' isolate scope in the directive to call the method you want.
Create the methods in the controller
var app = angular.module('app', []);
app.controller("Ctrl", function ($scope) {
$scope.goForward = function (){
alert("call to the paginator page up service");
};
$scope.goBack = function (){
alert("call to the paginator page down service");
};
});
Then set up the '&' scope isolates in the directive:
app.directive('paginator', function (){
return{
restrict: 'E',
scope:{
forward: '&',
back: '&'
},
template: '<button ng-click="forward()">Page up</button>' +
'<button ng-click="back()">Page down</button>'
}
});
Finally add the directive to the page with your attributes as defined in the directive:
<paginator forward="goForward()" back="goBack()"></paginator>
Here's the code in Plnkr.
HTH