I create a directive based on ES6 style:
export default class myDirective {
constructor() {
this.restrict = 'E';
this.scope = {};
this.link = link;
}
link() {
console.log('link myDirective');
}
}
then in index.js:
import angular from 'angular';
import myDirective from './myDirective';
export default angular
.module('app.directives', [])
.directive('myDirective ', () => new myDirective())
.name;
But when I call myDirective on html like: <my-directive><my-directive> it does not call link function or compile function. What can I do?
We use ES6 here, and our directives dont really look like that, I'll give an example:
import templateUrl from './some.html';
import SomeController from './someController';
export default () => ({
templateUrl,
controller: SomeController,
controllerAs: 'vm',
scope: {
someVariable: '='
},
link: (scope, element, attrs, controller) => {
scope.link = {
someFunction: function some() { }
}
},
bindToController: true
});
You get the idea anyway. Try structuring it like that and see if the link function works as you would expect.
I have the same problem using AngularJS + ES6 + Webpack. Maybe you could add this in your Directive, in your compile function:
compile() {
//console.log("compile");
return this.link.bind(this);
}
Check this links for more acurated info:
https://github.com/geniuscarrier/webpack-angular-es6/blob/master/app/modules/home/directive/footer.js
https://www.michaelbromley.co.uk/blog/exploring-es6-classes-in-angularjs-1.x/
Related
First of all, I'm using components.
I have this "parent" component:
(function() {
'use strict';
angular
.module('parentModule', [])
.component('parent', {
templateUrl: 'parent.tpl.html',
controller: ParentCtrl,
transclude: true,
bindings: {
item: '='
}
});
function ParentCtrl() {
var vm = this;
vm.item = {
'id': 1,
'name': 'test'
};
}
})();
And I'm simply trying to share the object item with another component, like this:
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
require: {
parent: '^item'
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
View (Parent):
Parent Component:
<h1 ng-bind='$ctrl.item.name'></h1>
<child></child>
View (Child):
Child component:
Here I want to print the test that is in the parent component
<h2 ng-bind='$ctrl.item.name'></h2>
Actually I'm getting the following error:
Expression 'undefined' in attribute 'item' used with directive
'parent' is non-assignable!
Here's the DEMO to illustrate better the situation
Can you explain to me how I can make it work?
You need to remove the bindings from yor parent component.
bindings binds to the component controller like scope binds to a directive's scope. You're not passing anything to <parent></parent> So you have to remove it.
Then your child component requires a parent component, not an item.
So
require: {
parent: '^parent'
}
Of course the child template should be modified to:
<h2 ng-bind='$ctrl.parent.item.name'></h2>
Finally, if from the child controller you want to log the item that is inside the parent, you will have to write:
function ChildCtrl($timeout) {
var vm = this;
$timeout(function() {
console.log(vm.parent.item);
});
}
I never need the timeout in my components, so there might be something obvious that I missed.
http://plnkr.co/edit/0DRlbedeXN1Z5ZL45Ysf?p=preview
EDIT:
Oh I forgot, you need to use the $onInit hook:
this.$onInit = function() {
console.log(vm.parent.item);
}
Your child should take the item as input via bindings.
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
bindings: {
item: '='
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
So your parent template will look like
<h1 ng-bind='$ctrl.item.name'></h1>
<child item="$ctrl.item"></child>
The rest should work same.
I have the following Angular 1 directive:
return function(module) {
module.directive('myButtonUpgrade', myButtonUpgrade);
function myButtonUpgrade() {
return {
restrict: 'E',
template: template(),
scope: {
image: '=',
imageColour: '='
}
};
function template() {
var html = '<my-button visible="flipVisible"\
enabled="enabled"\
image="{{::image}}"\
image-colour="{{::imageColour}}"\
</my-button>';
return html;
}
}
return myButtonUpgrade;
};
The directive uses "my-button" directive inside its template. The "my-button" directive requires "image" and "image-colour" attribute values to be present before compile its template.
I have got the following Angular 2 component which upgrades Angular 1:
import { Component } from '#angular/core';
import { upgradeAdapter } from '../../../upgrade/index';
var myButtonEx = upgradeAdapter.upgradeNg1Component('myButtonUpgrade');
#Component({
selector: 'my-button-ex',
template: '<my-button-ex [image]="refresh"></my-button-ex>',
directives: [myButtonEx]
})
export class ButtonExComponent {
}
As you can see the template sets [image] bindings to "refresh", however that did not resolve when rendering "my-button-ex" Angular 1 directive.
It looks like the upgrade adapter tries to compile HTML syntax first without resolving template arguments:
upgrade_ng1_adapter.ts
compileTemplate(...){
this.linkFn = compileHtml(this.directive.template);
}
"this.directive.template" - template arguments not resolved!
Any ideas?
in folder directives i created two files: directives.js and color.js
directives i've imported into app.js
directives.js:
import angular from 'angular';
import ColorDirective from './color';
const moduleName = 'app.directives';
angular.module(moduleName, [])
.directive('color', ColorDirective);
export default moduleName;
color.js
import angular from 'angular';
let ColorDirective = function () {
return {
link: function (scope, element) {
console.log('ColorDirective');
}
}
}
export default ColorDirective;
and on one element in component i've added color as attr.
But it's not working. I mean inner link loop. Why? What i have wrong coded? How to use directives with angular 1.5 & es2016 ?
From what you have written its not possible to see the problem. The code you provide works, assuming that you have included your module into the page and the code is correctly compiled.
I have put your code into a fiddle, https://jsfiddle.net/fccmxchx/
let ColorDirective = function () {
return {
link: function (scope, element) {
console.log('ColorDirective');
element.text('ColorDirective');
}
}
}
angular.module('app.directives', [])
.directive('color', ColorDirective);
unfortunately I cannot split your code into modules, but that is what your code is trying to do
I'm not very familiar with es6 syntax but here is the typescript's way I'm using:
class ColorDirective implements angular.IDirective {
constructor() { }
link(scope, iElement, iAttrs, ngModelCtrl) {
}
/**
* Instance creation
*/
static getInstance(): angular.IDirectiveFactory {
// deendency injection for directive
// http://stackoverflow.com/questions/30878157/inject-dependency-to-the-angularjs-directive-using-typescript
let directive: angular.IDirectiveFactory = () => new ColorDirective();
directive.$inject = [];
return directive;
}
}
angular
.module('moduleName')
.directive('color', ColorDirective.getInstance());
EDIT: after some research, I've found the es6 way to do the same thing as above:
import angular from 'angular';
class ColorDirective {
constructor() {
}
link(scope, element) {
console.log('ColorDirective');
}
static getInstance() {
var directive = new ColorDirective();
}
}
export default ColorDirective.getInstance();
I'm trying to set a controller dynamically to my directive using the name property. So far this is my code.
html
<view-edit controller-name="vm.controller" view="home/views/med.search.results.detail.resources.audios.html" edit="home/views/med.media.resources.edit.html"></view-edit>
js
export default class SearchResultsCtrl extends Pageable {
/*#ngInject*/
constructor($injector, $state, api) {
super(
{
injector: $injector,
endpoint: 'mediaMaterialsList',
selectable:{
itemKey: 'cid',
enabled:true,
params: $state.params
},
executeGet: false
}
);
this.controller = SearchResultsResourcesAudiosCtrl;
}
}
Directive
export default class ViewEditDirective {
constructor() {
this.restrict = 'E';
this.replace = true;
this.templateUrl = 'home/views/med.view.edit.html';
this.scope = {};
this.controller = "#";
this.name = "controllerName";
this.bindToController = {
'view': '#?',
'edit': '#?'
};
this.open = false;
this.controllerAs = 'ctrl';
}
}
I get undefined for vm.controller. I guess that it's rendering before the controller can assign the controller to the variable (I debbuged it, and it's setting the controller in the variable).
I'm following this answer to achieve this, but no luck so far.
How to set the dynamic controller for directives?
Thanks.
The problem is not related to ES6 (which is a sugar syntax coating over ES5), this is how Angular scope life cycle works.
This directive may show what's the deal with attribute interpolation
// <div ng-init="a = 1"><div sum="{{ a + 1 }}"></div></div>
app.directive('sum', function () {
return {
scope: {},
controller: function ($attrs) {
console.log($attrs.sum) // {{ a + 1 }}
// ...
},
link: function (scope, element, attrs) {
console.log(attrs.sum) // 2
}
};
});
And $attrs.sum may still not be 2 in link if a value was set after that (i.e. in parent directive link).
It is unsafe (and wrong per se) to assume that the value on one scope can be calculated based on the value from another scopes at some point of time. Because it may be not. That is why watchers and data binding are there.
All that controller: '#' magic value does is getting uninterpolated attribute value and using it as controller name. So no, it won't interpolate controller name from vm.controller and will use 'vm.controller' string as controller name.
An example of a directive that allows to set its controller dynamically may look like
// dynamic-controller="{{ ctrlNameVariable }}"
app.directive('dynamicController', function () {
return {
restrict: 'A',
priority: 2500,
controller: function ($scope, $element, $attrs, $interpolate, $compile) {
var ctrlName = $interpolate($attrs.dynamicController)($scope);
setController(ctrlName);
$attrs.$observe('dynamicController', setController);
function setController (ctrlName) {
if (!ctrlName || $attrs.ngController === ctrlName) {
return;
}
$attrs.$set('ngController', ctrlName);
$compile($element)($scope);
}
}
};
});
with all the side-effects that re-compilation may bring.
I have a component :
import template from './login.html';
import controller from './login.controller';
import './login.less';
let loginComponent = {
restrict: 'E',
bindings: {
redirect: '#'
},
template,
controller,
controllerAs: 'vm'
};
export default loginComponent;
Which uses a basic controller:
import angular from 'angular';
let LoginController = [
'AuthService',
'$resource',
'$state',
'config',
(AuthService, $resource, $state, config) => {
var vm = {
redirect: ''
};
var initialize = () => {
if (AuthService.authenticated()) {
loginSuccess();
}
};
var loginSuccess = () => {
console.log(vm.redirect);
if (vm.redirect) {
$state.go(vm.redirect);
}
};
initialize();
return vm;
},
];
export default LoginController;
The issue is that the redirect attribute is not available at the time of construction (when the initialize method is called). How do I access this attribute during controller construction? If there is no way, what's the best way to watching for this attribute to become available, and then calling the initialize function?
Solution:
$onInit was exactly the method I was looking for. Here's the corrected controller for future users:
import angular from 'angular';
let LoginController = [
'AuthService',
'$resource',
'$state',
'config',
(AuthService, $resource, $state, config) => {
var vm = {
redirect: ''
};
vm.$onInit = () => {
if (AuthService.authenticated()) {
loginSuccess();
}
};
var loginSuccess = () => {
console.log(vm.redirect);
if (vm.redirect) {
$state.go(vm.redirect);
}
};
return vm;
},
];
export default LoginController;
You can't access the binding at controller construction because of how Angular directive linking works.
It will be available in link function in directive, in component (which is sugar syntax for directive) it is available in magic $onInit controller method:
Called on each controller after all the controllers on an element have
been constructed and had their bindings initialized (and before the
pre & post linking functions for the directives on this element). This
is a good place to put initialization code for your controller.
The problem with listed controller is that it uses arrow function instead of regular one. The controller is an instance of controller constructor and provides this context.
There is no need to invent initialize method, it should be this.$onInit now.