how to make a relations between angular 1 components? - angularjs

i'm looking for some solution to work with angular 1.5 components in a way that a change in one component will change something in other component in another place in the tree.
what so you think is the best solution?
rootScope?
redux?
events?
some other global variable?

If you want your own service without redux or anything else, you can implement a service like :
angular.module("my_service", [])
.factory("Message", function() {
var messages = {
// m1: [],
// m2: []
};
function receive(message, messageHandler) {
if (!Array.isArray(messages[message])) {
messages[message] = [];
}
messages[message].push(messageHandler);
}
function send(messageName, message) {
if (Array.isArray(messages[messageName])) {
messages[messageName].forEach(function(messageHandler) {
messageHandler(message);
});
} else {
console.warn("sent message", message, "is not in the message list...");
}
}
return {
send: send,
receive: receive
};
}
});
And in somewhere else you can create two controllers for example :
angular.module("app", ["my_service"])
.controller("app1", function(Message) {
Message.receive("sthHappened", function(whatHappened) {
console.log("app1 says :", whatHappened);
});
})
.controller("app2", function(Message) {
Message.send("sthHappened", "app2 initiated");
});
Basically you register a function in order to execute when a message received, and a trigger when something happened in order to execute the registered functions.
Of course you may need to make some additions according to what you need and improve performance or async op in order to break the sequential execution, but this the basic structure of a publisher-subscriber mechanism in order to create a messaging channel between your controllers or directives or any functions.

Related

Using Angular Material, is it possible to close a specific dialog

I have an AngularJS app using the Angular Material UI framework.
The app has different mechanisms showing dialogs (e.g error and loading spinner) and it would be preferable to only close one specifically chosen in certain scenarios, e.g. when an AJAX request is finished fetching data, I would like my loading spinner to close, but not any error dialog that may be the result of the fetching.
What I can find in documentation and code doesn't agree (though code should win the argument):
Documentation says only the latest can be closed, with an optional response
The code says the latest, a number of latest or all open can be closed, with an optional reason
Example in the documentation says a specific dialog can be closed, with a flag denoting how or why
I have made a demo of my intent, as MCV as possible – these are the highlights:
var dialog = {},
promise = {};
function showDialogs(sourceEvent) {
showDialog(sourceEvent, "one");
showDialog(sourceEvent, "two");
}
function showDialog(sourceEvent, id) {
dialog[id] = $mdDialog.alert({...});
promise[id] = $mdDialog.show(dialog[id]);
promise[id].finally(function() {
dialog[id] = undefined;
});
}
function closeDialogs() {
$mdDialog.hide("Closed all for a reason", {closeAll: true});
}
function closeDialogLatest() {
$mdDialog.hide("Closed from the outside");
}
function closeDialogReason() {
$mdDialog.hide("Closed with a reason");
}
function closeDialogSpecific(id) {
$mdDialog.hide(dialog[id], "finished");
}
EDIT:
I know the code always wins the argument about what happens, but I wasn't entirely sure it was the right code I was looking at.
I have updated the examples to better test and illustrate my point and problem. This shows things to work as the code said.
What I'm really looking for is whether it might still be possible to achieve my goal in some other way that I didn't think of yet.
Using $mdPanel instead of $mdDialog I was able to achieve the desired effect; I forked my demo to reflect the changes – these are the highlights:
var dialog = {};
function showDialogs() {
showDialog("one");
showDialog("two");
}
function showDialog(id) {
var config = {...};
$mdPanel.open(config)
.then(function(panelRef) {
dialog[id] = panelRef;
});
}
function closeDialogs() {
var id;
for(id in dialog) {
closeDialogSpecific(id, "Closed all for a reason");
}
}
function closeDialogSpecific(id, reason) {
var message = reason || "finished: " + id;
if(!dialog.hasOwnProperty(id) || !angular.isObject(dialog[id])) {
return;
}
if(dialog[id] && dialog[id].close) {
dialog[id].close()
.then(function() {
vm.feedback = message;
});
dialog[id] = undefined;
}
}
I would suggest having two or more dialogs up at the same time isn't ideal and probably not recommended by Google Material design.
To quote from the docs
Use dialogs sparingly because they are interruptive.
You say:
when an AJAX request is finished fetching data, I would like my
loading spinner to close, but not any error dialog that may be the
result of the fetching.
My solution here would be to have one dialog which initially shows the spinner. Once the request is finished replace the spinner with any messages.

How to communicate an action to a React component

While my scenario is pretty specific, I think it speaks to a bigger question in Flux. Components should be simple renderings of data from a store, but what if your component renders a third-party component which is stateful? How does one interact with this third-party component while still obeying the rules of Flux?
So, I have a React app that contains a video player (using clappr). A good example is seeking. When I click a location on the progress bar, I want to seek the video player. Here is what I have right now (using RefluxJS). I've tried to strip down my code to the most relevant parts.
var PlayerActions = Reflux.createActions([
'seek'
]);
var PlayerStore = Reflux.createStore({
listenables: [
PlayerActions
],
onSeek(seekTo) {
this.data.seekTo = seekTo;
this.trigger(this.data);
}
});
var Player = React.createClass({
mixins: [Reflux.listenTo(PlayerStore, 'onStoreChange')],
onStoreChange(data) {
if (data.seekTo !== this.state.seekTo) {
window.player.seek(data.seekTo);
}
// apply state
this.setState(data);
}
componentDidMount() {
// build a player
window.player = new Clappr.Player({
source: this.props.sourcePath,
chromeless: true,
useDvrControls: true,
parentId: '#player',
width: this.props.width
});
},
componentWillUnmount() {
window.player.destroy();
window.player = null;
},
shouldComponentUpdate() {
// if React realized we were manipulating DOM, it'd certainly freak out
return false;
},
render() {
return <div id='player'/>;
}
});
The bug I have with this code is when you try to seek to the same place twice. Imagine the video player continuously playing. Click on the progress bar to seek. Don't move the mouse, and wait a few seconds. Click on the progress bar again on the same place as before. The value of data.seekTo did not change, so window.player.seek is not called the second time.
I've considered a few possibilities to solve this, but I'm not sure which is more correct. Input requested...
1: Reset seekTo after it is used
Simply resetting seekTo seems like the simplest solution, though it's certainly no more elegant. Ultimately, this feels more like a band-aid.
This would be as simple as ...
window.player.on('player_seek', PlayerActions.resetSeek);
2: Create a separate store that acts more like a pass-through
Basically, I would listen to a SeekStore, but in reality, this would act as a pass-through, making it more like an action that a store. This solution feels like a hack of Flux, but I think it would work.
var PlayerActions = Reflux.createActions([
'seek'
]);
var SeekStore = Reflux.createStore({
listenables: [
PlayerActions
],
onSeek(seekTo) {
this.trigger(seekTo);
}
});
var Player = React.createClass({
mixins: [Reflux.listenTo(SeekStore, 'onStoreChange')],
onStoreChange(seekTo) {
window.player.seek(seekTo);
}
});
3: Interact with window.player within my actions
When I think about it, this feels correct, since calling window.player.seek is in fact an action. The only weird bit is that I don't feel right interacting with window inside the actions. Maybe that's just an irrational thought, though.
var PlayerActions = Reflux.createActions({
seek: {asyncResult: true}
});
PlayerActions.seek.listen(seekTo => {
if (window.player) {
try {
window.player.seek(seekTo);
PlayerActions.seek.completed(err);
} catch (err) {
PlayerActions.seek.failed(err);
}
} else {
PlayerActions.seek.failed(new Error('player not initialized'));
}
});
BTW, there's a whole other elephant in the room that I didn't touch on. In all of my examples, the player is stored as window.player. Clappr did this automatically in older versions, but though it has since been fixed to work with Browserify, we continue to store it on the window (tech debt). Obviously, my third solution is leveraging that fact, which it technically a bad thing to be doing. Anyway, before anyone points that out, understood and noted.
4: Seek via dispatchEvent
I also understand that dispatching a custom event would get the job done, but this feels way wrong considering I have Flux in place. This feels like I'm going outside of my Flux architecture to get the job done. I should be able to do it and stay inside the Flux playground.
var PlayerActions = Reflux.createActions({
seek: {asyncResult: true}
});
PlayerActions.seek.listen(seekTo => {
try {
let event = new window.CustomEvent('seekEvent', {detail: seekTo});
window.dispatchEvent(event);
PlayerActions.seek.completed(err);
} catch (err) {
PlayerActions.seek.failed(err);
}
});
var Player = React.createClass({
componentDidMount() {
window.addEventListener('seekEvent', this.onSeek);
},
componentWillUnmount() {
window.removeEventListener('seekEvent', this.onSeek);
},
onSeek(e) {
window.player.seek(e.detail);
}
});
5: keep the playing position in state (as noted by Dan Kaufman)
Could be done something like this:
handlePlay () {
this._interval = setInterval(() => this.setState({curPos: this.state.curPos + 1}), 1000)
this.setState({playing: true}) // might not be needed
}
handlePauserOrStop () {
clearInterval(this._interval)
this.setState({playing: false})
}
componentWillUnmount () {
clearInteral(this._interval)
}
onStoreChange (data) {
const diff = Math.abs(data.seekTo - this.state.curPos)
if (diff > 2) { // adjust 2 to your unit of time
window.player.seek(data.seekTo);
}
}

Modeling relational data from REST api via angularjs

I'm building an app, that is backed with node-mysql combo, and angularjs on the frontend part. The backend REST service is ready, but I'm struggling with modeling my relational data. There are some questions regarding this like : $resource relations in Angular.js or $resource relations in Angular.js [updated] . Are those approaches still the best approaches, or were there any significant changes in $resource ? Or maybe Restangular is the way to go?
Here is my technique:
I declare a factory called dataService, which is a wrapper around Restangular, extended with some other features.
First let me gave some code and then explain:
.factory('identityMap',
var identityMap = {};
return {
insert: function(className, object) {
if (object) {
var mappedObject;
if (identityMap[className]) {
mappedObject = identityMap[className][object.id];
if (mappedObject) {
extend(mappedObject, object);
} else {
identityMap[className][object.id] = object;
mappedObject = object;
}
} else {
identityMap[className] = {};
identityMap[className][object.id] = object;
mappedObject = object;
}
return mappedObject;
}
},
remove: function(className, object) {
if (identityMap[className] && identityMap[className][id]) delete identityMap[className][id];
},
get: function(className, id) {
return identityMap[className] && identityMap[className][id] ? identityMap[className][id] : null;
},
flush: function(){
identityMap = {};
}
};
}
.factory('modelService', ['Restangular', 'identityMap', '$rootScope', '$log', function(Restangular, identityMap, $rootScope, $log) {
var ENUM1 = {STATE:0, OTHER_STATE:1, OTHER_STATE2: 2},
ENUM2 = {OK:0, ERROR:1, UNKNOWN:2};
function extendModel(obj, modelExtension, modelName){
angular.extend(obj, modelExtension);
obj.initExtension();
obj = identityMap.insert(modelName, obj);
}
function broadcastRestEvent(resourceName, operation, data){
$rootScope.$broadcast(resourceName + $filter('capitalize')(operation), data);
}
var resource1Extension = {
_extensionFunction1: function() {
// ... do something internally ...
if (this.something){
// this.newValue ....
;
}
else {
// ....;
}
},
publicExtensionFunction: function(param1) {
// return something
},
initExtension: function() {
this._extensionFunction2();
extendModel(this.resource2, resource2Extension, 'resource2');
}
};
var resorce2Extension = {
_extensionFunction1: function() {
// do something internally
},
publicExtensionFunction = function(param1) {
// return something
},
initExtension: function(){
this._extensionFunction1;
}
};
var modelExtensions = {
'resource1': resource1Extension,
'resource2': resorce2Extension
};
var rest = Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('/api');
RestangularConfigurer.setOnElemRestangularized(function(obj, isCollection, what, Restangular){
if (!isCollection) {
if (modelExtensions.hasOwnProperty(what)) {
extendModel(obj, modelExtensions[what], what);
}
else {
identityMap.insert(what, obj);
}
if (obj.metadata && obj.metadata.operation) {
broadcastRestEvent(what, obj.metadata.operation, obj);
}
}
return obj;
});
RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var newData;
if (operation === 'getList') {
newData = data.objects;
newData.metadata = {
numResults: data.num_results,
page: data.page,
totalPages: data.total_pages,
operation: operation
};
data = newData;
}
else if (operation === 'remove') {
var splittedUrl = url.split('/');
var id = splittedUrl.pop();
var resource = splittedUrl.pop();
identityMap.remove(resource, id);
broadcastRestEvent(resource, operation, id);
}
else {
data.metadata = {operation: operation};
}
return data;
});
});
return {
rest: rest,
enums: {
ENUM1: ENUM1,
ENUM2: ENUM2
},
flush: identityMap.flush,
get: identityMap.get
}
}]);
1) Let me explain identityMap (it's the code from this blog post with some extended features):
Let's consider a REST model which looks like this (each resource represents a database table):
resource 1:
id = Integer
field1 = String
field2 = String
resource2s = [] (List of resources2 which points to this resource with their foreign key)
resource 2:
id = Integer
field1 = String
field2 = String
...
resource1_idfk = Foreign Key to resource 1
Resource API is so smart that it returns resource1 relationships with resources2 with GET /api/resource1/1 to save the overhead that you would get with GET to resource2 with some query parameters to resource1_idfk...
The problem is that if your app is doing the GET to resource1 and then somewhere later GET to resource2 and edits the resource2, the object representing the resource2 which is nested in resource1 would not know about the change (because it is not the same Javascript object reference)
The identity map solves this issue, so you hold only one reference to each resource's instance
So, for example, when you are doing an update in your controller the values automatically updates in the other object where this resource is nested
The drawback is that you have to do memory management yourself and flush the identity map content when you no longer need it. I personally use Angular Router UI, and define this in a controller which is the root of other nested states:
$scope.$on("$destroy", function() {
modelService.flush();
});
The other approach I use within the Angular Router UI is that I give the id of the resource which i want to edit/delete within that controller as the parameter of nested state and within the nested state i use:
$scope.resource1instance = modelService.get('resource1', $stateParams.id);
You can than use
resource1.put(...).then(
function(){
// you don't need to edit resource1 in list of resources1
$state.go('^');
}
function(error){
handleError(error);
});
2) When I need to use some new functionality over resources I use `Restangular's setOnElemRestangularized. I think the code above is self explanatory and very similar to the one mentioned in blog post I have mentioned above. My approach is slightly different from the one in that post, that I don't use the mixin initialization before, but after I mix it to the object, so one could reference the new functions in initializer. The other thing I don't use, for example, he creates single factory for every resource, for example Proposal for extended logic and the other factory ProposalSvc for manipulating the instances. For me that's a lot of code you don't have to write and personally I think that Javascript is not suited very well for this object oriented approach, so I return just the whole Restangular object and do operations with it.
3) Another thing I have there is the broadcast of events when something in my model changes with Restangular, this is something I needed when I used ng-table. For example, when the model changed and rows in my table needed to be updated to reference the changes, so in the controller which manages the table I use $scope.on('eventName') and then change appropriate row. These events are also great when you have a multiuser live application and you use websockets for server notifications (code not included here in modelService). For example somebody deletes something in a database, so the server sends a notification to everyone who is alive through websocket about the change, you then broadcast the same event as used in Restangular and the controller does the same edits on its data.
This blog post should help you make your choice http://sauceio.com/index.php/2014/07/angularjs-data-models-http-vs-resource-vs-restangular/
I agree that there are a lot of good practices using http headers in Restangular, but you can pick them in the source and use them directly.
What you have to wonder is, will you be able to wrap your nested resources within a $resource and make instance calls while modifying the parent object. And that's not a given.
Your question seems to be asking whether you should be using ngResource, Restangular or some other framework or drop down to the low-level and use $http directly.
$resource is still widely used because it's included in the official docs and in all the popular tutorials and articles but Restangular is fairly popular.
The website ngModules shows a listing of REST API modules for AngularJS.
If you have a simple REST API, go with $resource for now and then switch to Restangular if you're doing lots of custom coding and filtering. It is a much nicer framework and more extensible.

Angularjs : How to switch between different implementations of a provider using DI

First I'd like to say my appreciation for this great website that I rely on rather often but never have used to ask anything.
I'm currently learning AngularJS by reading "Mastering web application development with AngularJS" and going through the provided examples.
I would like to understand how I can switch between different implementations of a provider (service) with minimal code change.
Here is the "notifications" service that I need to configure with different implementations of an "archiver" service, code for both below :
angular.module('notificationsApp', ['archiver'])
.provider('notificationsService', function () {
var notifications = [];
return {
$get : function(archiverService) {
return {
push:function (notification) {
var notificationToArchive;
var newLen = notifications.unshift(notification);
if (newLen > 5) {
notificationToArchive = notifications.pop();
archiverService.archive(notificationToArchive);
}
}
};
}
};
})
.config(function(notificationsServiceProvider){
**How could I make the 'archiverService' be of superConsoleArchiverService or consoleArchiverService ?**
});
I would like to be able to choose between different implementations for my "archiverService", namely "superConsoleArchiverService" or "consoleArchiverService" as defined in the following module.
angular.module('archiver', [])
.provider('consoleArchiverService', function () {
return {
$get : function() {
return {
archive:function (archivedNotification) {
console.log(archivedNotification);
}
};
}
};
})
.provider('superConsoleArchiverService', function () {
return {
$get : function() {
return {
archive:function (archivedNotification) {
console.log('super ' + archivedNotification);
}
};
}
};
});
Thanks a lot for helping me through this !
(also, I hope this question makes sense and has not been answered a gazillion times)
Let's say you have some condition, say a variable to use_super.
Then you could do something like this, by injecting $provide, and both of your providers:
$provide.value('archiver', use_super ? superConsoleArchiverService : consoleArchiverService);
Hope this helped!
Thanks to the answer provided by hassassin I was able to make it work, following is some working version, no code was changed in the 'archiver' module.
angular.module('notificationsApp', ['archiver'])
.provider('notificationsService', function () {
var notifications = [];
return {
$get : function(configuredArchiver) {
return {
push:function (notification) {
var notificationToArchive;
var newLen = notifications.unshift(notification);
if (newLen > 5) {
notificationToArchive = notifications.pop();
configuredArchiver.archive(notificationToArchive);
}
}
};
}
};
})
.config(function(notificationsServiceProvider, superConsoleArchiverServiceProvider, consoleArchiverServiceProvider, $provide){
// Here it is possible to set the 'configuredArchiver to either one of my archivers
//$provide.provider('configuredArchiver',consoleArchiverServiceProvider);
$provide.provider('configuredArchiver',superConsoleArchiverServiceProvider);
});
Some things I still don't fully understand like why can't I inject the 'configuredArchiver' directly in the 'notificationService' provider, but I strongly suspect it is related to my still very small grasp on the life cycle of AngularJS objects. Back to reading !

Saving one-many-one in nested controllers

I've been working on a requirement for a one-many-one relationship in Angular and Breeze and I've tried to apply Ward Bell's recommendation from this post in my solution so far. To see the code I'm using so far, please visit my original question and check the answer I submitted myself.
The problem, I'm now facing is when I want to prepare for saveChanges. The reason being, is that I have used nested controllers. The function that calls the save is in my parent controller and the property that holds my affected one-may-one entities is on the child controller.
In the parent controller...
function save() {
if (!canSave()) {
return $q.when(null);
}
// CODE TO PREPARE THE ONE-MANY-ONE ENTITIES FOR
// SAVING SHOULD GO HERE I'M GUESSING...
vm.isSaving = true;
return datacontextSvc.save().then(function(saveResult) {
vm.isSaving = false;
trapSavedDboardConfigId(saveResult);
}, function(error) {
vm.isSaving = false;
});
}
and in the child controller...
function getBusUnits() {
...
.then(function(data){
vm.busUnits = data;
vm.busUnits.forEach(function(bu){
getBusUnitChannels(bu);
});
});
}
function getBusUnitChannels(busUnit) {
datacontextSvc.dboardConfigs.getBusUnitChannelsById(busUnit.id)
.then(function (data) {
busUnit.busUnitChannelsList = data;
// THE PROBLEM IS THAT THE BUCHANNELS ARRAY IS CREATED
// FOR EACH BUSUNIT AND THE LIST OF BUSUNITS IS ONLY
// CALLED IN THE CHILD CONTROLLER.
// DOES THIS IMPLY I CAN'T HAVE A PROPERTY IN THE PARENT CONTROLLER
// THAT I CAN REFERENCE IN THE CHILD, SO THAT THE PARENT CAN ACCESS
// THE DATA DURING SAVE?
busUnit.buChannels = []; // HOW DO I DEFINE THIS GUY IN THE PARENT?
vm.channels.forEach(function (channel) {
busUnit.buChannels.push(channel);
});
busUnit.busUnitChannelsList.forEach(function (buc) {
busUnit.buChannels.forEach(function (buCh) {
if (buc.channelId === buCh.id) {
buCh.buChannel = buc;
buCh.isSelected = true;
} else {
buCh.isSelected = false;
}
});
});
});
}
The source of the problem is that the call for the one-many-one entity and the creation of an associated item viewmoder array (buChannels) only occurs after i've called for busUnits, and the call for busUnits happens in the child controller. Also, the buChannels array needs to be stored for each busUnit.
Because Angular parent controllers can't read child properties, the normal solution would be to create a property in the parent and reference it in the child. But, since the property I'd want to reference is dependent on an entity that's only fetched in the child, this can't be done.
Or can it? Any ideas or other recommended approaches. I'd really like to avoid only having a parent controller...

Resources