Thanks to an ongoing integration, I'm currently trying to get 2 JS applications running on the same page (one in Angular 2, other in React) to communicate with each other. Currently, all the data sharing happens between the 2 using an object in the window scope of the page (not ideal, but straightforward) and since data is non-critical, it'll make do. This is what that looks like
<script>
window.sharedStuff = {oscar_winner: "LALALAND", news: "FAKE"}
</script>
// The angular 2 Application
<my-app>
</my-app>
// React App
<div class="react-app">
</div>
However, I'm having issue setting up callbacks from the Angular application which the React application can listen to and perform some action upon. Basically, when a specific event occurs in an Angular component, I want to inform the React application that it has occured. How can I do this? I have access to the window scope from both Angular and react, so ideally, I want to use the window.sharedStuff itself to define a callback which the react application can bind to. But I can't find any documentation on how to go about this (Perhaps because this is a unique/weird situation).
How would I go about this? Help much appreciated!
For simple cases like yours you can always create mediator or some implementation of Observable pattern. For example quick and simplest one could looks like this:
window.observer = {
listeners: {},
subscribe (event, callback) {
this.listeners[event] = this.listeners[event] || []
this.listeners[event].push(callback)
},
broadcast (event, data) {
if (Array.isArray(this.listeners[event])) {
this.listeners[event].forEach(callback => callback(data))
}
}
}
And it could be used by Angular 2 app to push data with broadcast method. React app on other hand could subscribe on the same window.observer and set it's state:
window.observer.subscribe('name.selected', name => {
this.setState({ name })
})
Check the simple demo with Angular 2 and React apps talking like good friends: http://plnkr.co/edit/NT1eGQUvoKSAnH9ZwgeB?p=preview
Why not add an event listener to the window object and have your React app fire the event which is picked up by Angular and vice versa?
window.addEventListener('click', function () { alert('hello world') });
Related
We have various components in the application that are not in parent/child or sibling relationships. Let's say a checkbox that when in checked state is supposed to change the state of another component which is in a completely different container.
The application is over 500 different views, so a controller for each one is not an option. Those interactions are also completely custom, so we would need tens of methods to cover all of them (checkbox to tab, multiple checkboxes to tab, multiple checkboxes to more checkboxes etc).
What is the best course of action here? So far we thought about a globally available service to register components by id and then subscribe the dependent components to listen for the status change on that particular id in the service (for example in an ng-if directive to toggle), or use Redux. We have no previous experience with complex relationships like that.
Any ideas or similar experiences would be greatly appreciated.
The Observer pattern as you describe it is being implemented in angularjs with event emmiters ($broadcast $emit) so there is no need to create an independent service.
The point of component based applications is to have some tree structured architecture. So in those cases the child component notifies the parent and then the parent notifies some other child maybe and goes on.
If your application is not structured like this you might consider a refactoring but for now you could just bind some event emitters.
To solve this issue use the publish/subscribe pattern that allow get a loosely-coupled architecture.
On an AngularJS application a great library is postaljs that allow implements this pattern easely:
Define at app.config a $bus $scope variable that will be accesible on all places of the application: controlers, directives, ...
app.config(function($provide) {
$provide.decorator('$rootScope', [
'$delegate',
function($delegate) {
Object.defineProperty($delegate.constructor.prototype,
'$bus', {
get: function() {
var self = this;
return {
subscribe: function() {
var sub = postal.subscribe.apply(postal, arguments);
self.$on('$destroy',
function() {
sub.unsubscribe();
});
},
channel: function() {
return postal.channel.apply(postal, arguments);
},
publish: function() { postal.publish.apply(postal, arguments); }
};
},
enumerable: false
});
return $delegate;
}
]);
});
Publish
Publish on item updated.
var channel = $scope.$bus.channel('myresources');
channel.publish("item.updated", data);
Publish on list updated
var channel = $scope.$bus.channel('myresources');
....
channel.publish("list.updated", list);
Subscribe
The controller/directive that needs be notified for an event on the "myresources" channel.
var channel = $scope.$bus.channel("myresources");
....
//The wildcard * allow be notified on item/list. updated
channel.subscribe("*.updated", function(data, envelopment) {
doOnUpdated();
});
So, I'm trying to setup Aurelia in my Angular 1 web application so I can slowly upgrade. I need to do that since the application is too big and migrating everything at once would be impossible.
So, in my Aurelia folder I created a component folder with two components (aurelia-component.js and another-component.js with their views aurelia-component.html and another-component.html), I won't put the javascript as they are just two classes with one property, the html for both is the same, the only thing that changes is the text property value so I can differentiate them:
<template>
<div>${text}</div>
</template>
My entry point main.js looks like this:
export function configure(aurelia) {
aurelia.use
.basicConfiguration()
.developmentLogging()
.globalResources('components/aurelia-component')
.globalResources('components/another-component');
//window.aurelia = aurelia;
aurelia.start()
.then(a => {
window.aurelia = a;
});
}
As you can see, this puts Aurelia in the window object so I can access it from my Angular app, I'll improve this later.
In my angular app I have this directive:
'use strict';
function AureliaContainer() {
function Link($scope, element, attrs) {
window.aurelia.enhance(element[0]);
}
//
return {
restrict: 'A',
link: Link
};
}
module.exports = AureliaContainer;
I set this up in my app root with:
app.directive('aureliaContainer', require('./directives/aurelia.container'));
And in my Angular View I have these divs with my directive that calls the enhance function from Aurelia:
<div aurelia-container>
<aurelia-component></aurelia-component>
</div>
<div aurelia-container>
<another-component></another-component>
</div>
The reason I have two aurelia-container in the html is that I know I'll have to have more than one when I'm migrating the application.
And this works fine, both components load normally in the screen.
The problem is when I try to call another component from within one of those components.
What I did was, I created a new component called test-component.js with its view test-component.html. The html for this is just:
<template>
<h1>Header</h1>
</template>
And then, from the aurelia-component.html I called it using:
<template>
<require from="./test-component"></require>
<div>${text}</div>
<test-component></test-component>
</template>
Now, when I load the page, the test-component actually loads but the <div>${text}</div part of aurelia-component doesn't and I get this error in the console:
Uncaught (in promise) TypeError: Cannot read property 'behaviorInstructions' of undefined
I really don't understand why this error is happening, I should be able to load a custom element from within another one normally, shouldn't I. Or is there a limitation when you use enhance?
I also tried to use setRoot in both divs with no success, just one of them is loaded.
Maybe there's a better approach for this?
Again, I can't migrate my entire application at once, it's just no feasible.
Thanks in advance for the help.
First off, I know nothing about progressive enhancement in Aurelia. And cannot comment about its suitability for your scenario.
But I am wondering if maybe you missed some Au dependencies (like binding or templating?)
http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/app-configuration-and-startup/8
aurelia.use
.defaultBindingLanguage()
.defaultResources()
.developmentLogging()
.globalResources('resources/my-component');
That might explain why it fails when you want it to render a template?
I'm using SignalR with an Angular2 app, where we want the SignalR client methods to call into the Angular app with data received from the server, and then have Angular redo the data-bindings. For example, within the Angular app I expose a global variable for our store, which has a collection on it.
E.g.
(TypeScript)
....
export class Store{
Customers : Customer[];
constructor(){
window["GlobalStore"] = this;
}
setCustomers (customers : Customer[]){
this.Customers = customers;
}
}
....
and in my client SignalR javascript I have a function:
$.connection.MyHub.client.receive = function(data){
//Call into the Angular app and set data, which is then rendered in views
//via data-binding
//data contains a json array of customers
window.GlobalStore.setCustomers(data);
}
This seems to work and set the data on the store, however, when the data is reset Angular does not seem to detect changes, and hence the UI is not refreshed.
It's not an issue with data-typing, as even passing a simple string/integer etc through to the store correctly sets the store property when I debug, however, the Angular framework doesn't seem to then trigger change detection and refresh the views.
Any ideas on how to either:
A) Manually trigger the angular databinding so it refreshes the view?
B) Call methods within the Angular 2 app from external using a different means?
Thanks
To manually run change detection:
Use ApplicationRef::tick() method.
Use NgZone::run() method to wrap you code which should be executed inside angular zone.
You can get them by using dependency injection or by bootstrapping your application using platform().application(bindings).bootstrap(Component):
import { platform } from 'angular2/angular2';
const app = platform().application([] /* - bindings */); // you can use `app.tick()`
const zone = app.zone; // you can use `zone.run`
app.bootstrap(Component);
From what I've seen, most people use an external service that 'just works' and then they throw in some code in an initializer to report the errors.
In my initializer, so far I have:
Ember.onerror = function(error) {
displayError(error);
};
and displayError shows an error notification (similar to the Atom editor's error notifications)
But as I'm trying to add functionality to my displayError function (appending multiple errors, close one / all, showing stacktrace, eventually posting issues to github, etc), I've been finding that it's just VERY cumbersome to go back to vanilla javascript, and am wondering if there is a way to utilize ember to report errors that happen in my app.
Does anyone either:
- Know of an addon that does all this for me (I'm not interested in using a bug reporting service other than github)
- Know of a way to have Ember.onerror trigger an action in the application controller so I can have use components to render the errors, and have much easier development implementing what I need?
You can always try and use something like Ember.Instrumentation.
This will allow you fire off an event from inside the Ember.onerror callback that you can subscribe to anywhere else in your app. So let's say you create a component that lives at the root of your application. You can then subscribe that component to the error event and trigger when to display the error. Ember.instrument() also allows you to pass a payload which can be the error object itself.
In the root of your app you can have something like this:
Ember.onerror = function(error) {
Ember.Instrumentation.instrument("app.error", error);
};
Then in you're component you can subscribe to events in the init function like this:
init: function() {
Ember.Instrumentation.subscribe("app.error", {
before: function(name, timestamp, payload) {
// do logic here with error
},
after: function() {
// optional cleanup work here
}
}
}
I am wondering if there is a convention within AngularJS for creating an object that lives within the app module, but is not attached directly to the view in any way, but is called when the view has loaded and the app starts up. In particular, I am trying to write an object that dispatches messages to listening controllers when they come in from the server.
Currently, I have implemented this by creating a "Controller" that attaches to the view. It has a monitor() function that is called when the page loads, and then listens in a loop for any incoming messages. I call the monitor() function from within the loaded view, by setting the ng-controller like so:
<div ng-controller="MyController">
{{ monitor() }}
</div>
This doesn't feel like the right thing to do. This "Controller" isn't interacting with the view in any way, so my gut tells me I am violating principles of AngularJS. But I haven't been able to turn up an easy solution that is endorsed by the AngularJS doc.
I am looking for a way to create an object that lives within the AngularJS world (in other words, it can use dependency injection to get access to services, and it can use $scope.$broadcast to send messages to other listening controllers), but that doesn't need to attach itself to the view in any way.
Ideally, I am looking for a way to say, "Here Angular, on startup, create this object, and run this method on it." Is there a way to do this?
You may use this as a starting point:
declaration of your object.
AngularJS: Service vs provider vs factory
myApp.factory('MessageBus', function() {
return {
listeners: [],
init: function() {
// do whatever you need at startup
},
pushMessage: function(msg) {
angular.forEach(this.listeners, function(listener) {
listener(msg);
});
},
subscribe: function(onMessageCallback) {
this.listeners.push(onMessageCallback);
}
};
});
calling a method on angular appilcation start
https://docs.angularjs.org/api/ng/type/angular.Module#run
myApp.run(function(MessageBus) {
MessageBus.init();
});
using this object within controllers
https://docs.angularjs.org/guide/di
myApp.controller('MessageCtrl', function($scope, MessageBus) {
$scope.messagesToShow = [];
MessageBus.subscribe(function(message) {
$scope.messagesToShow.push(message);
});
$scope.submitMessage = function(id, text) {
MessageBus.pushMessage({
type: 'TEXTMESSAGE',
id: id,
payload: text
});
};
});
Note that this is something to start with and nothing for any production code. For example the controller doesn't unsubscribe after being destroyed - if the page changes - and so you leak memory.
Don't use $broadcast-events for this
1: they are slow
2: if this MessageBus has a specific concern, than in should be an own object with a meaningfull name and api. Otherwise your $rootScope will be flooded with thousends of different events for different concerns when your application grows. A service is always easier to document and you have a clean dependency on that specific service. Only using events on the $rootScope hides this dependency from every developer reading and hopefully understanding your codebase,
Yeah you approach is really smelly. This function will be called every time a $apply/$digest invokes.
Maybe move the function into the run callback on the module.
var app = angular.module("YourApp", [//dependencies]);
app.run(function($YourUIService){
$YourUIService.monitor();
});
The run will be invoked, when your angularjs-module has loaded every dependency and is ready to run.
Didn't find the doc for this :/