Building a reusable AngularJS directive with two-way databinding set through code - angularjs

I'm trying to build a reusable directive that shows a dialog box when a button is clicked and allows the user to customize an array of strings. I have this working fine in a single-use-case scenario, but I'm trying to figure out if I can create two-way data binding through code, so that I can use a single instance of this directive multiple times with different arrays of strings.
In my HTML, I have the directive (note the lack of binding to a particular array):
<my-array-dialog control='arrayDialog'></my-array-dialog>
I'm exposing a shared control object with a showDialog method on it that causes the dialog to be shown. An example of that pattern is: http://plnkr.co/edit/MqN9yS8R5dnqTfjqldwX?p=preview
What I want to do is have two-way data-binding with the parent controller passing in the data into my showDialog method. I'm unsure how to configure this though, or even if it is possible.
In my directive, I have the following:
$scope.control = {
showDialog: function (arrayData) {
// Ideally, this would create two-way data binding
$scope.arrayData = arrayData;
// Manipulate the DOM here to show the dialog
}
}
Ideally, changes to $scope.arrayData would be reflected in the calling code's arrayData. Again, the reason I'm not setting this up as an attribute is because I want to call this dialog multiple times with different data and to avoid having multiple dialog directive instances.
Is it possible to set up the two-way data binding in this manner, or am I going about this all wrong?

Absolutely! Check out the intimidating yet incredibly helpful AngularJS docs page on $compile. In particular, what it sounds like you'd like to do is create a two-way binding between a parent scope variable and a variable in the local scope of the directive.
When defining a directive, you can specify a scope object. You'll want to create an isolate scope (for reusability) and use the # feature to create the two-way bindings with the HTML attributes.
From the $compile docs:
= or =attr - set up bi-directional binding between a local scope property and the parent scope property of name defined via the value of the attr attribute. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and widget definition of scope: { localModel:'=myAttr' }, then widget scope property localModel will reflect the value of parentModel on the parent scope. Any changes to parentModel will be reflected in localModel and any changes in localModel will reflect in parentModel.

Related

Angular scope modification inside transcluded content

Angular: Can anyone explain why transcluded content in a directive can only update objects on the scope - not variables directly on the scope. Is it just because the object and functions are ref type in javascript and why does the binding work one way and ... why does the binding break after the update inside the transcluded content (see plunker samples)
-Plunker sample - variable on scope vs object on scope
Working -Plunker sample - variable on scope
Transcluded content can also update parent's scope properties
Transcluded content is like any other content, therefore if you followed the dot.rule you'll be able to update the parent scope properties you want. Always follow the dot.rule and refactor your logic to make sure everything is done in the angular way.
Directive scope types
Directives in angular prior to 2.0 version accept several types of scopes, the scope can be true, which creates a new one and inherits parent's properties; false, which does not create a new scope, but still inherit parent's properties; or {} which is known as an isolated scope, this creates a new scope with zero properties, it keeps only the properties you declare.
One-Way vs Two-Way data binding
Angular uses both, one-way and two-way data binding. For example, two-way data binding occurs when you use the ng-model directive, whenever you update the model, the view will reflect those changes and viceversa. On the other hand, one-way data binding occurs when you use the interpolation {{some.property}}
The two-way data binding should not break if you are using the dot.rule. That's how prototypical inheritance works after all.
Check out this Pen to illustrate everything said in this answer.

Ng-if and custom directives

I've been creating custom directives that involve initialization of data on load. Since this initialization depends on the state of scope objects from the parent bound to the isolate scope within my directives, I've been enclosing the directives in ng-if statements, so they are only rendered in the DOM, and therefore initialized, once the appropriate parent scope objects are created.
Ng-if seems preferable because the directives have various API calls bound to $watches that I would prefer to not trigger until the data is valid.
For example, I have a directive that provides an interface for editing a scheduled event. The parent page allows users to select an event for editing, or to create a new event. Creating a new event is done in a way that allows the user to select a variety of values to pre-populate the event object with (start dates, event type, whether the event is tied to another object in the database, etc.).
The ng-if wrapping the directive gets set to true when the user makes the selections necessary for working with the event, and the event (either an existing event, for edits, or a framework for an event created by a custom service, for adds) is bound to the isolate scope of the directive.
This prevents the $watches from triggering until the valid event is bound to the directive, and also allows me to initialize some variables local to the directive for validation and data manipulation of the event.
The problem I'm running in to is that enclosing the directive in an ng-if isolates the isolate scope.
For example, a start time for the event is something that may, or may not, be specified by the user when they create the new event from the controller. If they don't, I want to calculate a default start time based upon other variables within the directive. If they do, then I want to use that value.
The way I'm doing this is to bind a scope variable from the controller to the isolate scope of the directive:
scope: {
editEvent: '=',
overrideTime: '='
}
This is passed as an attribute of my directive:
<add-edit-event ng-if="viewEdit" event="editEvent" override-time="overrideTime">
</add-edit-slot>
Once the directive creates the new event (or cancels out), I want to reset that overrideTime in the parent scope to null.
To do that from within my directive, however, I can't rely on the two-way binding to work without traversing up past the ng-if scope, thus:
scope.$parent.$parent.overrideTime = null;
I keep running into situations like this, where the fact that the directive is enclosed in an ng-if causes complications with the scope, which makes me feel like there's a problem with my general approach.
Is there a better way of handling conditional initialization and loading of custom directives, or is enclosing them in ng-if statements okay, so long as I deal with the interposing ng-if's scope appropriately?

Sharing isolate scope with nested directives in Angular

I want to have a custom directive that is reusable and creates an isolate scope so it can be used anywhere (as long as the consumer uses the API defined by the directive). Then, I want the consumer to easily be able to mix and match different reusable pieces that fit within the main reusable directive.
The situation I'm working with is a drop down menu. The main directive would isolate the scope and define the API for the dropdown as a whole. The inner directives would allow the consumer to choose whether they want a button that opens the menu, a search box/input field that opens the menu, etc. Then they could also choose what menu style is used:
<dropdown items="..." selected-item="...">
<dropdown-button>(Transcluded button text here)</dropdown-button>
<dropdown-icon-list></dropdown-icon-list>
</dropdown>
The parent directive/controller would handle state/communication for the inner pieces (ie. the button might trigger the "open" state, and the list would respond by opening). In other words, the parent directive would provide a single place for the consumer to define behavior and isolate scope from the rest of the page, while the nested directives would change shared state/respond to changes in shared state based on their role.
I actually had this working by using an isolate scope on the main "dropdown" directive and then inheriting scope with the nested directives (didn't specify "scope: ..." on the nested directives). But, with Angular 1.2, things have changed such that the isolate scope of the parent is truly isolated--the children inherit the scope that exists outside the parent directive, rather than sharing its isolated scope.
What is the Angular way to accomplish such a thing?
I've started retrofitting my existing code to share the controller from the parent directive with the nested children, but I feel that's the wrong way to go once I get into the situation where the children need to listen for changes on the shared scope... The only way I can see to do that would be to pass a callback function from the nested directives into the shared controller which it would bind to a $scope.$on method. Seems like the wrong path to head down.
There’re 3 types of prefixes AngularJS provides.
"#" ( Text binding / one-way binding )
"=" ( Direct model binding / two-way binding )
"&" ( Behaviour binding / Method binding )
All these prefixes receives data from the attributes of the directive element and provide communication between directives. please visit below link for similar question.
Visit https://stackoverflow.com/a/33024209/4348824

Directive = or =attr not bi-directional

= or =attr - set up bi-directional binding between a local scope
property and the parent scope property of name defined via the value
of the attr attribute. ... Any changes to parentModel will be
reflected in localModel and any changes in localModel will reflect in
parentModel.
This is from Angular docs, and I have read a few post that say you DON'T get bi-directional binding when creating isolated scopes with directives. I am finding it difficult to understand. I want to create a widget, passing in state but I also want to be able to bind that state to other directives in the parent scope.
From my experiments you can't make a change to scope in the directive and pick up the change in it's parent controller/ other directive. Is this correct?
Yes and no an isolate scope (e.g. you've added a scope: {} to your directive) in angular is just that, an individual scope that is not linked in the prototypal inheritance chain. This means that by default and changes you make to the scope in the directive will not be reflected back into the scope of parent elements to this directive.
The '=' locals type in the scope creation sets up up a visible binding to the parent scope. Then changes to the isolated scope variable will be reflected back. Make sure your changes are being recognized by the locals scope in your directive or nothing will propagate up (e.g. try watching/$watch the locals variable to ensure a change is occurring in the directive, if not you may need to $apply depending on how you've bound within your directive).

Angular directive and dynamic templates: where does the controller logic go?

Here's the situation: I have entities that have many different properties: scalars, arrays, references to other entities, etc. Each property type can be displayed differently depending on the action (create, edit property definition, edit property value, view). Each property is treated equally by the entity and is usually rendered by iterating array of properties and creating a generic property directive.
The property directive checks property type and desired action in linking function, fetches the appriopriate template and compiles it. Here's some pseudo-code:
myModule.directives('property', function() {
return function(scope, element, attributes) {
var template = fetchTemplate(scope.propertyType, attributes.action);
element.html($compile(template)(scope));
}
}
Where should I put the controller logic? Currently I define a dedicated controller for each property in the template via ng-controller and put that controller to the application's controllers file. Yet I feel the logic should go into the directive itself. Here's the catch: I cannot use directive's controller function as the template is not ready by then. Should I handle all the logic for different property types in the same link function that fetches template? Or should I define directives for each property and then declare those directives in property templates? (This one seems doesn't seem right at all because a directive loads a template that declares another directive to do controller stuff).
What is the angular way of achieving this with dynamic templates?

Resources