I use ng-view to display a view inside my main web page. This view has it's own controller and in there I populate ui-grid data. In my outer page I have import/export buttons which seem like they use the app's controller. So how can I access the views data (it's grid object) inside it's controller from the app's controller?
I'm using Angular 1.
<body ng-app="myApp" ng-controller="myController" style="background-color: yellow;">
<!-- the header that the entire app will use no matter what view is displayed -->
<div ng-include="'Views/header.htm'"></div>
<div style="height: 100%;" ng-view></div>
</body>
I have a button inside header.htm. When it's pressed it seems to be at the myApp scope as that's the function that gets raised. Inside that I need to tell whatever view controller is loaded to do something.
You can use $broadcast service to let your view controller know about click event in app controller.
angular.module('app').controller('AppController', ['$scope', '$rootScope', function($scope, $rootScope){
$scope.exportList = function() {
$rootScope.$broadcast('exportData', { object: test}); // If data needs to be passed you can pass it in event
};
}])
angular.module('app').controller('ViewController', ['$rootScope', function($rootScope){
$rootScope.$on('exportData', function(event, data){
if(data.object) {
// Export logic here
}
});
}])
Call exportLink function on ng-click in your view.
You have mentioned in question that your data is populating in ui-grid from ViewController, so you don't need parameters to be passed from view as you already have it in your $scope variable. If needed, use event argument as shown.
You can use broadcast to send event to the child scope and listen back using on. There's another one , $emit. Since you want to pass the event/messages to the child scope/downward you must use broadcast, $emit is the reverse.
Keep in mind : $emit dispatches an event upwards through the scope hierarchy, while $broadcast dispatches an event downwards to all child scopes
myController.js
$scope.buttonClick = function() {
$rootScope.$broadcast('pass-event');
}
Received event
ViewController.js (eg)
$rootScope.$on('pass-event', function (event, args) {
// do what you want to do
});
Related
Hello Angular experts,
I have to preload a certain dataset (factory call to the database) to the controller. I don't use angular views so stateProvider or routeProvider cannot be used to resolve. Basically I need the dataset readily available before loading the controller.
Is there a way to achieve this?
I have a controller and a view. The view also has a widget. The widget has an attribute that expects a dataset. By the time the controller is done fetching data the view is already rendered so the widget input parameters are empty. So I need the widget dataset to be filled much before getting to the controller. By the way the app.run solution doesn't work as there is a promise involved.
You can't say as before loading controller, i correct it with before binding controller
angularjs has app.run and i think you know it, it work just when application run (first time) and every time you refresh it.
var app = angular.module("app", []);
app.run(function($http, $rootScope) {
$http.get("url").then(function(response){
console.log(response.data)
$rootScope.data = response.data; // as global scope
})
})
app.controller('ctrl', function($scope) {
$scope.$watch('data', function(newValue, oldValue) {
if(newValue){
console.log(newValue) // you will get `rootscope.data` when it's ready
}
})
})
You can add factory to the app run too.
please fill free to ask question.
Based on your comment, what you are trying to do shouldn't require any changes to the Controller.
What you are actually trying to do is delay rendering of the widget component until you have data that the widget needs. This is commonly handled by ng-if in your HTML.
For Example (pseudocode):
<div ng-if="myData">
<Widget input="myData"></Widget>
</div>
Angular will not render this div until myData has a value, and all your Controller has to do is make sure myData is added to $scope.
I'm using angular-ui router and I have a very simple todo app I'm making to learn more about angular 1.4. If I have my <ui-view></ui-view> tags for my home controller, what is the best way to have a click in the header (to add a task effect my home view + controller?)
(My header HTML just has some anchor buttons, like add task, refresh list)
Header controller :
angular.controller('headerController', ['$scope', function($scope) {
$scope.addTask = function(){
//add a task or show toggle task form or call to something below in home controller
}
}]);
My home controller:
angular.controller('homeController', ['$scope', 'getTasks', function($scope, getTasks) {
$scope.todos = getTasks.load;
}]);
Should a service be made for something so simple? Or is this a job for rootscope, I know that rootscope should be avoided though as it pollutes global namespace. Thank you
According to the below image:
I want to improve components communication method....I think this way is not efficient.
When clicking tabsetComponent to emit event, then parent controller catch this event, changing rootScope variable. Using $watch rootScope variable in tableComponent to trigger http fetch data function...
Could anyone has better and efficient way to communicate sibling component?
The accepted AngularJS method for communication between components is using component attributes for communication.
<div ng-controller="rootCtrl as vm">
<tab-set-component tsc-click="vm.fn($event, data)">
</tab-set-component>
<table-component="vm.tableData">
</table-component>
</div>
For more information on defining component attributes, see AngularJS Comprehensive Directive API -- isolate scope
Best practices
Only use .$broadcast(), .$emit() and .$on() for atomic events
Events that are relevant globally across the entire app (such as a user authenticating or the app closing). If you want events specific to modules, services or widgets you should consider Services, Directive Controllers, or 3rd Party Libs
$scope.$watch() should replace the need for events
Injecting services and calling methods directly is also useful for direct communication
Directives are able to directly communicate with each other through directive-controllers
-- AngularJS Wiki Best Practices
Controller Example
In your html, you use vm.fn that came from root controller right? So your advice is it should call the click method defined root controller, the click method will trigger http request function defined on the rootScope, then get table component datas, then bind the datas on table component attribute.
As example:
angular.module("myApp", []);
angular.module("myApp").controller("rootCtrl", function($http) {
var vm = this;
vm.tableData = { /* initial data */ };
//click handler
vm.fn = function(event, url) {
$http.get(url).then (function onFulfilled(response) {
vm.tableData = response.data;
}).catch (function onRejected(response) {
console.log(response.status);
});
};
});
The above example avoids cluttering $rootScope. All the business logic and data is contained in the controller.
The controller sets the initial data for the table-component, receives click events from the tab-set-component, makes HTTP requests, handles errors, and updates the data to the table-component.
UPDATE -- Using Expression Binding
Another approach is using expression binding to communicate events:
<header-component view="root.view" on-view-change="root.view = $event.view">
</header-component>
<main-component view="root.view"></main-component>
For more information, see SO: How to pass data between sibling components in angular, not using $scope
With version 1.5.3, AngularJS added the $onChanges life-cycle hook to the $compile service.
app.component("mainComponent", {
template: "<p>{{$ctrl.count}}",
bindings: {view: '<'},
controller: function() {
this.count = 0;
this.$onChanges = function(changesObj) {
if (changesObj.view) {
this.count++;
console.log(changesObj.view.currentValue);
console.log(changesObj.view.previousValue);
console.log(changes)bj.view.isFirstChanged());
};
};
}
});
For more information, see AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
See also SO: AngularJs 1.5 - Component does not support Watchers, what is the work around?
I am migrating an AngularJS multiple-page app to a single-page app, and I am having some trouble to replicate the following behaviour:
Each HTML file has a different base controller and a ng-view. For example, file1.html looks like this:
<body ng-controller="BaseCtrl1">
<!-- Routes to /view11, /view12, etc. with their corresponding controllers -->
<div ng-view></div>
</body>
<script src="file1.js"></script>
file2.html uses BaseCtrl2, routing to /views21, /view22 and so on. Each of this controllers initialize the scope, and the corresponding subset of views share this part of the model:
file1.js:
module.controller('BaseCtrl1', function($scope, ServiceA, ServiceB, ServiceC) {
// Populate $scope with ServiceN.get() calls
ServiceA.get(function(response) {
$scope.foo = response.results;
});
});
// ...
file2.js:
module.controller('BaseCtrl2', function($scope, ServiceX, ServiceY) {
// Populate $scope with ServiceN.get() calls
});
// ...
However, with a single-page app I cannot use a fixed parent controller (declared in the body element) for each different group of views. I have tried using the $controller service like in the answer of this question, but I need to inject all the dependencies of the parent in the child controller, and does not look like a neat solution at all:
module.controller('View11Ctrl', function($scope, ServiceA, ServiceB, ServiceC) {
$controller('BaseCtrl1', {/* Pass al the dependencies */});
});
module.controller('View12Ctrl', function($scope, ServiceA, ServiceB, ServiceC) {
$controller('BaseCtrl1', {/* Pass al the dependencies */});
});
I would like to know if there is a way to replicate the original behaviour by initializing a "common" part of the scope of a group of views, and maintain it when changing the route.
You can use $injector.invoke() service method to achieve this.
module.controller('View11Ctrl', function($scope, ServiceA, ServiceB, ServiceC) {
$injector.invoke(BaseCtrl1, this, { $scope: $scope });
}
The third argument is defined as:
If preset then any argument names are read from this object first,
before the $injector is consulted.
This way you only need to pass the locals to your base controller that is specific to your child controller, and all other base controller dependencies will be resolved using the normal $injector DI.
How can a html element outside of a controller communicate with a given controller ?
The situation is as following:
<button name="search">Search</button> --> in an existing layout provided by an existing framework
<div ng-app ng-controller="overviewCtrl">
<div ng-view>/div> --> this one gets a specific controller
</div>
<button name="search">Search</button> --> in an existing layout provided by an existing framework
I don't have control on the location of the button outside of the controller.
I can put attributes on it and I want to put an ng-click attribute.
It also fall outside the ng-app.
I could put a controller on it. But then I need a way to have a reference to the same controller.
What is the best way to do this ?
One way to do this is to use JQuery inside the Angular controller to bind the element outside the ng-app to a scope function through JQuery selector and click event.
You don't have control over its position, but you do have control over its rendering in some way? You say you could put an ng-click on it?
I ask because there's an easy dodge here if that's true. You could make a simple directive that's basically a "smart ng-click". Your issue with ng-click is really that it's hard to route its call into your controller because the button is elsewhere. What you need, then, is just a way to bridge the two.
It would look something like this:
angular.module('myApp', [])
.controller('MyController', ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.$on('SearchButtonClicked', function() {
// I will be called whenever the search button is clicked...
// and I am in my controller!
});
}])
.directive('notifySearchClick', ['$rootScope', function($rootScope) {
return {
restrict: 'A',
link: function($scope, iElement) {
iElement.bind('click', function() {
$rootScope.$emit('SearchButtonClicked');
});
}
};
}]);
Working Plunkr demonstrating this concept here:
http://plnkr.co/edit/dKuDl5?p=preview
The jQuery way is also perfectly valid but then you're back to the "what mystery event handlers are on THIS element" situation. It works, and it's reliable... but if you want to keep to the AngularJS philosophy, using a directive like the one above gives you a lot of flexibility.
Note that in the Plunkr reference above I added a tiny bit of sophistication. In that example I made the message name an attribute. You could thus use a single directive for any other cases like this in the future. Just put the message name into the attribute, like this:
<button notify-click="MyButtonNameClick"></button>