Interpolate in ES6 + Angular 1.4 directive - javascript

I am currently trying the 'new' ES6 + Angular combination and got stuck on interpolating a html string in a directive that contains scope bindings.
I have tried the following option:
Current situation
The following code works but it uses a filter instead of a directive.
HTML file
<div class="thumbnail-body">
<div ng-bind-html="vm.labels.hello | interpolate:this"></div>
</div>
filter in module (still old school angular without ES6)
//TODO: .filter('interpolate', () => new InterpolateFilter())
.filter('interpolate', function ($interpolate) {
return function (text, scope) {
if (text) {
return $interpolate(text)(scope);
}
};
});
The reason why I am trying to move the interpolate logic towards a
directive so I don't have to add a filter on multiple elements.
working but ugly situation
HTML file
<interpolate value="vm.labels.hello" scope="this"></interpolate>
Directive
class InterpolateDirective {
constructor() {
this.template = '<div ng-bind-html="value |interpolate:scope"></div>';
this.restrict = 'E';
this.scope = {value: '=',scope:'='};
}
}
export default InterpolateDirective;
Module
.directive('interpolate', () => new InterpolateDirective())
Desired situation (not working yet)
HTML file
<interpolate value="vm.labels.hello"></interpolate>
Directive
class InterpolateDirective {
constructor($interpolate,$scope) {
'ngInject';this.template = '<div ng-bind-html="value"> </div>';
this.restrict = 'E';
this.scope = {value: '='};
this.$interpolate = $interpolate;
this.$scope = $scope;
}
//optional compile or link function
link() {
this.scope.value = this.$interpolate(this.scope.value)(this);
}
}
export default InterpolateDirective;
Module
.directive('interpolate', () => new InterpolateDirective())
In short: I would like to work with the desired situation

Try this:
class InterpolateDirective {
constructor($interpolate) {
this.template = '<div ng-bind-html="value"> </div>';
this.restrict = 'E';
this.scope = {
value: '='
};
this.$interpolate = $interpolate;
InterpolateDirective.prototype.link = (scope) =>{
scope.value = this.$interpolate(scope.value)(this);
}
}
public static Factory() {
var directive = ($interpolate) => {
return new InterpolateDirective($interpolate);
};
directive['$inject'] = ['$interpolate'];
return directive;
}
}
export default InterpolateDirective;
.directive('interpolate', InterpolateDirective.Factory());
Scope in directives isn't injected like in controllers by dependency injection. Directive can access scope by first parameter of link function.
Scope defined by directive's object property isn't the same. It's a part of configuration to create directive's API by scope isolation.

Related

Angular service inheritance using Object.create()

I'm trying to use inheritance in angular services, as explained here:
http://blog.mgechev.com/2013/12/18/inheritance-services-controllers-in-angularjs/, I want to use the "Inject the parent" method.
However, it doesn't seem to work, and I can't see why.
var myApp = angular.module('myApp',[]);
angular.module('myApp').controller('MyCtrl', MyCtrl);
angular.module('myApp').factory('BaseModel', BaseModel);
angular.module('myApp').factory('ThreadModel', ThreadModel);
angular.module('myApp').factory('PostModel', PostModel);
function MyCtrl($scope, ThreadModel, PostModel) {
$scope.tableNameForThreads = ThreadModel.getTableName();
$scope.tableNameForPosts = PostModel.getTableName();
}
function BaseModel() {
var tableName = "";
var service = {
init: init,
getTableName: getTableName
};
return service;
function getTableName() {
return tableName;
}
function init(theTableName) {
tableName = theTableName;
}
}
function ThreadModel(BaseModel) {
var service = Object.create(BaseModel);
service.init("threads");
return service;
}
function PostModel(BaseModel) {
var service = Object.create(BaseModel);
service.init("posts");
return service;
}
The result is that ThreadModel.getTableName() returns "posts" in stead of "threads".
I tried both Object.create(...) and angular.copy(BaseModel, this), but both don't seem to make a deep copy.
JSFIDDLE: http://jsfiddle.net/dirkpostma/Lvc0u55v/3989/
What am I doing wrong here?
The problem is that with this set up using Object.create you produce services with the tableName variable stored in the same common closure (BaseModel function). To put it simply, init method modifies the same local tableName variable.
You could fix it like this:
function BaseModel() {
var service = {
init: init,
getTableName: getTableName
};
return service;
function getTableName() {
return this._tableName;
}
function init(theTableName) {
this._tableName = theTableName;
}
}
Note, that getTableName and init methods now work with instance property this._tableName which is not shared between TableModel and PostModel instances.
Demo: http://jsfiddle.net/Lvc0u55v/3991/
#dfsq has already well explained and given a simple solution. I put here what I am thinking about this issue.
In your code Object.create(BaseModel) creates a new object whose prototype is a returned value of BaseModel function. In those children models init method modifies tableName within the local scope of BaseModel function. If you replace tableName with this.tableName, that will work as you expected: both init and getTableName methods will actually modify/call tableName property of service variable within ThreadModel or PostModel functions. But it looks complicated.
In your case I would like suggest the following service inheritance solution, which would be clearer. There is an other post that can be interesting.
var myApp = angular.module('myApp', []);
angular.module('myApp').controller('MyCtrl', MyCtrl);
angular.module('myApp').service('BaseModel', BaseModel);
angular.module('myApp').service('ThreadModel', ['BaseModel', ThreadModel]);
angular.module('myApp').service('PostModel', ['BaseModel', PostModel]);
function MyCtrl($scope, ThreadModel, PostModel) {
$scope.tableNameForThreads = ThreadModel.getTableName();
$scope.tableNameForPosts = PostModel.getTableName();
}
function BaseModel() {
this.tableName = "";
this.getTableName = function() {
return this.tableName;
}
this.init = function(theTableName) {
this.tableName = theTableName;
}
}
function ThreadModel(BaseModel) {
angular.extend(ThreadModel.prototype, BaseModel);
this.tableName = "threads";
}
function PostModel(BaseModel) {
angular.extend(PostModel.prototype, BaseModel);
this.tableName = "posts";
}
JSFiddle: http://jsfiddle.net/Lvc0u55v/3993/

Communicating Events from Parent to Child in AngularJS Components

in the new project I'm working on I've started using the components instead of directives.
however, I've encountered an issue where I cannot find a concrete standard way to do it.
It's easy to notify an event from child to parent, you can find it on my plunkr below, but what's the correct way to notify a event from parent to child?
Angular2 seems to solve this issue by using something like this: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-child-local-var
But I don't tink there's a possibilty to define a "pointer" to the child component like the example did with #timer
In order to mantain a possible easy conversion to Angular2 I want to avoid:
event emitting (emit and broadcast from the scopes)
using the require from the child (and then add a callback to the parent..UGLY)
using a one-way binding, injecting the scope in the child and then "watch" this property.. MORE UGLY
Example code:
var app = angular.module('plunker', []);
app.controller('RootController', function() {
});
app.component('parentComponent', {
template: `
<h3>Parent component</h3>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Child</a>
<span data-ng-bind="$ctrl.childMessage"></span>
<child-component on-change="$ctrl.notifiedFromChild(count)"></child-component>
`,
controller: function() {
var ctrl = this;
ctrl.notifiedFromChild = function(count){
ctrl.childMessage = "From child " + count;
}
ctrl.click = function(){
}
},
bindings: {
}
});
app.component('childComponent', {
template: `
<h4>Child component</h4>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Parent</a>
`,
controller: function() {
var ctrl = this;
ctrl.counter = 0;
ctrl.click = function(){
ctrl.onChange({ count: ++ctrl.counter });
}
},
bindings: {
onChange: '&'
}
});
You can find an example here:
http://plnkr.co/edit/SCK8XlYoYCRceCP7q2Rn?p=preview
This is a possible solution I created
http://plnkr.co/edit/OfANmt4zLyPG2SZyVNLr?p=preview
where the child requires the parent, and then child sets a parent reference to the child... now parent can use the child... ugly but it's like angular2 example above
Communicating Events from Parent to Child in AngularJS Components
Publish Directive $API Using Expression Binding
To allow parent components to communicate events to a child component, have the child publish an API:
<grid-component grid-on-init="$ctrl.gridApi=$API; $ctrl.someFn($API)">
</grid-component>
JS
app.component('gridComponent', {
//Create API binding
bindings: {gridOnInit: "&"},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}</p>
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
//Publish save function
ctrl.api.save = save;
//Invoke Expression with $API as local
ctrl.gridOnInit({$API: ctrl.api});
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
The above example invokes the Angular Expression defined by the grid-on-init attribute with its API exposed as $API. The advantage to this approach is that the parent can react to child initialization by passing a function to the child component with the Angular Expression.
From the Docs:
The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the directive's element. These local properties are useful for aliasing values for templates. The keys in the object hash map to the name of the property on the isolate scope; the values define how the property is bound to the parent scope, via matching attributes on the directive's element:
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given <my-component my-attr="count = count + value"> and the isolate scope definition scope: { localFn:'&myAttr' }, the isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression to the parent scope. This can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment($amount) then we can specify the amount value by calling the localFn as localFn({$amount: 22}).
-- AngularJS Comprehensive Directive API -- scope
As a convention, I recommend prefixing local variables with $ to distinguish them from parent variables.
Alternately use Bi-Directional Binding
NOTE: To ease the transition to Angular 2+, avoid the use of bi-directional = binding. Instead use one-way < binding and expression & binding. For more information, see AngularJS Developer Guide - Understanding Components.
To allow parent components to communicate events to a child component, have the child publish an API:
<grid-component api="$ctrl.gridApi"></grid-component>
In the above example, the grid-component uses bindings to publish its API onto the parent scope using the api attribute.
app.component('gridComponent', {
//Create API binding
bindings: {api: "="},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}</p>
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
//Publish save function
ctrl.api.save = save;
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
Then the parent component can invoke the child save function using the published API:
ctrl.click = function(){
console.log("Search clicked");
ctrl.gridApi.save();
}
The DEMO on PLNKR.
Here is an easy way: http://morrisdev.com/2017/03/triggering-events-in-a-child-component-in-angular/
basically, you add a bound variable called "command" (or whatever you want) and use the $onChanges to pay attention to changes of that variable and trigger whatever event it says to trigger manually.
I personally like to put all my variables into an object called "settings" and send that to all my components. However, a change to a value within an object does NOT trigger the $onChanges event, so you NEED to tell it to trigger the event with a flat variable.
I'd say it is not the "proper" way to do it, but it sure is a lot easier to program, a lot easier to understand, and a lot easier to convert to A2 later on down the road.
I faced with same question. What do you think about this approach: to use inheritance via require instead of Bi-Directional Binding?
http://plnkr.co/edit/fD1qho3eoLoEnlvMzzbw?p=preview
var app = angular.module('plunker', []);
app.controller('RootController', function() {
});
app.component('filterComponent', {
template: `
<h3>Filter component</h3>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
<span data-ng-bind="$ctrl.childMessage"></span>
<grid-component api="$ctrl.gridApi"></grid-component>
`,
controller: function() {
var ctrl = this;
ctrl.click = function(){
console.log("Search clicked");
ctrl.gridApi.save();
};
}
});
app.component('gridComponent', {
require: {parent:'^^filterComponent'},
bindings: {api: "<"},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
ctrl.api.save = save;
ctrl.parent.gridApi = ctrl.api;
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
Or we can define setter method for parent to make it more explicit.
http://plnkr.co/edit/jmETwGt32BIn3Tl0yDzY?p=preview
var app = angular.module('plunker', []);
app.controller('RootController', function() {
});
app.component('filterComponent', {
template: `
<h3>Filter component</h3>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
<span data-ng-bind="$ctrl.childMessage"></span>
<grid-component pass-api="$ctrl.setGridApi(api)"></grid-component>
`,
controller: function() {
var ctrl = this;
var gridApi = {};
ctrl.setGridApi = function(api){
gridApi = api;
};
ctrl.click = function(){
console.log("Search clicked");
gridApi.save();
};
}
});
app.component('gridComponent', {
bindings: {
passApi:'&'
},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
ctrl.api.save = save;
ctrl.passApi({api: ctrl.api});
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
SIMPLE: You just need one property 1 way bound because 2 way binding only calls onChanges at creation.
Set a new boolean property on the parent controller.
vm.changeNow = false;
//update this to vm.changeNow = !vm.changeNow when you want to tell the component to
//call a method.
Open Child Component, in the bindings section,
bindings: {
a2waybind: '=',
changenow: '<'
}
You now need an $onChanges event on the child.
$onChanges() {
// do that sweet stuff you wanted to listen from the parent for.
}
Now when calling the template:
childComponent a2waybind="$ctrl.mycoolvalue" changenow="$ctrl.changeNow" /childComponent"
A second way to do this is in your child component:
var vm = this;
var myprop;
Object.defineProperty(vm, 'mytwowayprop', {
get() {
return myprop;
},
set(value) {
myprop = value;
vm.onchangeseventbecausemypropchanged();
}
});
vm.onchangeseventbecausemypropchanged = function () {//woot}
This will allow you to have a defined onchanges event when your two way binding property changes both internally and externally.

Angular 1.x directive controller promise issue in ES6

I have a directive in which I am calling promise function inside constructor.Below is the snippet of it
class exampleController {
constructor(exampleService) {
var vm = this;
vm.itemLoaded = false;
exampleService.getSomeData().then(function(response){
vm.data = response;
vm.itemLoaded = true;
}
}
}
angular.module('exampleApp').controller('exampleController',exampleController)
.directive('exampleDirective', exampleDirective)
.service('exampleService', exampleService);
class exampleDirective {
constructor(){
this.controller = 'exampleController';
this.controllerAs = 'vm';
}
}
function exampleService() {
getData: function () {
return $http.get('url');
}
}
In template if I call
<example></example>
<example></example>
<example></example>
The updation of itemLoaded scope goes to last directive though each directive have it's respective async function got called and I also add a break point in promise function and for second/third directive I am getting first directive value.It's behaving like a singleton directive sharing same address for all 3 directive.
Anyone face this type of issue?I am using babel to convert it into ES5

A proper way to compile angular template into html, convert result into string

Abstract
Hi, I'm using angular to render documents, I have the view-model that contains the data which should go into document, and I have angular template that represents the document. The template is valid angular-html markup that is later rendered using angular's $compile, here is directive I use to render documents for presentation purposes:
angular.module('app').directive('render', function ($compile, server) {
return {
restrict: 'E',
link: function ($scope, $element, $attributes) {
var scope;
server.resolve().then(function (repo) {
var _template, _constants, _variables, _substitutes;
var render = function () {
if (_template) {
$scope.repo = repo;
var references = repo.references;
if (_constants) {
for (var constantName in _constants) {
$scope[constantName] = references[_constants[constantName]];
}
}
if (_variables) {
for (var variableName in _variables) {
var substitute = _substitutes[variableName];
var variableValue = _variables[variableName];
var reference = repo.references[substitute];
if (reference) {
if (reference.table === variableValue) {
$scope[variableName] = reference;
} else {
throw new Error('Invalid reference type');
}
}
}
}
if (scope) scope.$destroy();
scope = $scope.$new();
var element = angular.element('<div class="print"></div>');
element.html(_template);
$element.empty();
$element.append(element);
$compile(element)(scope);
}
};
$scope.$watch($attributes.template, function (template) {
_template = template;
render();
});
$scope.$watch($attributes.constants, function (constants) {
_constants = constants;
render();
});
$scope.$watch($attributes.variables, function (variables) {
_variables = variables;
render();
});
$scope.$watchCollection($attributes.substitutes, function (substitutes) {
_substitutes = substitutes;
render();
});
});
}
};
});
Question
I need to make a hard-copy of the document, in other words I need to substitute the view-model values into document template, convert result into string and put it into variable. I can't use directive for that, angular's $compile is really heavy function to call, it creates watches under the hood, I don't need whole shebang, I just need to substitute values. What would be the best way to do that?
Thank you in advance
With your tip for $interpolate I could finalize my demo for your question.
It's with-out a service for storing the interpolated template into database to keep the demo focused on the issue.
So as I've understood, the difference between $compile and $interpolate is the following:
$compile: Creates DOM elements with angular binding to scope. That's what you would normally use to render your DOM to have two-way binding etc. working. In most cases you don't call it manually because if you add template or templateUrl to the direcive definition object it will run $compile automatically.
$interpolate: It is pretty similar to compile with the only difference that it will return the DOM elements wiht-out angular bindings.
You can see the difference if you have a look at the rendered html markup. Compiled templates have ng-binding class in the markup and the other is just static html with-out that class.
So as you mentioned $interpolate is the way to go to get the compiled string that you can easily store in the database with a $http service.
Please have a look at the demo below or at this jsfiddle.
angular.module('myApp', [])
.directive('render', Render);
var templateStore = [];
function Render($compile, $interpolate) {
return {
restrict: 'E',
controllerAs: 'render',
controller: function () {
this.hello = 'hello from controller';
//console.log($templateCache.get('testTemplate'));
},
compile: function (tElem, tAttrs) {
return function (scope, element, attrs, controllers) {
//controllers.hello = 'hello from controller';
//console.log(controllers);
var template = angular.element(
document.getElementById('template.html')).html(),
compiled = $compile(template)(scope),
obj = {
render: {
hello: "hello from 'fake' controller"
},
hello: 'hello from other object.'
};
scope.hello = "Hello from scope";
element.replaceWith(compiled);
var result = $interpolate(template)(scope);
templateStore.push(result);
var result = $interpolate(template)(obj);
templateStore.push(result);
//console.log(result);
//console.log(templateStore[0]);
$('#test').append( // append just to test the saved template
templateStore[0]);
$('#test2').append( // append just to test the saved template
templateStore[1]);
};
}
}
}
Render.$inject = ['$compile', '$interpolate'];
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<script type="text/ng-template" id="template.html">
<div>
<p>controllerAs: {{render.hello}}</p>
scope: {{hello}}
</div>
</script>
<h2>Compiled template with binding</h2>
<render></render>
<h2>The following is a string copy from above template with-out binding</h2>
<div id="test"></div>
<h2>You can also $interpolate a object with the following result (also no binding)</h2>
<div id="test2"></div>
</div>

Angular binding to service value not updating

I cannot get a binded service value to update when it is changed. I have tried numerous methods of doing so but none of them have worked, what am I doing wrong? From everything I have seen, this seems like it should work...
HTML:
<div class="drawer" ng-controller="DrawerController">
{{activeCountry}}
</div>
Controller:
angular.module('worldboxApp')
.controller('DrawerController', ['$scope', 'mapService', function($scope, mapService) {
$scope.$watch(function() { return mapService.activeCountry }, function(newValue, oldValue) {
$scope.activeCountry = mapService.activeCountry;
});
}]);
Service:
angular.module('worldboxApp').
service('mapService', function(dbService, mapboxService, userService) {
this.init = function() {
this.activeCountry = {};
}
this.countryClick = function(e) {
this.activeCountry = e.layer.feature;
};
this.init();
});
I put a break point to make sure the mapService.activeCountry variable is being changed, but all that ever shows in the html is {}.
If you work with objects and their properties on your scope, rather than directly with strings/numbers/booleans, you're more likely to maintain references to the correct scope.
I believe the guideline is that you generally want to have a '.' (dot) in your bindings (esp for ngModel) - that is, {{data.something}} is generally better than just {{something}}. If you update a property on an object, the reference to the parent object is maintained and the updated property can be seen by Angular.
This generally doesn't matter for props you're setting and modifying only in the controller, but for values returned from a service (and that may be shared by multiple consumers of the service), I find it helps to work with an object.
See (these focus on relevance to ngModel binding):
https://github.com/angular/angular.js/wiki/Understanding-Scopes
If you are not using a .(dot) in your AngularJS models you are doing it wrong?
angular.module('worldboxApp', []);
/* Controller */
angular.module('worldboxApp')
.controller('DrawerController', ['$scope', 'mapService',
function($scope, mapService) {
//map to an object (by ref) rather than just a string (by val), otherwise it's easy to lose reference
$scope.data = mapService.data;
$scope.setCountry = setCountry; //see below
function setCountry(country) {
// could have just set $scope.setCountry = mapService.setCountry;
// however we can wrap it here if we want to do something less generic
// like getting data out of an event object, before passing it on to
// the service.
mapService.setCountry(country);
}
}
]);
/* Service */
angular.module('worldboxApp')
.service('mapService', ['$log',
function($log) {
var self = this; //so that the functions can reference .data; 'this' within the functions would not reach the correct scope
self.data = {
activeCountry: null
}; //we use an object since it can be returned by reference, and changing activeCountry's value will not break the link between it here and the controller using it
_init();
function _init() {
self.data.activeCountry = '';
$log.log('Init was called!');
}
this.setCountry = function _setCountry(country) {
$log.log('setCountry was called: ' + country);
self.data.activeCountry = country;
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="worldboxApp">
<div ng-controller="DrawerController">
<button ng-click="setCountry('USA')">USA</button>
<br />
<button ng-click="setCountry('AUS')">AUS</button>
<br />Active Country: {{data.activeCountry}}
</div>
</div>
In some case $watch is not working with factory object. Than you may use events for updates.
app.factory('userService',['$rootScope',function($rootScope){
var user = {};
return {
getFirstname : function () {
return user.firstname;
},
setFirstname : function (firstname) {
user.firstname = firstname;
$rootScope.$broadcast("updates");
}
}
}]);
app.controller('MainCtrl',['userService','$scope','$rootScope', function(userService,$scope,$rootScope) {
userService.setFirstname("bharat");
$scope.name = userService.getFirstname();
$rootScope.$on("updates",function(){
$scope.name = userService.getFirstname();
});
}]);
app.controller('one',['userService','$scope', function(userService,$scope) {
$scope.updateName=function(){
userService.setFirstname($scope.firstname);
}
}]);
Here is the plunker
Note:- In Some case if broadcast event is not fired instantly you may use $timeout. I have added this in plunker and time depends on your needs. this will work for both factories and services.

Categories