Passing variables to directive - angularjs

I'm hoping someone can help me get my head around passing a variable into a directive. I've looked at other answers to what seem like similar questions to mine, but none seem to apply directly and/or I don't understand them.
So, my problem is that I have a toolbar that I want to be able to have access to variables found in different controllers. Not sure if this is even possible.
This is my directive (the scope and link options are all wrong, so they are here just for show):
.directive('toolbar', function(){
return {
restrict: 'E',
scope: {
page: '='
},
templateUrl: '/templates/toolbar.html',
link: function(scope, elem, attrs) {
scope.page = vm.page;
},
replace: false
};
})
I want to use it once in index.html, like this:
<body ng-app="app" ng-cloak layout="column">
<div layout-align="center center" layout-margin flex="50">
<img src="/images/logo.png" class="logo">
</div>
<toolbar page="{{vm.page}}"></toolbar>
<md-content>
<div ui-view ng-view></div>
</md-content>
</body>
where vm.page is a variable found in the controller that drives the ui-view, which is set up like this...
angular
.module('app')
.controller('dogsCtrl', function(dogsFactory, sessionService,
searchService, locationService, adoptableService, toastService,
errorHandlerService, $document, $mdSidenav, $scope, $state, $q) {
var vm = this;
vm.page = 'Home';
vm.currentUser = sessionService.getUser(); ....
I need to be able to access vm.page and the vm.currentUser object in a sub-directive on the toolbar. The toolbar template looks like this:
<md-toolbar class="md-menu-toolbar" hide show-gt-xs>
<div layout="row" layout-align="space-between center">
<div class="page-title" flex hide show-gt-md>
{{ vm.page }}
</div>
<div class="main-menu">
<md-menu-bar>
<menu></menu>
</md-menu-bar>
</div>
<md-input-container class="search">
<md-select name='breed' ng-model="breed._id" placeholder="Select a breed" ng-change="vm.getDogDetail(breed._id)">
<md-option value="{{breed._id}}" ng-repeat="breed in vm.dbBreeds"> {{ breed.breed }}</md-option>
</md-select>
</md-input-container>
</div>
As it stands now, I have to repeat the toolbar directive on every page, but I would rather not do it that way. Is this possible?

It should be like this
<toolbar page="vm.page"></toolbar>
because the page is two way binding.
OR
change the page scope type to this
restrict: 'E',
scope: {
page: '#'
},

Related

ionic custom directive not working in emulator

I am building an application in Ionic 1 that uses custom directives. These work when testing in the browser, but do not show up in the emulator or on the iphone. I have seen that this has been an issue for others, but none of the suggestions have worked for me. Are there any suggestions out there for how to make a custom directive appear in the emulator (or, more importantly, on the iphone)?
Here is what I have tried. None of this has worked.
Moving the directives and controllers into the same file
Changing element directives to attribute directives (i.e <div my-directive></div> instead of <my-directive></my-directive>
Adding the CSS display: block to my element directives
Adding <ion-view><ion-content> tags around the directives, both within the directive templates, and then outside of the templates around the directives themselves in the parent templates.
Link to my project on github
And here are the essential pieces of code:
Directives
angular.module('starter.directives', [])
.directive('grid', function(GridFactory){
return {
restrict: 'AE',
templateUrl: '/templates/grid.html',
link: function(scope){
//does stuff
}
}
})
.directive('cell', function(){
return {
restrict: 'AE',
templateUrl: '/templates/cell.html',
scope: {
marker: '=',
isAlive: '=',
cellClick: '&'
}
}
})
Directive Templates
grid
<div class="row" ng-repeat="row in grid" style="height: 5vw" >
<div class="col gc-border gc-no-padding" ng-repeat="cell in row">
<cell is-alive=isAlive(cell.marker) marker=cell.marker neighbors = cell.neighbors cell-click=cellClick(cell)></cell>
</div>
</div>
cell
parent template
<ion-view view-title="Play">
<ion-content>
<div class="card">
<p>It rebuilt 2!</p>
<grid></grid>
</div>
<div class="button-bar">
<a class="button button-calm" on-tap="step()">Step</a>
<a class="button button-calm" on-tap="play()">{{isPlaying ? "Pause" : "Play" }}</a>
<a class="button button-calm" on-tap="clear()">Clear</a>
</div>
</ion-content>
</ion-view>
For the templateUrl in your directives you have the following:
.directive('grid', function(GridFactory){
return {
restrict: 'E',
templateUrl: '/templates/grid.html',
link: function(scope){
scope.grid = GridFactory.makeGrid(20,20);
scope.alive = [];
...
Remove the forward slash:
templateUrl: '/templates/grid.html', to
templateUrl: 'templates/grid.html', and for the rest of the templateUrl's.

How to communicate between sibling directives/components in angular 1.5

What is the best way to communicate between angular 1.5 sibling components and/or directives.
I am using angular material.
I am using ui-router.
I am trying to keep my components and directives separate and not dependent on each other.
I would like to refactor, where appropriate, my directives into .component() modules.
I have a navbar that I have separated into a directive (navBar). In that navbar, I have a search bar that I would like to filter a list. The list is in a sibling directive.
Originally I had the navbar directive(and tried to use it as .compontent()) outside of the scope of MainCtrl as defined by ui-router. This seemed to make sense to me as the navbar would be relatively consistent throughout the application.
I deferred to putting it inside the scope of MainCtrl where I can then bind properties from MainCtrl to elements in my navBar directive. This seems wrong as now the navBar and fileBrowser are coupled with the MainCtrl.
Other options I was looking into:
Using and scope.$watch() to define properties on the parent component from the child component navBar. Then in the other child component, fileBrowser, using scope.$watch() to watch for these changes in the parent component and respond accordingly.
Using a service to store data and pass data.
Using $emit, $broadcast events.
What is the best solution in this situation to keep my directive/components separate? What is the best/cleanest/recommended way to communicate between sibling directive/components?
This state is initiated by ui-router
main.component.js
angular.module('glossa')
.component('mainComponent', {
controller: MainCtrl,
controllerAs: 'vm',
transclude: true,
templateUrl: 'app/main/main.html'
});
function MainCtrl($scope, nodeSrvc, fileSrvc) {
var vm = this;
vm.selectedFile = {};
vm.fileList = [];
vm.searchText = '';
vm.filteredFiles = [];
activate();
function activate() {
buildFileList();
}
/**
* Queries for all files in db.
*/
function buildFileList() {
fileSrvc.queryAllFiles().then(function(docs) {
vm.fileList = docs;
});
}
}
main.html
//where the input where I filter the list
<navbar-directive></navbar-directive>
<div flex layout="row" >
//where the list is located
<file-browser layout="column"></file-browser>
<tabbar></tabbar>
</div>
<drawer-directive></drawer-directive>
I would like navbar to filter a list located in the sibling directive or component filebrowser
navbar.directive.js
angular.module('glossa')
.directive('navbarDirective', navBarDirective);
function navBarDirective(fileSrvc) {
var directive = {
restrict: 'E',
replace: true,
controller: NavbarCtrl,
controllerAs: 'navVm',
templateUrl: 'components/navbar/navbar.html',
bindToController: true
};
return directive;
}
navbar.html
<md-toolbar
layout="row"
class="nav-content primary-bg"
md-whiteframe="1"
md-selected-nav-item="currentNavItem"
nav-bar-aria-label="navigation links">
<span flex></span>
<div class="md-toolbar-tools">
<md-input-container md-no-float flex >
<form ng-submit="vm.searchSubmit()">
<input ng-model="vm.searchText" placeholder="Search...">
</form>
</md-input-container>
</div>
</md-toolbar>
This is where the list I'd like to filter is located.
filebrowser.js
angular.module('glossa')
.directive('fileBrowser', fileBrowser);
function fileBrowser() {
var directive = {
restrict: 'E',
templateUrl: 'components/filebrowser/filebrowser.html'
};
return directive;
}
filebrowser.html
<md-sidenav
md-component-id="left"
md-is-locked-open="true"
layout="column">
<md-content>
<md-list flex>
<md-list-item ng-repeat="file in vm.filteredFiles = (vm.fileList | filter: vm.searchText)" class="md-2-line">
<md-item-content md-ink-ripple layout="row" layout-align="start center">
<div class="md-list-item-text" layout="column">
<h3>{{file.name}}</h3>
<p>Preview of first few lines of a baseline</p>
</div>
</md-item-content>
</md-list-item>
</md-list>
</md-content>
</md-sidenav>
To communicate berween sibling components use bidirectional binding:
angular.module('glossa')
.directive('navbarDirective', navBarDirective);
function navBarDirective(fileSrvc) {
var directive = {
//Use bi-directional binding
scope: {
searchText: '='
},
restrict: 'E',
replace: true,
controller: NavbarCtrl,
controllerAs: 'navVm',
templateUrl: 'components/navbar/navbar.html',
bindToController: true
};
return directive;
}
HTML
<nav-bar-directive search-text="main.searchText">
</nav-bar-directive>
<sibling-component search-text="main.searchText">
</sibling-component>

When using custom directive, in which cases would it be better to use attribute instead of element type

From the angularJS docs, it's written that the common case for using element directive
is when you are creating a Domain-Specific Language for parts of your template. Use an attribute when you are decorating an existing element with new functionality.
The documentation gives an example for the element type but not for the attribute type. What does decorating an existing element with new functionality mean? Can someone provide an example?
UPDATE
I know that syntax for writing a directive.
The example given in the doc for element is as follows
Using an element for the myCustomer directive is clearly the right choice because you're not decorating an element with some "customer" behavior; you're defining the core behavior of the element as a customer component.
I am looking for a similar example, but for attribute. An example that explains the case when using the custom directive as attribute would be useful.
I got the answer I want. The links provide by #Mikko, link2 and link3 helped.
Those links give enough information to explain what decorating an existing element with new functionality mean. For eg: adding validation behavior to an input element.
You can use an 'Element' or an 'Attribute' in a Directive.
All you need to do is to state what you want to use in your
template in your 'Directive' file i.e.
restrict: 'A' ... For Attributes and
restrict: 'E' ... For Elements and
restrict: 'AE' ... For Elements and Attributes.
Refer to following example.
//------------------------------------------------------------------------------------
# change-password.directive.js
# In this case you are using ... restrict: 'A' for Attributes
# So your template will have the following
# <!-- Directive As Attribute -->
# <div foo-change-password></div>
# Note that 'fooChangePassword' in the Directive below
# changes to 'foo-change-password'in your template
(function() {
'use strict';
angular
.module('app.foo.authentication')
.directive('fooChangePassword', changePassword);
/* #ngInject */
function changePassword() {
var directive = {
restrict: 'A',
templateUrl: 'app/foo/authentication/change-password/change-password-validation.tmpl.html',
transclude: true,
replace: true,
bindToController: true,
controller: 'ChangePasswordController',
controllerAs: 'vm',
link: link
};
return directive;
function link($scope, $element, attrs) {
}
}
})();
//------------------------------------------------------------------------------------
# change-password.directive.js
# In this case you are using ... restrict: 'E' ... For Elements
# So your template will have the following
# <!-- Directive As Element -->
# <foo-change-password></foo-change-password>
# Note that 'fooChangePassword' in the Directive below
# changes to 'foo-change-password' in your template
(function() {
'use strict';
angular
.module('app.foo.authentication')
.directive('fooChangePassword', changePassword);
/* #ngInject */
function changePassword() {
var directive = {
restrict: 'E',
templateUrl: 'app/foo/authentication/change-password/change-password-validation.tmpl.html',
transclude: true,
replace: true,
bindToController: true,
controller: 'ChangePasswordController',
controllerAs: 'vm',
link: link
};
return directive;
function link($scope, $element, attrs) {
}
}
})();
//------------------------------------------------------------------------------------
# change-password-validation.tmpl.html
# This will be the template that you will inject in the 'change-password.tmpl.html'
# below. Note that the template being injected must have a <div> ... </div>.
# Otherwise your page wont render.
<div>
<form name="changePasswordForm">
<md-input-container class="md-block">
<label for="password" translate>CHANGE-PASSWORD.PASSWORD.LABEL</label>
<input id="password"
label="password"
name="password"
type="password"
ng-model="vm.user.password"
tri-same-password="changePasswordForm.confirm"
required/>
</md-input-container>
<md-input-container class="md-block">
<label for="password" translate>CHANGE-PASSWORD.PASSWORD_CONFIRM.LABEL</label>
<input id="confirm"
label="confirm"
name="confirm"
type="password"
ng-model="vm.user.confirm"
ng-keypress="handleKeyPress($event)"
tri-same-password="changePasswordForm.password"
ng-minlength="8"
required/>
</md-input-container>
<div layout="row" layout-align="center center">
<md-progress-circular ng-show="vm.loading" md-mode="indeterminate"></md-progress-circular>
</div>
<div layout="row" class="change-password-buttons-margin-top">
<md-button
class="md-warn full-width md-raised md-button"
ng-click="vm.backToLoginClick()"
translate="CHANGE-PASSWORD.LOGIN"
aria-label="{{'CHANGE-PASSWORD.LOGIN' | translate}}">
</md-button>
<md-button
class="md-primary full-width md-raised md-button"
ng-disabled="vm.user.password !== vm.user.confirm"
ng-click="vm.changePasswordClick()"
translate="CHANGE-PASSWORD.BUTTON"
aria-label="{{'CHANGE-PASSWORD.BUTTON' | translate}}">
</md-button>
</div>
</form>
</div>
//------------------------------------------------------------------------------------
# change-password.tmpl.html
# In this case
# <!-- Directive As Element -->
# <foo-change-password></foo-change-password>
<div layout="row" flex layout-padding layout-fill layout-align="center center">
<div class="change-password-card" flex="40" flex-lg="50" flex-md="70" flex-xs="100">
<md-card>
<md-toolbar class="padding-20 change-password-card-header">
<div layout="row" layout-align="center center">
<h1 class="md-headline" translate>CHANGE-PASSWORD.TITLE</h1>
</div>
</md-toolbar>
<md-content class="md-padding">
<!-- Directive As Element -->
<foo-change-password></foo-change-password>
</md-card>
</div>
</div>
//------------------------------------------------------------------------------------
# change-password.tmpl.html
# In this case
# <!-- Directive As Attribute -->
# <div foo-change-password></div>
<div layout="row" flex layout-padding layout-fill layout-align="center center">
<div class="change-password-card" flex="40" flex-lg="50" flex-md="70" flex-xs="100">
<md-card>
<md-toolbar class="padding-20 change-password-card-header">
<div layout="row" layout-align="center center">
<h1 class="md-headline" translate>CHANGE-PASSWORD.TITLE</h1>
</div>
</md-toolbar>
<md-content class="md-padding">
<!-- Directive As Attribute -->
<div foo-change-password></div>
</md-content>
</md-card>
</div>
</div>
//------------------------------------------------------------------------------------
# In both instances your page will render.

AngularJS - Transclusion Containing Transclusion?

I am using Bootstrap UI's accordion directive. This uses transclusion under the hood. I have some logic that needs repeated, so I am trying to create a directive that wraps the accordian, which also uses transclusion.
<div>
<div accordion>
<div accordion-group is-open="isOpen">
<div accordion-heading>
<span class="glyphicon" ng-class="{'glyphicon-minus-sign': isOpen, 'glyphicon-plus-sign': !isOpen}"></span>
<strong>{{headerTitle}}</strong>
</div>
<div ng-transclude></div>
</div>
</div>
</div>
Here is the JavaScript:
application.directive('collapsePanel', ['baseUrl', function (baseUrl) {
return {
restrict: 'A',
templateUrl: baseUrl + 'content/templates/collapse_panel.html',
replace: true,
transclude: true,
scope: {
headerTitle: '#'
},
controller: ['$scope', function ($scope) {
$scope.isOpen = false;
}]
};
}]);
It should be as simple to use as:
<div collapse-panel header-title="Title">
{{scopeVariable}}
</div>
Assuming scopeVariable is in my controller, I would think its value would appear. From what I can tell, the scope belongs to the collapse-panel rather than the outer scope. It is almost like having nested transclusion directives is causing my problem.
Is there a trick to nesting transclusions like this?

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

Resources