AngularUI: why modal is not implemented as a directive? - angularjs

I am using angular-ui in my project and I wanted to implement a modal window. Most of the components of the library (http://angular-ui.github.io/bootstrap/) are implemented as directives (as expected). However, modal is not - it is implemented as a service. This leads to a strong dependency on the view in the controller (i.e. the controller needs to know that the view uses a modal window, what should not be the case).
My question is: why is modal implemented as a service, not directive? Does it give any benefits?

The $modal directive is quite flexible, and by its API, it supports:
Being triggered on any event you want, since the controller controls when it opens.
Passing data from the controller to the controller in the dialog, using the resolve option
Using any controller or template in the dialog.
Passing data back from the dialog on close to the triggering controller, by its result promise.
While not impossible for this to all be in a directive, I think it could only realistically be achieved by using a lot of options, or a complicated object, which would have to be constructed in the controller anyway.
Another reason for this not being a directive, is that directives are usually for things in a particular place in a page. The modal by its very design is not attached to any element in the page.
I would suggest trying to design an API for a directive that gives the same functionality as $modal, and I suspect it'll reveal that using a service is, on the whole, clearer, and probably more flexible.

Related

Compile AngularJS pages and remove ng-* directives

Is it possible to compile AngularJS application to remove ng-* directives like (ng-click) and generate plain HTML like Angular 2+? If we inspect Angular 2 application with say chrome dev tools, it does not show event handlers like onClick. However AngularJS shows ng-click, ng-for etc.
Short answer:
No.
Long answer:
I won't prvide one, as your question touches many (complex) topics that are already explained elsewhere and possibliy better than I could do, but I will provide some usefull links in my short explanation.
Medium answer:
What you asking for is not possible, because AngularJS uses a different approach for change detection and event bindings.
One of the cool things about AngularJS/Angular is it's ability to to detect changes and perform updates automtically. To make this possible, Angular(JS) has to notice if things change.
Taking your example with the click event, AngularJS uses event based directives to notice the click event, whereas Angular performs a event binding using one-way bindng from the template view to the component. Angular has this possibility because it uses Zones to get aware of changes. Therefore it doesn't need a directive as AngularJS did.
This explains why you don't see event handlers on the HTML element, because Angular directly sets up the event binding. You can verify that the event is handled by Zone.js if you check the Event Listeners tab in Chrome:
Other usefull links to the topic:
https://angular.io/guide/architecture-components#data-binding
https://blog.thoughtram.io/angular/2016/01/22/understanding-zones.html

Why isn't Angular's `$location.url` working after my Bootstrap Modal close?

Problem:
Anyone know why in my cb.register function below, my Modal window closes OK, but the $location.url('/dashboard') does not properly run?
I don't get any console errors, and the page does not redirect. However, console.log('TRANSITION DONE') still prints out in the code below after the modal window closes, so I know the event is being recognized:
Code:
app.controller('userController', ['$scope', 'userFactory', '$location', function($scope, userFactory, $location) {
var cb = {
register: function(createdUser) {
// Closes Modal window:
$('#myModal').modal('hide');
// Runs after close with completed transition effects:
$('#myModal').on('hidden.bs.modal', function (e) {
console.log('TRANSITION DONE');
$location.url('/dashboard');
})
},
};
}]);
Questions:
Why does the $location service not behave properly when nested inside of my .on() function?
Should I be using angular-ui instead? (Of which I was not familiar until researching solutions to this issue, and instead started my project by framing out all the views first using bootstrap3 and including the JS files, etc).
What I've Tried:
I've tried passing the $location parameter into the .on()'s callback function, along with e, but that made no real change and the function should still have access to $location in the larger scope.
Console logging $location or $location.url works properly, so I know the methods are accessible in the scope.
Moving the $location function outside of the on() function does load the dashboard page, however the fade-out modal close does not complete. A light-grey transparent BG is overlaying my dashboard page, which is why I chose to utilize the hidden.bs.modal event per the documentation: http://getbootstrap.com/javascript/#modals
// Closes window:
$('#myModal').modal('hide');
// Redirects to page but fade-out on Modal not completed:
$location.url('/dashboard');
Desired Behavior/Pseudo-Code:
Modal window closes (when callback runs)
After Modal fade out:
$location.url('/dashboard') runs, loading dashboard page
Thank you to any angular/bootstrap ninjas whom can help!
Why does the $location service not behave properly when nested inside of my .on() function?
You're directly using Bootstrap modals in your application -- which is OK. But, this requires you to manipulate the modal with jQuery. It is not advisable to use jQuery in an AngularJS application as AngularJS has its own methods of DOM manipulation and therefore you shouldn't manually manipulate the DOM via jQuery.
To answer your question, since you're using jQuery to directly manipulate the DOM, it is likely that AngularJS' digest cycle has not picked up on the modal closing. You could get around this by telling AngularJS to activate a digest cycle via $scope.$apply(). However, this will easily dirty up your code and just doesn't make sense according to best AngularJS principles.
Should I be using angular-ui instead? (Of which I was not familiar until researching solutions to this issue, and instead started my project by framing out all the views first using bootstrap3 and including the JS files, etc).
Yes. I would highly suggest using angular-ui. As per the documentation:
This repository contains a set of native AngularJS directives based on Bootstrap's markup and CSS. As a result no dependency on jQuery or Bootstrap's JavaScript is required.
As you can see, all of the Bootstrap functionality has been wrapped up into AngularJS directives that can easily be dropped into your application without worrying about jQuery.

How do I create a popup Angular component with parameters?

How do I create a popup Angular 1.6 component that accepts several parameters and will be used in several pages. One of the parameters will be dynamic -- set in the ng-click that opens the popup.
The popup scope should be a child scope of the calling page (not isolate), and it should have outputs back to the calling page.
Anyone know of a good pattern for that?
It mainly depends on what front-end framework are you using: if you go for Bootstrap than search for Bootstrap's Modal; if you are using Angular Material than it's called Dialog.
And in terms of how to set up the logic for it, well, there are quite a variety of options here as well. The simplest one I can think of would be to:
bind any variables from the popup using ng-model AND
pass it to a service and have custom logic OR in $rootScope OR in Location
This would be just a simple exammple.
Again, I think it mainly depends on what you actually want to achieve, which is not quite clear. I hope this helps.

Proper way to build a Modal component in Angular

I have a Modal component build in jQuery.
I want to integrate it in an Angular app that does not use jQuery, and I don't just want to have it as an external dependency. By that I mean, I don't what to leave it as a thing that can be called from controllers, but isn't a directive or service.
What I'm confused about is if it would be a good idea to have DOM logic inside an Angular service (factory, service, provider), or if I should just make a directive with & expressions? Or maybe both?
Inside controllers, I want to be able to inject Modal and call methods to show or hide the modal and set its content.
What would the proper way of doing this be?
I'm not looking for code, but rather guidance of how a "proper Angular" implementation of this would sound.
Thank you.
To answer your question about dom logic in an angular service. No, it is not a good idea, and if you really think about it, there is no need for that. What do you need:
a modal that can close/open
can execute callbacks based on whether you clicked yes or cancel
For that simple functionality you most certainly don't need any dom logic and especially not any dom logic in your services.
You can use the $modal service and do the styling yourself to make it look the same. Take a look at this plunkr:
http://plnkr.co/edit/WLJfs8axxMJ419N2osBY?p=preview
It is as simple as:
$modal.open({
templateUrl: 'someTemplateOfTheModal',
controller: SomeControllerOfTheModal,
})
You can refer to this for options.
Otherwise, if we are just talking about how to integrate existing jquery stuff in angular, I would still suggest rebuilding it from scratch in the angular way. Basically replace all jquery trigger/event functionality by pure angular.
For example, a modal can be on or off, might have an overlay or not. The modal itself would probably be used to execute a callback event on yes and not on cancel. You might want to also style it using custom classes so you should take that into consideration. Here are the directives you might wanna take a look if you are rebuilding it from scratch:
ng-if/ng-show (would probably be used to show/hide your modal and/or
overlay)
ng-class (would be used to inject any custom classes you might want
to put on your modal)
ng-include (in case you want your modal to be templatized)
ng-click (to do click events on click of your yes/no/cancel buttons
and/or clicking away from the modal or on the overlay)
You can see that it can get a bit cluttered, so the best thing would be to use angular's $modal service because it has all those features builtin. I think it has everything that you might possibly want from a modal, and you should just style it. But of course, for learning purposes, you might want to reinvent the wheel.

Creating a service that updates the DOM

I am trying to create a web application and I need little widgets that should be available from all controllers (i.e. a loading overlay and a sliding drawer). I have created directives with methods to display such behaviour, but you can't inject directives in controllers, that's not how they work.
I know I have to create a service that deals with this, and inject it in both controller and directive, but still can't figure out how to make the communication. The elements that have the directive are unique.
Another option I've considered is to create event listeners in the $rootScope or somewhere else and call $emit from all controllers that might need it, but I feel like the other way is more angular-y
Your "widgets" need ng-controller directives. I think this is what you're looking for:
<div ng-mywidgetdirective ng-controller="mywidgetcontroller"></div>

Resources