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) {}
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 an angular app where we have 4 different pages.Each page has its own controller. There is an home page which has a controller which routes to each page and its controller using
when('/a',{
templateUrl: './components/a.html',
controller:'aCtrl'
}).
when('/b',{
templateUrl: './components/b.html',
controller:'bCtrl'
}).
when('/c',{
templateUrl: './components/c.html',
controller:'cCtrl'
}).
when('/d',{
templateUrl: './components/d.html',
controller:'dCtrl'
}).
when('/home',{
templateUrl: './components/Home.html',
controller:homeCtrl'
}).
Now I want to share some data or some common functions between these controllers/pages. How can we do this? I googled it they say to use SERVICE. But I don't know in which controller I need to write the service. Can anybody give a good example for this.
A service in AngularJS is not written within a controller. It is bound to your app directly and can be used anywhere within your application. This is why Services are the recommended means of communication between controllers in AngularJS.
What you need to do is write a service like so:
angular.module('yourApp').service('serviceName', function () {....});
Within the service, you can:
Fetch data from an API end point (You can use the $http provider for this)
Define constant data (You can use Angular's constant provider for this)
Define some code that takes in some data and manipulates it and returns new data
Pretty much anything else you want to do with your data
Now, include the service in your controller as a dependency like so:
angular.module('yourApp').controller('yourController', function (serviceName) {
console.log(serviceName.getData());
// Do something with your data
});
Now within this controller, you have access to the data that the service has returned. Of course, the same service can be injected into multiple controllers, thereby making it possible to share data across controllers.
There are many ways you can share data.
event
services
$rootScope
Services provide an easy way for us to share data and functionality throughout our app. The services we create are singletons that can be injected into controllers and other services, making them the ideal place for writing reusable code.
var app = angular.module('app', []);
app.controller('leftCtrl', function ($scope,userService) {
left.btnClicked = function (object) {
userService.saveData(object);
}
});
app.controller('rightCtrl', function ($scope, userService) {
$scope.getData = userService.getData();
});
app.service('userService', function () {
var data = {};
this.saveData = function(object){
data = object;
}
this.getData = function(){
return data;
}
});
Dustin has the right approach. However there are times when you could use a different approach and that is to wrap the application in an AppController.
Everything that is in AppController can now be accessed. You could use this approach to put functions or constants that you want the child controllers of the application to have access to and don't have to inject services everywhere.
<body ng-controller="AppController">
<div ng-view></div>
</body>
The short story is that I'm trying to lever some data model code that's not written for angular in particular into an angular application. This code is written using ES6 import / export syntax for modules, and I'd like to keep using that. So I have something like:
export class DataModel {
//some stuff with promises
}
What I did was create a utility module that exposes the relevant Angular (1.5) services to the ES6 module system thusly:
import angular from 'angular';
export const services = {};
angular.injector(['ng', 'toastr']).invoke([
'$q',
'$http',
'$rootScope',
(
$q,
$http,
$rootScope
) => {
services.$q = $q;
services.$http = $http;
services.$rootScope = $rootScope;
},
]);
Then I can just import the $q library into my DataModel classes and hey presto, everything kind of works - I'm doing promises, and the appropriate scopes should update when the .then methods fire.
The problem is that this doesn't actually work. I'm 90% sure that the reason this doesn't work is that the $rootScope element I get from the angular.injector call isn't a singleton rootscope, it's a fresh new one that gets created just for this context. It does not share any scope linkage with the actual scope on the page (I can confirm this by selecting a DOM element and comparing services.$rootScope to angular.element($0).scope().$root). Therefore, when a promise resolves or a $http returns, I get the data but have the standard symptoms of not notifying a scope digest in the interface (nothing changes until I manually trigger a digest).
All I really want is a copy of the $q, $rootScope and $http services that angular uses live in the active page. Any suggestions are welcome. My next try will be to see if I can grab the relevant services from some .run block where I inject $q et al instead of doing it with the injector. That introduces some problematic timing issues, though, since I need to bootstrap angular, run the run block, and then expose the services to my data model. But the bootstrapping process requires the datamodel. It's a bit circular.
I'm answering this myself for now, but would love to see any other ideas.
I changed the angularServices code to look like:
import angular from 'angular';
import { Rx } from 'rx-lite';
export const servicesLoaded = new Rx.Subject();
export const services = {};
angular.module('app.services', []).run([
'$q',
'$http',
'$rootScope',
(
$q,
$http,
$rootScope
) => {
services.$q = $q;
services.$http = $http;
services.$rootScope = $rootScope;
servicesLoaded.onCompleted();
},
]);
Since I was already using rx-lite anyway. This allows me to do
import { services } from 'angularServices';
services.$http(options) // etc;
whenever I'm working in code that is run after the application bootstrap cycle. For the code that was running prematurely (it was just config stuff that was in a few places, I wrapped it inside the RxJS event thusly:
import { services, servicesLoaded } from '../../common/angularServices';
servicesLoaded.subscribeOnCompleted(() => {
services.$rootScope.$on('$stateChangeSuccess', () => {
//etc
That way I don't try to get in touch with $rootScope or $window before it actually exists, but the $q, $rootScope, and $http I've stashed a reference to in my services object is actually a real thing, and digests all fire properly.
And now hey presto, while my model layer references $http and $q, they'll be pretty easy to exchange with some other provider of promises and XHRs, making all the work I put into that not bound to angular 1.x. Whee.
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 have some application settings that i want to retrieve from backend, so that they would be available to all of my application controllers via injection. What is the most angular-way to do that?
1) If i only needed settings for one or few controllers i could retrieve them via routing resolve method, but what about global application scope?
2) I could use the .run() method, but since call will be async i have no guaranties that my config will be loaded before i access controllers.
Currently my settings are returned as a json object, and my templates/html files are simply served by web server. So i cannot embed anything into script tags, parse html on the server side or any similar technique.
I would create a service for your settings. Then using the .run() method, called a service that returns your app settings data:
angular
.module('YourApp',[])
.service('Settings',function(){
this.data = {}
})
.run(function(Settings,$http){
$http
.get('/ajax/app/settings')
.success(function(data){
Settings.data = data
})
})
function Controller($scope,Settings){
// use your Settings.data
}
http://docs.angularjs.org/api/angular.Module#methods_run
There is a neat plugin to do the job. https://github.com/philippd/angular-deferred-bootstrap
You need to change bootstrap method
deferredBootstrapper.bootstrap({
element: document.body,
module: 'MyApp',
resolve: {
APP_CONFIG: function ($http) {
return $http.get('/api/demo-config');
}
}
});
angular.module('MyApp', [])
.config(function (APP_CONFIG) {
console.log(APP_CONFIG);
});