Sharing buttons and logic across views and controllers - angularjs

I'm working on a single page app that behaves similar to a wizard. The user lands on page 1 and needs to save and continue to get to page 2. What's the best way to share those buttons between all the views? My form names are different so I currently have to duplicate these buttons so I can use logic like:
<div class="mySaveButton" ng-disabled="page1Form.$invalid"></div>
but then on page 2:
<div class="mySaveButton" ng-disabled="page2Form.$invalid"></div>
To further complicate matters, saving on page1 posts the data to a different address than page2. I have a navigation controller which is the parent and that needs to be handled as well.
So to summarize I need my buttons (Back, Save and Save and Continue) to do all of the following without having to duplicate the buttons across all views:
Check if the current form is valid
If it's valid, the data for that form needs to post to the correct endpoint for that form
Navigation needs to be notified so that it can update and/or take action

Essentially you need to re-use a template (/controller), and somehow pass options into it. You could probably do something with ngInclude and nesting controllers, but a way that is more friendly to more complex structure later is to create a custom directive. It depends a bit on your exact use-case, and exactly what you need to pass into it, but a simple version would be something like:
app.directive('formButtons', function() {
return {
restrict: 'E',
scope: {
'localBack':'&back',
'localSave':'&save',
'localSaveAndContinue':'&saveAndContinue'
},
templateUrl: 'buttons.html'
};
});
With a template of
<div>
<button ng-click="localBack()">Back</button>
<button ng-click="localSave()">Save</button>
<button ng-click="localSaveAndContinue()">Save & Continue</button>
</div>
The scope options with & each define a function on the directive's scope, that evaluates contents of the attribute on the <form-buttons> element of that name in the parent scope. You can then use the directive as follows
<form-buttons back="back(2)" save="save(2)" save-and-continue="saveAndContinue(2)"></form-buttons>
which will get replaced by the template of the directive, and the function save, back and saveAndContinue, which are defined on the parent scope, will get called when clicking on the buttons in the template, passing the appropriate step number which can be used to customise behaviour.
You can see a working example at http://plnkr.co/edit/fqXowQNYwjQWIbl6A5Vy?p=preview

Related

Accessing components from within a different component

Using Angularjs 1.5, I have been having trouble making a D3.js graph that's defined in one component display on a page that relies on a different one. I'm new to Angular, and while I think the answer is buried somewhere in the official documentation's example of a component tree, I wasn't able to completely follow the logic.
My goal is to make the graph defined in graphController, and make it appear as accessed by requests.template.html.
Trying to inject it into requests.component like I would a directive gave me an unknown provider error
I inherited the setup from someone else (and would prefer to leave the protect structure the way it is), and it more or less looks like this:
requests.module.js
angular.module("requests", []);
requests.component.js
angular.module('requests').component('requests', {
templateURL: 'Bindings/Templates/requests.html',
controller: function requestsController($scope) {
[code]
}
}
requests.template.html
//where I'd like to be able to access the controller from the graphs component
graphs.module.js
angular.module('graphs', []);
graphs.component.js
angular.module('graphs').component('graphs', {
templateURL: '/Bindings/Templates/graphs.template.html',
controller: function graph() {(code for d3 graph)};
}
graphs.template.js
{{$ctrl.graph()}}
//this page is just a placeholder to see the graph until I can view it on requests' page
Any help you can give or ways to think about how I should thread this controller call through would be great. Thanks!
If you are wanting the graph to display in the requests component then you can just insert the component element tag within the requests template. For example, in the Bindings/Templates/requests.html insert:
<div class="stuff-to-wrap-graph">
<graphs></graphs>
</div>
Once you have created a component, think of it like a unique html element that you can insert anywhere, including within other components. You would normally only want direct access to the controller of another component if it was a tightly coupled child of a parent, eg. an individual tab within a tabs component that needs to talk to the parent tab container.

Mechanism for change parts of view (angular js)

I am building SPA application and I want to get advice about best practice (organize project structure) for follow issue:
My HOME page have some information and place for authentication or greeting user. In this place I need to show one of two states: first - user not is authorized and I show him form to login, second: - user is authorized and I show him "Hello, man".
I know two bad decision for this. 1. I can use ng-switch (and I think that it's not good). 2. I can use something like ng-include wich will call function for get actual html subview. (I think that it worse then first).
Also I listened about "ui router", but I'am not sure that it best way.
How will better for organize my project and what will better to use?
You can use ng-switch or ng-if controlled by a variable isLogged
I would make a template login.html and inside I would use ng-switch.
But a more advanced option is to use a directive controlled by events like:
.directive('loginDialog', function (AUTH_EVENTS) {
return {
restrict: 'A',
template: '<div ng-if="visible"
ng-include="\'login-form.html\'">',
link: function (scope) {
var showDialog = function () {
scope.visible = true;
};
scope.visible = false;
scope.$on(AUTH_EVENTS.notAuthenticated, showDialog);
scope.$on(AUTH_EVENTS.sessionTimeout, showDialog)
}
};
})
I use events in that case because this funcionality is extended to the whole application and it's very easy to control the component sending events from any place I need.

Having a set of checkboxes map to a nested array

I am working on a SPA that pulls in customer data from one $resource call, and gets some generic preference data from another $resource call.
The preference data is sent as an array, which I want to use to populate a series of checkboxes, like so:
<div ng-repeat="pref in fieldMappings.mealPrefs">
<input type="checkbox"
id="pref_{{$index}}"
ng-model="customer.mealPrefs"
ng-true-value="{{pref.name}}" />
<label class="checkbox-label">{{pref.name}}</label>
</div>
When a user clicks one or more checkboxes, I want the values represented in that array of checkboxes to be mapped to an array nested inside a customer object, like so:
.controller( 'AppCtrl', function ( $scope, titleService, AccountDataService ) {
// this is actually loaded via $resource call in real app
$scope.customer = {
"name": "Bob",
"mealPrefs":["1", "3"]
};
// this is actually loaded via $resource call in real app
$scope.fieldMappings.mealPrefs = [
{'id':"1", 'name':"Meat"},
{'id':"2", 'name':"Veggies"},
{'id':"3", 'name':"Fruit"},
{'id':"4", 'name':"None"}
];
});
I have tried setting up ng-click events to kick off functions in the controller to manually handle the logic of filling the correct part of the customer object model, and $watches to do the same. While I have had some success there, I have around 2 dozen different checkbox groups that need to be handled somehow (the actual SPA is huge), and I would love to implement this functionality in a way that is very clean and repeatable, without duplicating lots of click handlers and setting up lots of $watches on temporary arrays of values. Anyone in the community already solved this in a way that they feel is pretty 'best practice'?
I apologize if this is a repeat - I've looked at about a dozen or more SO answers around angular checkboxes, and have not found one that is pulling values from one object model, and stuffing them in another. Any help would be appreciated.
On a side-note, I'm very new to plunkr (http://plnkr.co/edit/xDjkY3i0pI010Em0Fi1L?p=preview) - I tried setting up an example to make it easier for folks answer my question, but can't get that working. If anyone wants to weigh in on that, I'll set up a second question and I'll accept that answer as well! :)
Here is a JSFiddle I put together that shows what you want to do. http://jsfiddle.net/zargyle/t7kr8/
It uses a directive, and a copy of the object to display if changes were made.
I would use a directive for the checkbox. You can set the customer.mealPrefs from the directive. In the checkbox directive's link function, bind to the "change" event and call a function that iterates over the customer's mealPrefs array and either adds or removes the id of the checkbox that is being changed.
I took your code and wrote this example: http://plnkr.co/edit/nV4fQq?p=preview

Angular modal dialog best practices

What is the best practice for creating modal dialogs with dynamic content, contrasted with dialogs that don't have dynamic content.
Eg..
We have some modal forms that accept a list of form elements, and have submit/cancel.
Also, there are modal dialogs that just display a confirm/ok type of operation.
I've seen a lot of people saying that dialogs should be services passed into the controller, but it seems to me that services shouldn't be rendering UI components and manipulating the DOM.
What is the best practice for assembling these two types of dialogs? Thanks.
Angular UI Boostrap provides a service - $dialog - that can be injected wherever you need to use a dialog box. That service has two main methods: dialog and messageBox. The former is used to create a dialog with dynamic content and the latter to create a message box with a title, a message and a set of buttons. Both return a promise so you can process its result, when it's available.
I think this approach works well, because it fits the somehow natural, imperative way of handling dialogs. For instance, if the user clicks on a button and you want to show a dialog and then process its result, the code could look like this:
$scope.doSomething = function() {
$dialog.dialog().open().then(function(result) {
if (result === OK) {
// Process OK
}
else {
// Process anything else
}
});
}
You can indeed use directives to do the same, and perhaps it seems the right way to do it since there is DOM manipulation involved, but I think it would be kind of awkward to handle it. The previous example would be something like this:
<dialog visible="dialogVisible" callback="dialogCallback()"></dialog>
...
$scope.doSomething = function() {
$scope.dialogVisible = true;
}
$scope.dialogCallback = function(result) {
if (result === OK) {
// Process OK
}
else {
// Process anything else
}
}
IMO, the first example looks better and it's easier to understand.
Since dialogs are DOM components, they should probably be directives. You can either build up the DOM elements of the modal inside the directive itself or put the elements on the main html page hidden and unhide them from the directive. If you don't isolate the directive's scope, you can just refer to the controller scope (unless you are in a child scope) from the directive.
Dynamic vs. static content isn't that much of a decision point IMO. Since you have access to the scope from within the directive, you can access whatever you need from the inherited scope.
One quite simple design that works well is to :
Have such a "modal dialog" div somewhere in your html. It will be typically absolute, taking all the screen width and height (typically a dark translucent div with a smaller dialog div into it) and not displayed by default (use ng-show to display it conditionally, depending on the existence of modals or not)
Declare a controller that listens to dialog events ("dialogShow", "dialogClose", etc.) and change its "currentModal" $scope value when receiving them. According to the ng-show condition setup in the previous step, the modal will accordingly display or change or disappear (if set to null/undefined)
Trigger dialog events from anywhere in your application, using broadcasts.
Improvements are:
Events parameters properties (setup when triggering and received by the controller) could include title, message, images, even html (to be sanitized), buttons, callbacks for those buttons, display durations (throught $timeout)
Remember a stack of received alerts. When one is closed, the next pending one displays

Updating URL in Angular JS without re-rendering view

I'm building a dashboard system in AngularJS and I'm running into an issue with setting the url via $location.path
In our dashboard, we have a bunch of widgets. Each shows a larger maximized view when you click on it. We are trying to setup deep linking to allow users to link to a dashboard with a widget maximized.
Currently, we have 2 routes that look like /dashboard/:dashboardId and /dashboard/:dashboardId/:maximizedWidgetId
When a user maximizes a widget, we update the url using $location.path, but this is causing the view to re-render. Since we have all of the data, we don't want to reload the whole view, we just want to update the URL. Is there a way to set the url without causing the view to re-render?
HTML5Mode is set to true.
In fact, a view will be rendered everytime you change a url. Thats how $routeProvider works in Angular but you can pass maximizeWidgetId as a querystring which does not re-render a view.
App.config(function($routeProvider) {
$routeProvider.when('/dashboard/:dashboardId', {reloadOnSearch: false});
});
When you click a widget to maximize:
Maximum This Widget
or
$location.search('maximizeWidgetId', 1);
The URL in addressbar would change to http://app.com/dashboard/1?maximizeWidgetId=1
You can even watch when search changes in the URL (from one widget to another)
$scope.$on('$routeUpdate', function(scope, next, current) {
// Minimize the current widget and maximize the new one
});
You can set the reloadOnSearch property of $routeProvider to false.
Possible duplicate question : Can you change a path without reloading the controller in AngularJS?
Regards
For those who need change full path() without controllers reload
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
I realize this is an old question, but since it took me a good day and a half to find the answer, so here goes.
You do not need to convert your path into query strings if you use angular-ui-router.
Currently, due to what may be considered as a bug, setting reloadOnSearch: false on a state will result in being able to change the route without reloading the view. The GitHub user lmessinger was even kind enough to provide a demo of it. You can find the link from his comment linked above.
Basically all you need to do is:
Use ui-router instead of ngRoute
In your states, declare the ones you wish with reloadOnSearch: false
In my app, I have an category listing view, from which you can get to another category using a state like this:
$stateProvider.state('articles.list', {
url: '{categorySlug}',
templateUrl: 'partials/article-list.html',
controller: 'ArticleListCtrl',
reloadOnSearch: false
});
That's it. Hope this helps!
We're using Angular UI Router instead of built-in routes for a similar scenario. It doesn't seem to re-instantiate the controller and re-render the entire view.
How I've implemented it:
(my solution mostly for cases when you need to change whole route, not sub-parts)
I have page with menu (menuPage) and data should not be cleaned on navigation (there is a lot of inputs on each page and user will be very very unhappy if data will disappear accidentally).
turn off $routeProvider
in mainPage controller add two divs with custom directive attribute - each directive contains only 'templateUrl' and 'scope: true'
<div ng-show="tab=='tab_name'" data-tab_name-page></div>
mainPage controller contains lines to simulate routing:
if (!$scope.tab && $location.path()) {
$scope.tab = $location.path().substr(1);
}
$scope.setTab = function(tab) {
$scope.tab = tab;
$location.path('/'+tab);
};
That's all. Little bit ugly to have separate directive for each page, but usage of dynamic templateUrl (as function) in directive provokes re-rendering of page (and loosing data of inputs).
If I understood your question right, you want to,
Maximize the widget when the user is on /dashboard/:dashboardId and he maximizes the widget.
You want the user to have the ability to come back to /dashboard/:dashboardId/:maximizedWidgetId and still see the widget maximized.
You can configure only the first route in the routerConfig and use RouteParams to identify if the maximized widget is passed in the params in the controller of this configured route and maximize the one passed as the param. If the user is maximizing it the first time, share the url to this maximized view with the maximizedWidgetId on the UI.
As long as you use $location(which is just a wrapper over native location object) to update the path it will refresh the view.
I have an idea to use
window.history.replaceState('Object', 'Title', '/new-url');
If you do this and a digest cycle happens it will completely mangle things up. However if you set it back to the correct url that angular expects it's ok. So in theory you could store the correct url that angular expects and reset it just before you know a digest fires.
I've not tested this though.
Below code will let you change url without redirection such as: http://localhost/#/691?foo?bar?blabla
for(var i=0;i<=1000;i++) $routeProvider.when('/'+i, {templateUrl: "tabPages/"+i+".html",reloadOnSearch: false});
But when you change to http://localhost/#/692, you will be redirected.

Resources