Nested directives with transclusion - angularjs

I have a set of form directives in which duplicate code can be extracted into a separate directive.
I know that I can use transclusion, but have not been able to identify any technique that allows this (element cloning or passing functions). Tutorials on pluralsight, sitepoint and few other describe extraction into a directive inside the current directive, but I have not been able to apply them to my use case.
Brief Synopsis:-
Two sample directives
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 col-xl-2">
<div class="form-group">
<label ng-show="visible">Input:
<input ng-model="person.name" type="text" required>
</label>
<p ng-show="!visible"> {{person.name}} </p>
</div>
</div>
And
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 col-xl-2">
<div class="form-group">
<label ng-show="visible">Input:
<select> <option value="volvo">Volvo</option> .... </select>
</label>
<p ng-show="!visible"> {{person.name}} </p>
</div>
</div>
I wish to condense them into directives like
<my-input-box ng-model="person.name" required></my-input-box>
<my-select-box options = "person.options"></my-select-box>
where my-input-box template is
<my-wrapper-box><input ng-model="person.name" type="text" required></my-wrapper-box>
and my-select-box template is
<my-wrapper-box><select>......</select></my-wrapper-box>
Please see this plunker: http://plnkr.co/edit/k6LWjn?p=preview
How do I extract the wrapper box so that 2-way binding is maintained with the ng-model and overriding attributes like required, bootstrap classes, validation regex, etc. in either the my-wrapper-box or the transcluded HTML element?
Many thanks to all who have read the question. I look forward to your suggestions.

Plunker.
I've added the following to myInputBox directive:
controller: function(){},
bindToController: true,
controllerAs: 'ctrl',
and changed the html to
<my-wrapper-box>
<input type="text" ng-required="reqd" ng-model="ctrl.value" />
</my-wrapper-box>

Related

How can I have two ng-contents in the same template controlled by *ngIf

I have an angular 2 component for displaying a bootstrap 3 button group. The component can either have a label or it can stand alone.
My solution was to use two ng-contents controlled by an *ngIf however, it refuses to display either of the ng-contents and does not throw an error.
Here's btn-multi.html:
<div class="form-group"
*ngIf="label">
<label class="control-label col-lg-2 col-md-3">
{{ label }}
</label>
<div class="col-lg-10 col-md-9">
<div class="btn-group">
<ng-content></ng-content>
</div>
</div>
</div>
<div class="btn-group"
*ngIf="!label">
<ng-content></ng-content>
</div>
And here is how it is used:
<btn-multi label="Some Label"
[(value)]="someValue">
<btn [value]="true">Yes</btn>
<btn [value]="false">No</btn>
</btn-multi>
And this is it working with just the one ng-content:
I'm currently on angular 2 beta-15.
Thanks!
NgIf is getting in the way of including the content since ng-content is being rendered after NgIf is evaluated.
You need to take another approach on it, maybe something like this:
<div [ngClass]="{'form-group': label}">
<label *ngIf="label" class="control-label col-lg-2 col-md-3">
{{ label }}
</label>
<div [ngClass]="{'col-lg-10 col-md-9': label}">
<div class="btn-group">
<ng-content></ng-content>
</div>
</div>
</div>
And there are also even better ways to do it, it all depends on how you want this component to be consumed.
FYI, just did this on the fly so its not tested, but just to give you a general idea.

AngularJS Multiple Transclusion

I have an AngularJS directive with multiple transclusions and one transclusion slot is wrapped by a form.
Everything is working fine except for the form validation messages.
The directive template:
<ng-form name="nbcardform" ng-submit="submit()" novalidate>
<ng-transclude ng-transclude-slot="back"></ng-transclude>
<div class="row">
<div class="col-xs-12">
<button type="submit">Save</button>
</div>
</div>
</ng-form>
Here is an example of the directive usage:
<nb-card>
<nb-card-back>
<input type="text" name="username" ng-model="vm.username" required>
<div ng-messages="nbcardform.username.$error" role="alert">
<div ng-message="required">Required field</div>
</div>
</nb-card-back>
<nb-card>
For some reason the expression nbcardform.username.$error is undefined.
Can someone help me with this?
You should be creating a subform in your directive as it's scope is (likely?) different and it has no idea what nbcardform is.
<nb-card ng-form="myDirectiveForm">
<nb-card-back>
<input type="text" name="username" ng-model="vm.username" required>
<div ng-messages="myDirectiveForm.username.$error" role="alert">
<div ng-message="required">Required field</div>
</div>
</nb-card-back>
<nb-card>
This will still wire in nicely and in the parent directive you could use something like this:
<ng-form name="nbcardform" ng-submit="submit()" novalidate>
<ng-transclude ng-transclude-slot="back"></ng-transclude>
<div class="row">
<div class="col-xs-12">
<button type="submit">Save</button>
</div>
</div>
{{ nbcardform.$valid }}
{{ nbcardform.myDirectiveForm.$valid }}
{{ nbcardform.myDirectiveForm.username.$valid }}
</ng-form>
Have you tried:
<div ng-messages="vm.username.$error" role="alert">
The transcluded content uses the outer scope unless you specify a different scope to the transclude function in your linking function. See "Providing your own Transclusion Scope" here. Note that once you do that, you may no longer be able to reference vm.

Passing ng-model from parent view to ng-include template

How can i pass a model defined in an controller to ng-include template?
For example: i have a model in my controller's $scope named "account". In the view i am referencing another template for address input.
the address template is below:
<div class="form-group col-lg-12">
<label>Street Address</label>
<textarea ng-model="physicalAddress.AddressLine1" class="form-control">dfdsf</textarea>
</div>
<div class="form-group col-lg-6">
<label>Town / City</label>
<input type="text" class="form-control" ng-model="address.city">
</div>
<div class="form-group col-lg-6">
<label>State / Region</label>
<input type="text" class="form-control" ng-model="address.region">
</div>
<div class="form-group col-lg-6">
<label>Postal / Zip Code</label>
<input type="text" class="form-control" ng-model="address.postalCode">
</div>
In the template above I want to add the physicalAddress object to my "account" model which is defined in the controller of parent view. The only way I see achieving this is by explicitly adding the address prefix address.physicalAddress.AddressLine1. However, this will defeat the purpose of having an template, because i want to be able to use the address template with other objects (shop,user etc).
is there any alternative way of achieving this?

How to re-use "component" in angularjs?

I have a view for editing address, that has a controller attached to it. It fetches all states and territories, with some internal nomenclature, allows user to select country/state, allows use to enter zip code and it fetches correct city etc. There is also validation, and model that gets populated. It depends on some server side services also. Now, I need to use this exact same functionality in like 5-6 forms. I don't want to copy or repeat logic, I would like to declare it in view as directive, and bind complete model (Address) to model of the enclosing view. How do I do this? How do I make connection between view controller and directive controller, and also how do I make connection between view's model (vm.address) and enclosing model (for example vm.client.address)?
Let's say I have this html snippet that has a controller (addressEditCtrl) associated with it.
<div class="row" data-ng-controller="addressEditCtrl">
<div class="col-xs-3">
<div class="form-group">
<label>Address:</label>
<span style="margin-left: 10px; font-weight: normal; font-size: 9px;">
Clear
</span>
<textarea class="form-control" style="height: 80px; width: 300px;"
data-ng-model="vm.address.street" maxlength="255"></textarea>
</div>
</div>
<div class="col-xs-6">
<div class="row">
<div class="col-xs-3">
<label for="zipCode">Zip Code:</label><br>
<input id="zipCode" type="text" class="form-control" placeholder="" style="float: left; width: 45%; margin-right: 10px;"
data-ng-model='vm.address.zipCode'
data-ng-blur="vm.bindZipCodeInfo()" maxlength="5">
<input id="zipCode2" name="zipCode2" type="text" class="form-control" placeholder="" style="float: left; width: 45%;"
data-ng-model='vm.mailingZipPlus' maxlength="4">
</div>
<div class="col-xs-4">
<label for="city">City:</label>
<input id="city" type="text" class="form-control" placeholder=""
data-ng-model='vm.address.city' maxlength="30">
</div>
<div class="form-group col-xs-5 p-0">
<label for="county">County:</label>
<input id="county" class="col-xs-12" type="text" disabled
data-ng-model='vm.address.countyName'>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<label for="states">State:</label>
<select id="states" class="form-control"
data-ng-model="vm.address.stateCode"
data-ng-options="state.Code as state.Description for state in vm.states">
<option value=''> Please Select</option>
</select>
</div>
<div class="col-xs-6">
<label for="countries">Country:</label>
<select id="countries" class="form-control"
data-ng-model="vm.selectedCountry"
data-ng-options="country as country.Description for country in vm.countries"></select>
</div>
</div>
</div>
</div>
I want to turn this into directive. This piece does not work, because in link function scopes are different (directive vs addressEditorCtrl). I am also not sure if I need link or controller defined on the directive as I am not manipulating anything.
(function (angular) {
'use strict';
angular
.module('app')
.directive('srAddressEditor', srAddressEditor);
srAddressEditor.$inject = [];
function srAddressEditor() {
var directive = {
templateUrl: '/app/shared/addressEditor/addressEdit.html',
scope: {
address:'=address'
}
};
return directive;
}
})(angular);
This is what I am after, two way binding. vm.client.mailingAddress and vm.address in above address editor are of the same shape. Any change in either one of them should be synched. I also want to be able to put multiple of these on the form, so isolated scope is what I am after.
<div data-ng-controller="clientEditCtrl">
<div class="col-xs-12">
<sr-address-editor address="vm.client.mailingAddress"></sr-address-editor>
</div>
</div>

AngularJS Validation - ng-messages-multiple not displaying multiple errors

All,
I am working on an AngularJS form and am trying to see how the ng-messages directive works with ng-messages-multiple. I can't seem to get it to pick up multiple errors. I expect to see both the required and minimum errors at the same time but for some reason I only see required, then minimum. I posted the HTML below. I have the ng-messages included using bower, the script call in my index.html page, and I am injecting into my app.js module as required.
I am using AngularJS v1.3.2 in this project.
<div class="panel panel-default">
<div class="panel-heading">
<h1>Validation Test Form</h1>
</div>
<div class="panel-body">
<form class="form" name="form" role="form" ng-submit="submit(form)">
<div class="row">
<div class="form-group" show-errors>
<label for="name">Name:</label>
<input
class="form-control"
type="text"
name="name"
ng-model="formModel.name"
minlength="5"
required/>
<div ng-messages="form.name.$error" ng-messages-multiple class="has-error">
<div ng-message="required">Required!</div>
<div ng-message="minlength">Minimum length is 5</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group">
<button class="btn btn-success" type="submit">Save</button>
</div>
</div>
</form>
</div>
<div class="panel-footer">
{{formError}}
</div>
</div>
Try to use ng-minlength instead minlength
<input
class="form-control"
type="text"
name="name"
ng-model="formModel.name"
ng-minlength="5"
required/>
instead
<input
class="form-control"
type="text"
name="name"
ng-model="formModel.name"
minlength="5"
required/>
EDIT
It is normal behaviour for ng-minlength directive, this directive validate only when we have not 0 size of input, entered a value it must be at least 5 characters long, but it's ok to leave the field empty, and, unfortunately, in anyway you don't achieve, that you want. I offer you to create your custom directive or see in direction ng-pattern directive with need behaviour, if you very want that showing two message.

Resources