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?
Related
We're not using AngularJs as a SPA but embedded module to manage some behavior and shared data, so we're not actually utilising something like angular router. How should I initialize components only after a shared data service finished an asynchronous request? AngularJS was used with Typescript
Angular Module
import ... from '...'
import ... from '...'
...
angular.module('app-1', [])
.service('data-service', DataService)
.component('zeroDateButton', new ZeroDateButtonComponent())
.component('zeroPanel', new ZeroPanelComponent())
.component('zeroChart', new ZeroChartComponent())
ASP.NET Page hosting Angular module
BI.aspx
<asp:Content ID="standardContent" ContentPlaceHolderID="MainContent" runat="server">
...
<zero-date-button></zero-date-button>
<zero-date-button></zero-date-button>
<zero-panel name="panel-1"></zero-panel>
<zero-panel name="panel-2"></zero-panel>
<zero-panel name="panel-3"></zero-panel>
<zero-chart></zero-chart>
...
<script src="Scripts/Components/component.app-1.js) "></script> //compiled angular module js file
</asp:Content>
Page URL: https://www.example.com/BI/Zero
DataService.ts
public tryGetData() {
return $http.get(url).then((res: any) => {
this.panels = res.panels;
});
}
ZeroPanelComponent.ts
...
public $onInit(): void {
this.panels = this.dataService.panels;
this._render();
...
Most of the logics for this module relies on the data from the three components, so I want to fetch and store them all together in the data service, from which each component access the data they need from this service, and let the service figure out the logics and tell each of them by broadcasting the events.
Upon the components initialization(in $onInit method), it should display things using data retrieved from data service. The problem is component initialization is not awaiting data service to finish data fetching, so the component can't get the data they need and render nothing.
Trial with $routeProvider
I've seen seen lot's of people advising $routeProvider with appModule.config(), however it was not working. I'm not sure if this solution will work considering the way we use Angular, but I'm still posting the code snippet.
angular.module('app-1', ['ngRoute'])
.config(($routeProvider) => {
$routeProvider
.when('/BI/Zero', {
template: '<zero-panel class="flex-basis-half marginBottom" panel-name="SalesSnapshot", container-id="sales-snapshot"></zero-panel>',
resolve: {
DataService: (DataService) => {
return DataService.tryGetData();
},
},
});
})
.service('zero-data-service', DataService)
...
and I added ng-view directive to one in BI.aspx
There's NO error in browser, <zero-panel> is not rendered and tryGetDate() is not called too. I found someone said the 'path' defined to when() is part of the URL after the # symbol. Could you verify if this is true?
In terms other solution, the most intuitive thing I can think of is broadcasting an event when data service has obtained the data, and components listen to event to fetch the data, instead of fetching during their initialization.
I appreciate if anyone can suggest if $routeProvider would work in my usecase, or suggest any other possible solution to achieve the goal.
I have a hybrid angular application tha uses both angularJS and angular 5.
I'm migrating a controller from angularJS to angular 5. This controller (let's call controller1) have the following code:
$rootScope.$emit('goToHomeEvent', 'Navigate to HomePage');
Then, I have another controller (let's call controller2) that subscribes the goToHomeEvent:
$rootScope.$on('goToHomeEvent', function (event, data) {
//Do some stuff
});
My goal is to only migrate the controller1 to angular 5, mantaining the code of controller2 has it is, maintaining the ability to subscribe the goToHomeEvent event.
Any suggestions?
I think that you could probably make usage of the localStorage for this as they dont't really communicate between each other.
I would suggest you to populate the localStorage on emit and watch it in the controller2. It's probably going to work although I don't know if it's the best approach for your problem.
The solution I came up is to create global functions and associate them to them to the window variable like this:
controller1:
$rootScope.$emit('goToHomeEvent', 'Navigate to HomePage');
window.goToHomeEvent(data);
controller2 (in angular 2+):
window["goToHomeEvent"] = fuction(data) {
//do stuff here
}
Our solution was a variation on the global. In the angularJS file at top level that calls .module('app').run(fn), we inject the $rootScope, $q, $injector, and $state and set them to a global var app (as opposed to directly onto the window object). So Angular2+ calls app.$rootscope.etc.
app.$q is effectively the browser's native/polyfilled Promise.resolve and related functions, so $q's replacement doesn't need to be injected into newer code.
app.$state is just for router concerns. Once you change over to Angular2's router, re-implement the parts of $state that you actually use in terms of the new router.
app.$injector is useful for a lot of oddball situations.
i've solved by global function without emit/broadcast:
AngularJS:
$window.bridgeFormAngular = function (params)
{
console.log(params);
}
Angular 7:
public sendToAngularJS(params: Object)
{
window["bridgeFormAngular"](params);
}
You can upgrade the $rootscope of angularjs and use it inside the Angular side of the hybrid.
You will have to follow the following steps:
1 - Register the '$rootScope' service as a provider inside Angular:
#NgModule({
providers: [{
provide: '$rootScope',
useFactory: ($injector: any) => $injector.get('$rootScope'),
deps: ['$injector']
}]
})
2 - Use the $rootScope inside your Angular components or Services via injection:
constructor(#Inject('$rootScope') private _rootScope: any) {}
constructor(#Inject('$rootScope') private _rootScope: any) {}
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 have a page.html with a directive in it. I also have a service which I want to use as a manager of events between the directive and the page.html controller.
I'm new to Angular so I don't know if I'm approaching this the right way. I want the service to update the directive if say my controller has a button clicked. Do I just put the directive as a dependency for my service?
No. You have to inject your service in your directive.
The service should handle communication with your server or strictly business logic, best you must separate into two services if you have communication and business logic.
Directive is only for the visualization part.
Events should be placed into directive "link" function and visualization functionality into "controller" but remember. Angular is not jQuery so many events like "click" are obsolete in angular by that i mean they handled automatically by a property in the controller.
This sounds like a fine approach and is often necessary, since services are singletons and controllers and directives are not. You will inject your service into both your directive AND the page controller. This way both can act on the service and the service can act as a bridge between the two.
app.service("myService", function() {
//define your service
});
app.directive('myDirective', function (myMervice) {
return {
template: '...',
restrict: '...',
scope:true,
controller: function($scope) {
//you can use the service here
}
};
});
app.controller("myController", function($scope, myMervice) {
//you can use the service here
});
In new angular components, $scope cannot be injected so I can't use the standard $emit.
How to I emit a value from a component to parent controller?
I have this in appCtrl:
$scope.$on('eventName', function (event, args) {
this.pageTitle = args.pageTitle;
}.bind(this));
Usually I have done it using:
this.scope.$emit('eventName', { pageTitle: _this.campaign.title });
I have a similar issue. I need to do a $scope.apply() in a component.
Check this issue here How can we watch expressions inside a controller in angular 1.4 using angular-new-router
I am not sure if using / injecting $scope into a component is the way to go because of the migraiton path to Angular 2. Maybe there is a better way using zone.js https://github.com/angular/zone.js/