AngularJS: ngInclude with custom scope - angularjs

I have an existing directive that creates a generic wizard that will be used across my current organization. This wizard will have steps, and each step has an HTML template to be loaded as the "body" of the wizard. Currently, I'm adding the template using ng-include. I don't know which attributes the wizard will have, as it will come from the wizard consumer.
Question: Is there a way to bind the template models (attributes) to a directive scope variable instead to the scope itself?
Currently I have:
Directive HTML
<div ng-include="/step1.html"></div>
Note: This step1.html is just an example. The actual HTML template will come as a configuration parameter from the wizard directive.
Step 1 HTML
<input type="text" ng-model="data.email"/>
This is "forcing" the wizard consumer to include "data" for each of his attributes.
As a result, I was looking for something like this:
Directive HTML
<div ng-include="/step1.html" ng-scope="scope.data"></div>
Step 1 HTML
<input type="text" ng-model="email"/>

I was able to solve the problem using ng-transclude. Now, the consumer does not need to send me the HTML code (step1.html). Right now, this goes directly into the wizard directive body, as follows:
Sample Controller
angular.module('sampleApp').controller('SampleCtrl', function ($scope) {
$scope.email;
});
Sample HTML
<div wizard>
<div step title="Step 1">
<input type="text" ng-model="email"/>
</div>
</div>
Directives with transclude option
angular.module('sampleApp').directive('wizard', function () {
return {
restrict: 'A',
transclude: true,
scope: {
},
template: '<div> Wizard <div class="stepsContainer" ng-transclude /></div>'
};
});
angular.module('sampleApp').directive('step', function () {
return {
require: "^wizard",
restrict: 'A',
transclude: true,
scope: {
title: '#'
},
template: '<div ng-show="current"> {{ title }} <div ng-transclude></div></div>'
};
});

I believe what you need is a directive, with which you can have isolate scope, just like taking parameter from outer scope.
Directive
yourModule.directive('step1', function() {
return {
scope: {
data: '=anyname'
},
templateUrl: "/step1.html"
};
})
Step1 HTML
<input type="text" ng-model="data.email"/>
Then when the consumer uses it:
<div step1 anyname="scope.consumerData"></div>
Then anyname can be named anything meaningful to the consumer, without knowing the template is actually using data.

Related

AngularJS directive input fields are read only

So I am trying to write an AngularJS (version 1.4.8) directive which outputs the value of an attribute in an input box. The directive looks like this:
(function () {
angular.module("myApp").directive("inputAttributeDirective", function () {
return {
templateUrl: "inputAttributeTemplate.html",
// The directive is a custom element
restrict: "E",
// Declare the scope of the directive as isolated
scope: {
attribute: "=",
value: "="
}
}
});
})();
And the HTML template for it is the following (that's in inputAttributeTemplate.html):
<label id="AttributeLabel" class="control-label">{{ attribute }}</label>
<div id="AttributeInput" class="input-prepend">
<input id="attributeName" name="attributeName" class="form-control input-large" type="text" ng-model="value"/>
</div>
Where 'attribute' and 'value' correspond to the attributes of an object called 'Client' that looks like this:
vm.client = {
name: "Some Client",
description: "This is a cool client",
networkPath: "network path 1",
active: "1"
}
So basically, a Client has four attributes, name, description, networkPath, and active.
And the way I am including the directive is like so (this is in the parent HTML file):
<div class="panel-body">
<div ng-repeat="(key, value) in vm.client">
<incident-attribute-directive attribute="key" value="value"></incident-attribute-directive>
</div>
</div>
The problem is that, while it does display the attributes correctly, they are read-only and I need them to be editable:
Any idea why they are read-only?

Reusing a directive template for multiple forms with isolated scope

I'm working on a project where the user needs to be able to create many instances of the same form. As of now the user can click a button to create one or more forms. The problem I'm having is that by isolating the scope, as I think I should be doing given that I'm reusing the same directive, my ng-models can't communicate with the parent controller.
My directive for <rule-form></rule-form>..
(function(){
'use strict';
var ruleForm = function(){
return{
restrict: 'E',
replace: true,
scope: {},
templateUrl: 'edit/rule-create/ruleForm.html',
link: function(scope, element, attrs){
scope.length = document.forms.length;
}
}
}
angular.module('ganeshaApp')
.directive('ruleForm', ruleForm)
})();
And my template...
<form class="edit__div--rule-form" name="form_{{length}}">
<input type="text" placeholder="Rule Title" ng-model="rcCtrl.ruleTitle">
<div class="edit__div--rc-toolbar">
<select class="edit__btn--rc-select" ng-model="rcCtrl.select" apply-statement-type>
<option value="obligation statement">obligation statement</option>
<option value="prohibition statement">prohibition statement</option>
<option value="permission statement">restricted permission statement</option>
</select>
<div class="edit__btn--rc-noun">
Add noun/verb
</div>
<div class="edit__btn--rc-save" ng-click="rcCtrl.saveRule()">
<span class="glyphicon glyphicon-floppy-saved"></span>Save
</div>
<div class="edit__btn--rc-cancel">
<span class="glyphicon glyphicon-remove"></span>
Cancel
</div>
</div>
<div class="edit__select--statement-type"></div>
<div ng-show="rcCtrl.showTextEdit" class="edit__div--rule-form-text" contenteditable="true" ng-model="rcCtrl.ruleText"></div>
I tried using $parent , (e.g. $parent.rcCtrl.ruleText), but then I'm back to the problem of not having isolated scopes and each form updates the others. I'm a bit confused about this really. Does anyone know a solution to this problem, or is it just a problem with my code?
Add a controller to your directive.
angular.module('ganeshaApp').directive('ruleForm', function(){
return {
restrict: 'E',
replace: true,
scope: {},
templateUrl: 'edit/rule-create/ruleForm.html',
controller: "rulesFormController as rcCtrl",
link: function(scope, element, attrs){
scope.length = document.forms.length;
}
}
});
The AngularJS $compile service will then create an instance of the controller for each instance of the directive and attach it to each isolate scope.
For more information, see the AngularJS Comprehensive Directive API Reference.

Angular Directive: Isolate Scope & Transclude is true, but HTML in the directive needs to continue to bind to parent $scope

To start, I set up a JSFiddle here: http://jsfiddle.net/qLagkgt5/3/
I hope the title is clear, but basically, I am trying to use directives to help in repeatable html. In the example on JSFiddle, I am using a box directive with options for spacing and box-type.
The html that I am turning into repeatable code:
<div class="box">
<div class="box-inner">
{{CONTENT GOES HERE}}
</div>
</div>
With optional classes:
<div class="box spacing-small box-rounded">
<div class="box-inner">
{{CONTENT GOES HERE}}
</div>
</div>
What I'd like to be able to do is:
<box spacing="small" box-type="rounded">
{{CONTENT GOES HERE}}
</box>
This is obviously a very simplified set of html that doesn't necessarily need a directive, but it is just an example to illustrate what I am running into.
Now to the angular side of things...
Here is my controller:
app.controller("Ctrl", ["$scope", function($scope) {
$scope.text = "Starting Text... FOOBAR!";
}]);
And here is my directive:
app.directive("box", function() {
return {
restrict: "E",
transclude: true,
replace: true,
scope: {
spacing: "#",
boxType: "#"
},
template: '<div class="box" ng-class="{\'spacing-{{spacing}}\' : spacing, \'box-{{boxType}}\' : boxType}"> <div class="box-inner" ng-transclude></div></div>'
}
});
If I now place my directive inside html with a controller like this:
<div class="wrap" ng-controller="controller">
{{text}}
<box spacing="small">
<input ng-model="text"/>
</box>
</div>
The $scope.text that is outside the <box> is never updated when I change the input inside the box.
How do I make it so that when this directive is used, the content inside the box goes up to the parent scope rather then the isolated scope?
If I nest a box inside another box, can I also have it go up to the same parent scope?
Thanks!
I read something here on stackoverflow that immediately jumped in my head when I read your post. It said something like "If you do it without a dot you are doing it wrong..." I'll search for the article and post it here as soon as I found it but for now I think I "fixed" your code:
<div ng-app="app" ng-controller="Ctrl">
<h1><span class="grey">$scope.data.text:</span> {{data.text}}</h1>
<box spacing="large" box-type="rounded">
<h2><span class="grey">$scope.text in box directive:</span> {{data.text}}</h2>
<input ng-model="data.text" />
</box>
<box spacing="small" box-type="rounded-shadow">
<h2><span class="grey">$scope.text in different box directive:</span> {{data.text}}</h2>
<input ng-model="data.text" />
</box>
</div>
var app = angular.module("app", []);
app.controller("Ctrl", ["$scope", function($scope) {
$scope.data = {};
$scope.data.text = "Starting Text... FOOBAR!";
}]);
app.directive("box", function() {
return {
restrict: "E",
transclude: true,
replace: true,
scope: {
spacing: "#",
boxType: "#"
},
template: '<div class="box" ng-class="{\'spacing-{{spacing}}\' : spacing, \'box-{{boxType}}\' : boxType}"> <div class="box-inner" ng-transclude></div></div>'
}
});
You have to create an object and use this for databinding. I am now using "data.text" and do the binding with this expression.
Cheers,
Tim.
P.S.: There are a lot of links.
To mention only two:
AngularJS: If you are not using a .(dot) in your models you are doing it wrong?
AngularJS: dot in ng-model

Directive to directive scope

This is related to a question: Listen for broadcast in sub directive
I have two directives, one is a child, one is a parent. The issue is, I what the child to only catch an event of the parent directive. Here is what I need:
I have some check boxes and a select all button for a group of check boxes. When I click the "select all" button, I want it to select all the boxes. This part I have working. The catch is I have two instances of this on the page. Right now when I click "select all", all of the check boxes on the page are selected, not just the ones inside the directive instance. I'm sure this is a scope problem... but I'm not sure what. Here is my code:
HTML:
<div all-checkboxes-broadcast>
<div all-checkboxes-listener>
<input type="checkbox" />
</div>
<a ng-click="checkAll()" href="">Select All</a> <!-- this should ONLY check the boxes above, not the ones below. Currently clicking either select checks all the boxes on the page.-->
</div>
<div all-checkboxes-broadcast>
<div all-checkboxes-listener>
<input type="checkbox" />
</div>
<a ng-click="checkAll()" href="">Select All</a>
</div>
AngularJS:
app.directive('allCheckboxesBroadcast', [function () {
return {
restrict: 'A',
scope: true,
controller: ["$scope",function($scope) {
//select all checkboxes
$scope.checkAll = function () {
$scope.$broadcast('allCheckboxes',true);
};
}]
}
}]);
app.directive('allCheckboxesListener', [function () {
return {
restrict: 'A',
require: '^allCheckboxesBroadcast',
link: function(scope, element, attrs) {
scope.$on('allCheckboxes', function(event, shouldCheckAll) {
element.find('input').prop('checked',shouldCheckAll);
});
}
}
}]);
Edit: I found the answer myself. By adding "scope: true" to the parent directive, it creates a child scope that will prototypically inherit from its parent, which creates the functionality I was looking for. If anyone has a better way to do it, I'm all ears.
I found the answer myself. By adding "scope: true" to the parent directive (I changed my original question to include this edit), it creates a child scope that will prototypically inherit from its parent, which creates the functionality I was looking for. If anyone has a better way to do it, I'm all ears.
Perhaps you need to isolate the scope by adding a parameter in your return object in the allCheckboxesBroadcast directive, eg: scope: {}
Something you might wanna do also is label those directives:
<div all-checkboxes-broadcast="1">
<div all-checkboxes-listener="1">
<input type="checkbox" />
</div>
<a ng-click="checkAll()" href="">Select All</a> <!-- this should ONLY check the boxes above, not the ones below. Currently clicking either select checks all the boxes on the page.-->
</div>
<div all-checkboxes-broadcast="2">
<div all-checkboxes-listener="2">
<input type="checkbox" />
</div>
<a ng-click="checkAll()" href="">Select All</a>
</div>
Then:
app.directive('allCheckboxesBroadcast', [function () {
return {
restrict: 'A',
scope: { bcId: "&allCheckboxesBroadcast" },
controller: ["$scope",function($scope) {
//select all checkboxes
$scope.checkAll = function () {
$scope.$broadcast('allCheckboxes',true, $scope.bcId);
};
}]
}
}]);
app.directive('allCheckboxesListener', [function () {
return {
restrict: 'A',
scope: { bcId: "&allCheckboxesListener" },
require: '^allCheckboxesBroadcast',
link: function(scope, element, attrs) {
scope.$on('allCheckboxes', function(event, shouldCheckAll, id) {
if(id == scope.bcId){
element.find('input').prop('checked',shouldCheckAll);
}
});
}
}
}]);
This is a workaround I am using in one of my modules. If you find better options, let me know.
Update: I fixed your plnkr, took me longer than I thought, I am no angular expert and isolated scopes can mess with my mind still. The main issue was that you didn't use a template so your methods defined in your directives were not accessible in the html. I also replaced the & by = in the scope assignment. I thought & would work but I guess I should read the doc again. Anyway, there it is:
http://plnkr.co/edit/J2qBBj3R0rEkfxgPBE84?p=preview

How to write an angularjs directive that makes use of both scope and attributes and refer it thru compiled partial?

I want to write a directive which takes advantage of custom attributes, as follows:
<plant-stages
title="Exploration<br/>du cycle de<br/>développement<br/>de la plante"
></plant-stages>
The controller is currently as follows:
app.directive('plantStages', function () {
return {
restrict: 'AE',
templateUrl: 'corn.figure.plant.stages.html',
link: function (scope, element, attrs) {
scope.title = attrs.title;
}
};
});
The partial is as follows:
<figure class="cornStages">
<div>
<p>{{title}}</p>
</div>
<div ng-repeat="stage in stages">
<div class="stage{{stage.stage}}"></div>
<div>
BBCH : {{stage.bbch}}<br/>
{{stage.displayName}}
</div>
</div>
</figure>
The partial makes use of some scope model variables.
And {{title}} should support plain HTML injection out of the view which embeds it, hence should be compiled. I tried to support this but without success.
What modification should I make to have the HTML compiled?
A bonus question: when I pass the attribute in, I create a dummy title variable in the scope that persists where it should only be local. How would one make changes to handle this?
If you want to wrap HTML in your custom directive take a look at the transclude option (see docs):
module.directive('myDirective', function() {
return {
restrict: 'E',
transclude: true,
template: '<div ng-transclude></div>'
};
});
This enables you to place HTML within the directive tag which can be used in the template:
<div ng-controller="Controller">
<my-directive>
<h1>Test</h1>
</my-directive>
</div>
In case you really want to pass HTML via an attribute use ng-bind-html. This requires the ngSanitize module:
module.directive('myDirective', function () {
return {
restrict: 'E',
template: '<div ng-bind-html="title"></div>',
scope: {
title:'#'
}
};
});
I added this to your fiddle.

Resources