Update the DOM based on click event using service - javascript

I'm trying to update the DOM based on a click event, by using a service.
Specifically, I'm trying to use bootstrap's alerts to alert the user when they click on a particular button on the page. I'd like to be able to call notify.createNotification("Saved successfully") for instance, to tell the user that they have saved successfully.
I'm using ui-router and have managed to abstract the notification to a (isolate scope) directive and the accompanying template. The notification currently shows, updated, at the head of my application (all other views inherit this view) upon $scope.createNotification() (from within the click event). This works because the child views inherit the $scope property. Clearly though this pollution and abuse of the inheritance of the $scope is not ideal, hence I'd like to move it all into a service.
I've got as far as trying to use a factory to update the notification object, which contains state information for the alert, i.e. display:true/false, text etc. The trouble with the factory is it just returns where it's called from, I need to be able to update the parent.
...I feel I've done 9/10ths of the work on this, but that last 1/10th is really puzzling me.
Any help would be greatly appreciated.
Note: Guess I'm also looking for a 'best practices' here too, I mean $scope pollution works, but it's far from ideal. Thanks

This could be solved using pub/sub approach.
Create NotificationService that is used to send notifications. For callers it would look like NotificationService.alert({text: '..', ...})
Create <notification-area> component that subscribes to NotificationSerivce and displays notifications sent from anywhere.
NotificationService itself should implement pub/sub interface. Use any implementation of EventEmitter (like this one) or even angular.element to provide on(), off(), trigger() methods.

Related

Transfer alerts or notifications to next route with Angular 5

I am building a CRUD (Create, Read, Update, Delete) form with Angular 5 and want to navigate to the route showing an overview of all already added elements including the new one.
this.myElement.create({
//... Do some API magic
}).subscribe(() =>
this.router.navigate(['/overview']);
);
After updating the database the user gets router.navigate'd to the overview. There should be displayed a message (alert, notification, toast or what ever) whether the change was successfull or not.
Possible aproaches:
Do I have to implement an own service to transfer informations like this one?
Is there a built-in solution in Angular?
Should I use something like an event handler or oberserver watching for any CRUD methods?
You need to be careful where you implement this alert. If it's a service, you might do
.subscribe(() =>
this.toast.show('Task is finished');
this.router.navigate(['/overview']);
);
Since you are switching routes, the toast component has to be in the parent templates of <router> or up.
If the service is designed targeting the body tag, ex. using position: fixed. Then you are safe no matter what.
I don't think the entire page will get refreshed if that's your concern.
Just to answer your question
Do I have to implement an own service to transfer informations like this one?
YES, not necessarily you, but someone.
Is there a built-in solution in Angular? Maybe, because you can just put an element that positioned to the screen as a simple alert. Angular doesn't do anything specific for this task.
Should I use something like an event handler or oberserver watching for any CRUD methods? No need, you want to finish this alert before moving to others. However if you want others to know an alert has been fired, then YES, you want that service to publish an observerable.

Angular 2 - single service provider across multiple browser windows

I am trying to supply an alert once a task is complete - the user may be in any of multiple pages at the time. The alert should display to all pages.
I am using a service implementing BehaviorSubject
The provider for which is in my app.component.ts page - single instance
In my app.component.html I have the two components, one the alert, the other that fires the alert.
<alert></alert>
<submit-service></submit-service>
The service emits to the alert component which renders the alert.
This works fine, but only ever on the page that submits the service (not to any other page) - submission function is also in the alert component.
submit-service utilises
public emit: BehaviorSubject<model> = new BehaviorSubject(new model());
Once the event is completed it then fires off this.emit.next(_model);
In the alert component I subscribe to the event
ngOnInit(): void {
this.service.emit.subscribe(data=> {
this.fireAlert(data);
}
});
}
so I suppose the main question is, how do I have a single service subscribed across multiple instances, across multiple pages?
EDIT 1
Apologies for the ambiguity, by page I mean separate browser window or tab i.e. window.open
Just in case others are having this same issue - there is in fact an answer.
Using global storage events allows the traversing of information across all browser tabs/windows.
In the service instead of using BehaviourSubject, the service updates or 'emits' the data to a local storage item, event listener utilising a HostListener() decorator can then listen for these udpates - which listens across all windows and tabs.
Example:
#HostListener('window:storage', ['$event'])
onStorageChange(ev: StorageEvent) {
console.log(ev.key);
console.log(ev.newValue);
}
See here for further information: Storage events
So there's a couple things at play here. The first is the service that let's your application know that it's time to display the alert. It sounds like you already have that, but for simplicity sake I would make sure you are declaring that in a forRoot() context. I won't go into a crazy amount of detail regarding this topic, but essentially you need to make sure that your service is running in the root context. If you start lazy loading modules, and then subscribing to your service from within the lazy loaded module, it will create it's own Dependency Injection context and you'll start pounding your head against the table wondering why your service isn't updating. (been there :)
The next thing to look at is where you want to render your alert. You'll likely want to use the ComponentFactoryResolver to render your alert in the highest level component you can think of that makes sense. Basically (if I understand your need correctly), you need this to be within the same component, or higher as all of the pages you want to have the alert rendered to. For example I am working on an application that has a dashboard where we have a ComponentFactoryResolver that renders any and all modals we might need throughout the application. This allows us to call modals from anywhere within the dashboard using, like you, a behavior subject that activates the modals. Here's a great article on using the ComponentFactoryResolver.
Update 1
So after realizing that "page" was actually a new browser window this method won't necessarily work. Using BehaviorSubjects will only update within the application context, so opening a new window creates a new application context, i.e. killing the BehaviorSubject of being a viable candidate to make this work. You'll need to have a service that is not instance specific. Web sockets as you mentioned would be a good alternative.
It is worth noting though that if it's possible to refactor the code to open modals instead of new windows, you could maintain the integrity of your Dependency Injection tree, and then use BehaviorSubjects to achieve this. Otherwise you'll need something outside of the application that is maintaining state.

Durandal Event in widgets

So I have yet another DurandalJS question.
So I have a few widgets that are pretty much self contained. They render or hide themselves depending on whether the current user is logged in or not e.g. I have a widget that displays the current users name, and another one that displays some setting for the current user. The 'current user is a value stored in local storage so everyone basically knows to get it from there and do their bit.
I have a security module which triggers an event on itself when a user is logged in and when a user is logged out.
All my widgets including shell require this security module and they all handle the event.
Now I know the event is working because shell's event handler gets called but the widgets never see the event even thought they are displayed in shell.
However, if I do a hard refresh of the page (whether the user is logged in or out), all the widgets render properly so I know the widgets know what to do.
Am I doing this wrong? If yes how best do I go about it.
Thanks
The problem was that I was returning singletons from my widgets. I have no idea what the difference was but I never got the events when I returned singletons. Durandal expects widgets to be constructor functions so that it can instantiate multiple widgets of the same kind see here
viewmodel.js is a function exported module that will serve as a location for all your widget's code. It will be bound to view.html by the widget infrastructure via the composition module.

Calling into or between controllers in AngularJS

I have some fairly complicated logic in a bootstrap dialog which I've moved into its own controller for isolation.
There are times I want to launch the dialog or call a function in the controller based on some logic that occurs elsewhere in the app - in another controller, or a service. I've achieved this by adding an id to the ng-controller element then looking up the element by that id, and calling things off the .scope() of that controller. Essentially this:
In html:
<div id="modalController" ng-controller="modalController">
And in another service or controller:
angular.element("#modalController").scope().somefunction()
This seems pretty weird that I can't just get a controller by name. Is there a way to do this?
Create a service and bind the model to data maintained in that service. Make a change to the model within the service and it's made everywhere.
You could also create a service that provides a pubsub interface to the changes you need to make.
Yet another way to do it would be to have a single model representing the state of your system and modify that. Attach the relevant parts of that model to the scopes of each widget as necessary and you have a communication device built in.
It sounds like you are making a change in one place that should cause a change in another place. If that's the case, I'd argue having a service that updates all parts of the model correctly is the best way to go. Always imagine what you'd do if you added another widget that hangs off this functionality.

Marionette Event Aggregator vs Backbone Router

I'm pretty new to this world so I need some clarifications on it. Maybe I could be wrong on the subject. So, feel free to correct me.
I'm studying how Marionette and Backbone work together. Oh yeah. Marionette gives us extension to Backbone. Really nice stuff.
The thing that is not obvious to me is when to use the routing mechanism provided by Backbone and when to use publisher/subscriber pattern by Marionette.
Is there any rule of thumb?
Here, Where to use event aggregator in backbone marionette?, a similar discussion but there is no advice on how using that or the other.
My take on the route management is explained in the free preview to my book on Marionette (http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf)
Basically, my opinion (others don't necessarily share it) is that Backbone's routing should be used to configure the application state when the user "enters" the application via a URL. In other words, it will parse parameters, and call proper controller actions.
But once that initial state is setup, routing code should no longer be triggered, even as the user navigates through the application.
Here's an example:
The user enters arrives on the "contacts/2/edit". Backbone routing code will extract the 2 argument and call the edit controller action with that id parameter (which fetches that contact, displays the proper views, etc.). In other words, the initial application state is being configured.
The user clicks on the "show all contacts" link leading to the "contacts" URL. Here, I believe this modification should be handled through Marionette events (i.e. indicating the user wants to see all contacts). After all, we know what the user wants to do, and which URL fragment should be displayed. In other words, there is no reason for the routing code to get involved.
Note that this is my opinion, and other developers simply pass trigger: true when the user clicks a link. But as I explain in the book extract linked above, this tends to lead developers to create "stateless applications in javascript" (e.g. passing lots of parameters in the URL, even though they should be stored in the application's state). Ater all there is a reason that by default, Backbone's navigate method has trigger: false.
Derick Bailey (Marionette's creator) also discussed the issue here: http://lostechies.com/derickbailey/2011/08/03/stop-using-backbone-as-if-it-were-a-stateless-web-server/
Event aggregator is more useful for notifying things. (think small bits of feedback)
Message from server (updated record)
Let other models know things have changed
Lock everything down while saving until saved
Single Moment in time things
Router is for things where you want the state to be save-able (think separate page in a MPA)
Model Edit Page
Model View Page
Something that will stay until another event or activity changes it
If you are not sure if something is an event or a page, then think about it and ask that separate question.

Categories