angular 1.5 (components): how to use directives with es6? - javascript

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();

Related

Angular project architecture. Controllers. Directives. Style guides

We currently have a project that we are going to scale up soon. I am in the process of refactoring the project to make sure it's a bit more maintainable in the long term, and isn't a complete headache for anyone new to the project.
Please ignore any syntactical errors below, I had to change a bit of the code to try and best illustrate the architectural issue.
We have tried to follow the john papa style guide as closely as possible.
currently our setup is as follows:
PageController
(function() {
'use strict';
angular
.module('front')
.controller('PageController', PageController)
/** #ngInject */
function PageController($rootScope, $scope, $timeout, toastr, dataService, allowed, Pusher) {
var vm = this;
vm.allowed = allowed;
vm.widget = {
};
activate();
function activate(){
getOurWidgetData();
} // end activate()
/* get widget data */
function getOurWidgetData() {
dataService.getData(
$rootScope.wf.api + '/endpoint',
function(response) {
vm.widget.data = response.data.data;
vm.widgetlastModified = new Date(response.data.timestamp * 1000);
},
function(error) {
vm.widget.error = error;
}
);
}
vm.getOurWidgetData = getOurWidgetData;
}
})();
In our PageController view:
<div class="col-xs-12 col-md-4">
<div class="panel">
<div class="panel__header">
<h2><span>Widget</span>
<last-updated last-updated="main.widget.lastModified" lu-error="main.widget.error" label="As at"></last-updated>
</h2>
</div>
<div class="panel__body">
<div our-widget="" our-data="main.widget.data"></div>
</div>
</div>
</div>
and then in OurWidget:
(function() {
'use strict';
angular
.module('front')
.directive('ourWidget', ourWidget);
/** #ngInject */
function ourWidget() {
var directive = {
restrict: 'A',
templateUrl: 'app/components/ourWidget/ourWidget.html',
scope: {
aumData: '='
},
controller: ourWidgetController,
controllerAs: 'ow',
bindToController: true
};
return directive;
/** #ngInject */
function ourWidgetController() {
var vm = this;
activate();
function activate() {
}
}
}
})();
Now, this directive / component is quite empty. My proposal at this point is to move the dataService call from the PageController and into the component.
So, the ourWidget directive looks like this:
(function() {
'use strict';
angular
.module('front')
.directive('ourWidget', ourWidget);
function ourWidget() {
var directive = {
restrict: 'E',
templateUrl: 'app/components/ourWidget/ourwidget.html',
scope: {
ourModel: '='
},
controller: ourWidgetController,
controllerAs: 'ow',
bindToController: true
};
return directive;
/** #ngInject */
function ourWidgetController($rootScope, dataService, constants, endpoints, $log) {
var vm = this;
vm.OurWidget = {};
activate();
function activate() {
getOurWidgetData();
} // end activate()
function getOurWidgetData() {
dataService.getData(
constants.apiv2 + endpoints.OurWidget,
function(response) {
$log.log(response);
vm.OurWidget.data = response.data.data;
vm.OurWidget.lastModified = new Date(response.data.timestamp * 1000);
},
function(error) {
vm.OurWidget.error = error;
}
);
}
vm.getOurWidgetData = getOurWidgetData;
}
}
})();
I have prototyped this and it works flawlessly, meaning I have a html tag like:
<our-widget></our-widget>
That I can put anywhere in the application I need it. This directive is now responsible for its data, and means we aren't copy pasting dataService calls across any page controller that this component would be needed.
My question is, is this a better way of doing it, having the dataService call inside the component / directive instead of the PageController? I'd say it is, as our previous method goes directly against the DRY principle. I'm getting a bit of resistance though as some suggest it is going against the Angular style guide, though I haven't seen any examples or suggestion not to do it in this way, or in fact anything alluding to this kind of project architecture besides having a dataService handle all our http request, which we already have.
And yes, I've noticed we're using $rootScope incorrectly, I am attempting to fix these things as I'm going along :)

Call link function directive Angular 1 ES6

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/

Template attributes not resolved for upgraded Angular 1 component directive

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?

Angular component binding at construction

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.

importing angular services with ES6 and webpack

I'm trying to import $timeout with ES6 and webpack and I keep getting that $timeout is undefined.
Can anyone help?
If there's a way that doesn't involve using $inject I'd prefer it because I'm gradually trying to get rid of angularjs in my code.
randomTVNames.service.js:
import angular from 'angular';
class RandomTVNames {
constructor($timeout) {
this.tv = ['Shield', 'Walking Dead', 'Castle', 'Leftovers'];
this.timeout = $timeout;
console.log(this.timeout);
}
getName() {
const totalNames = this.tv.length;
const rand = Math.floor(Math.random() * totalNames);
return this.tv[rand];
}
getTimeout(){
this.timeout(function () {
alert("this is timeout")}, 3000);
}
}
RandomTVNames.$inject = ['$timeout'];
//todo - try to inject angular argument (such as $timeout) with $inject
var randomTVNames = new RandomTVNames();
export default randomTVNames;
home.controller.js:
import randomTVNames from '../../services/randomTVNames.service';
import mainModule from '../../mainModule';
class HomeController {
constructor() {
this.tv = randomTVNames;
this.name = 'World';
}
randomTVName($timeout) {
this.name = this.tv.getName();
}
getCtrlTimeout(){
this.tv.getTimeout();
}
}
mainModule.controller('HomeController', HomeController);
ES6 modules are not compatible with the module system from Angular 1.x. This means that exporting and importing services and controllers won't work, you need to register and inject them using Angular's module system.
randomTVNames.service.js:
import mainModule from '../../mainModule';
class RandomTVNames {
// ... snip ...
}
RandomTVNames.$inject = [ /* ... snip ... */ ];
mainModule.service('randomTVNames', RandomTVNames);
home.controller.js:
import mainModule from '../../mainModule';
class HomeController {
constructor($scope, randomTVNames) {
this.$scope = $scope;
this.tv = randomTVNames;
}
}
HomeController.$inject = ['$scope', 'randomTVNames'];
mainModule.controller('HomeController', HomeController);
Then in your main webpack file, make sure to import both of them so they get bundled:
import 'services/randomTVNames.service';
import 'controllers/controller.service';
There is no way to get rid of $inject, unless you are not minifying you code (but i hope you are).
Angular is injecting variables using their names, so when it sees $scope, it know to look for it and inject it, but when minifying your code the variable names are changed ($scope becomes c etc.) and angular does not know what object you want to inject.
That is what $inject is for since strings are not minified.
please take a look at this loader. it helps with what you're trying to do. but mind adding 'ngInject' anywhere in your js file, that injects anything

Categories

Resources