AngularJS - Cannot access ng-click in component when $compile is used - angularjs

I have a normal JS file that contains some utility functions. One of these utility functions are to return the AngularJS component <tooltip>. I know that this is not very good coding practice, but this is what I have to work with. I have one problem:
onClickDescription() does not trigger when I click on the <p>. It however gets triggered by $onInit().
utility.js
function getTooltipHtml($compile, $scope) {
const scope = $scope.$new();
scope.description = getDescription();
const html = '<tooltip description="{description}"></tooltip>';
const element = $compile(angular.element(html))(scope);
scope.$apply();
return element.html();
}
tooltip.js
class Tooltip {
$onInit() {
this.onClickDescription();
}
onClickDescription() {
console.log('onClickDescription()');
}
}
export default {
bindings: {
description: '<'
}
}
tooltip.html
<div class="tooltip">
<p ng-click="$ctrl.onClickDescription()">{{ $ctrl.description }}</p>
</tooltip>

Related

Find element not found in AngularJS

I have component in AngularJS and a template which looks something like the following
Template
<div id="{{ $ctrl.idName }}"></div>
And the component controller looks something like this
Component controller
export class SomeCtrl {
idName: string = "idName";
constructor(public $element) {
}
$postLink(): void {
const divElement = this.$element.find(`#${ this.idName }`);
console.log(divElement); //divElement is undefined
}
}
For some reason divElement becomes undefined, however if I write the name in the template as following
<div id="idName"></div>
Then the element is found. My assumption that it was due to template hasn't finished compiling. But it seems not because of that either since the $postLink() method is fired when compiling is finished according to the following article ThoughtRam - Exploring Angular 1.5: Lifecycle Hooks
Enclose the find in a $timeout:
export class SomeCtrl {
idName: string = "idName";
constructor(public $element, public $timeout) {
}
$postLink(): void {
this.$timeout( _ => {
const divElement = this.$element.find(`#${ this.idName }`);
console.log(divElement);
});
}
}
This will allow the AngularJS framework and the browser time to render the interpolation.

How to update child components from the updated list of parents

I'm new to Angular and currently using version 1.6.
I'm implementing the component style of Angular. I just want to ask what's the best way to communicate from parent to child components? I know there is an existing question but I have a specific scenario (I'm not sure if it's unique or not).
Here is the scenario:
Modal -> create new todo -> Parent ( update the object ) -> personal todo ( update the list )
I have a modal for creating todo.
Then after creating new todo pass the value on the parent to update the object of todo.
And when I updated the parent list of todo pass to the personal todo components to update the list on the view.
angular.module('tab')
.controller('TabController', TabController);
function TabController() {
let vm = this;
let updatedTodoObject = {};
vm.$onInit = function () {
vm.personalTodo = vm.todo.own_todo;
vm.externalTodo = vm.todo.external_todo;
}
vm.$onChanges = function (changes) {
console.log('I\'m triggered');
}
vm.updateTodoList = updateTodoList;
function updateTodoList( result ) {
updatedTodoObject = angular.copy(vm.todo);
updatedProjectObject.user_todos.push(result)
if( vm.todo !== updatedTodoObject) {
vm.todo = updatedTodoObject;
} else {
console.log("Still in reference");
}
}
vm.getUpdatedTodotList = function( ) {
return vm.todo;
}
}
angular.module('...')
.component('...', {
bindings: {
onResultTodoUpdated: '&'
},
controllerAs: 'todo',
controller: ['TodoService', '$log', '$state', function(TodoService, $log, $state) {
let vm = this;
let todo = {};
vm.newTodoModal = function() {
TodoService.newTodoModal()
.then(function (TodoName) {
TodoService.createTodo(TodoName)
.then(function(response) {
if( response.status === 201 ) {
todo = {
...
...
}
vm.onResultTodoUpdated( { result: todo } );
}
})
.catch(function(error) {
console.log(error);
});
angular.module('...')
.component('...', {
bindings: {
todos: "<"
},
controllerAs: 'personal',
controller: function(){
let vm = this;
vm.isShowTodoArchived = false;
vm.$onInit = function () {
getWatchedTodo();
}
function getWatchedTodo () {
vm.todos = vm.todos;
vm.todosSize = vm.todos.length;
}
My question again is how I can pass the updated data after I create to the child component which is in charge of displaying the todo list?
UPDATED
<div class="tab-pane active" id="todosTab">
<nv-new-todo on-result-todo-updated="todo.updateTodoList(result)"></nv-new-project>
<div class="my-todos">
<nv-personal-todo todos="todo.personalTodo" ></nv-personal-todo>
<nv-external-todo todos="todo.externalTodo"></nv-external-todo>
</div>
</div>
How to update child components with changes from parents
Use one-way bindings <
< or <attr - set up a one-way (one-directional) binding between a local scope property and an expression passed via the attribute attr. The expression is evaluated 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. You can also make the binding optional by adding ?: <? or <?attr.
For example, given <my-component my-attr="parentModel"> and directive definition of scope: { localModel:'<myAttr' }, then the isolated scope property localModel will reflect the value of parentModel on the parent scope. Any changes to parentModel will be reflected in localModel, but changes in localModel will not reflect in parentModel.
— AngularJS Comprehensive Directive API Reference - scope
And the $onChanges life-cycle hook:
$onChanges(changesObj) - Called whenever one-way bindings are updated. The changesObj is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form { currentValue, previousValue, isFirstChange() }. Use this hook to trigger updates within a component.
— AngularJS Developer Guide - Components
With object content — Use the $doCheck Life-cycle Hook
When binding an object or array reference, the $onChanges hook only executes when the value of the reference changes. To check for changes to the contents of the object or array, use the $doCheck life-cycle hook:
app.component('nvPersonalTodo', {
bindings: {
todos: "<"
},
controller: function(){
var vm = this;
this.$doCheck = function () {
var oldTodos;
if (!angular.equals(oldTodos, vm.todos)) {
oldTodos = angular.copy(vm.todos);
console.log("new content");
//more code here
};
}
})
From the Docs:
The controller can provide the following methods that act as life-cycle hooks:
$doCheck() - Called on each turn of the digest cycle. Provides an opportunity to detect and act on changes. Any actions that you wish to take in response to the changes that you detect must be invoked from this hook; implementing this has no effect on when $onChanges is called. For example, this hook could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not be detected by Angular's change detector and thus not trigger $onChanges. This hook is invoked with no arguments; if detecting changes, you must store the previous value(s) for comparison to the current values.
— AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
For more information,
AngularJS angular.equals API Reference
AngularJs 1.5 - Component does not support Watchers, what is the work around?
Simple DEMO
angular.module("app",[])
.component("parentComponent", {
template: `
<fieldset>
Inside parent component<br>
parentData={{$ctrl.parentData}}
<child-component in-data="$ctrl.parentData"></child-component>
</fieldset>
`,
controller: function () {
this.$onInit = () => {
this.parentData = 'test'
};
},
})
.component("childComponent",{
bindings: {
inData: '<',
},
template: `
<fieldset>Inside child component<br>
inData={{$ctrl.inData}}
</fieldset>
`,
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<parent-component>
</parent-component>
<body>
For more information, see
AngularJS Developer Guide - Component-based application architecture
AngularJS Comprehensive API Reference - scope

angularJS ES6 Directive

I am trying to develop an application in angular es6 . I have a problem with directve.
Here is my code
export default class RoleDirective {
constructor() {
this.template="";
this.restrict = 'A';
this.scope = {
role :"#rolePermission"
};
this.controller = RoleDirectiveController;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
// Directive compile function
compile(element,attrs,ctrl) {
console.log("df",this)
}
// Directive link function
link(scope,element,attrs,ctrl) {
console.log("dsf",ctrl.role)
}
}
// Directive's controller
class RoleDirectiveController {
constructor () {
console.log(this.role)
//console.log("role", commonService.userModule().getUserPermission("change_corsmodel"));
//$($element[0]).css('visibility', 'hidden');
}
}
export default angular
.module('common.directive', [])
.directive('rolePermission',[() => new RoleDirective()]);
The problem is i couldn't get the role value inside constructor.
here is my html implementation
<a ui-sref="event" class="button text-uppercase button-md" role-permission="dfsd" detail="sdfdsfsfdssd">Create event</a>
If i console this it will get the controller object. But it will not get any result while use this.role.
Ok, so I managed to find out how this works.
Basically, the scope values cannot be initialized on the controller's constructor (because this is the first thing executed on a new object) and there is also binding to be considered.
There is a hook that you can implement in your controller that can help you with your use case: $onInit:
class RoleDirectiveController {
constructor () {
// data not available yet on 'this' - they couldn't be
}
$onInit() {
console.log(this.role)
}
}
This should work. Note that this is angular1.5+ way of doing things when not relying on $scope to hold the model anymore. Because if you use the scope, you could have it in the controller's constructor (injected).

Typescript + Angular 1.x, directive w/ require:'^someCtrl' failing with 'unknown provider'

Using Angular with Typescript. I have a Tabs controller based on Angular Bootstrap UI tabs that works in pure Angular, but when I move it over to our Typescript style, I can't get one directive (OneTab) to find the controller for the (parent) directive (TabSet).
ERROR: Unknown provider: tabProvider <- tab
I've tried a bunch of different ways of doing this but none of them make it work properly like the non-Typescript version does.
Ex: Angular-only Plunker
We're using a modular model, which is tied together with a config file each for directives, controllers, etc. This structure is working fine except for this tab directive experiment.
module app.directives {
'use strict';
angular.module('app')
.directive('tabSet', ()=> new TabSet())
.directive('oneTab', ()=> new OneTab())
;
}
module app.directives {
interface ITabScope extends ng.IScope {
active:boolean;
}
export class OneTab implements ng.IDirective {
priority = 0;
restrict = 'E';
transclude = true;
require = '^tabsetCtrl';
scope = {
heading: '#'
};
template = '<div role="tabpanel" ng-transclude ng-show="active"></div>';
link = function (scope:ITabScope, element:ng.IAugmentedJQuery, attr:ng.IAttributes, tabsetCtrl:any) {
scope.active = false;
tabsetCtrl.addTab(scope);
}
}
}
module app.directives {
import TabSetController = app.controllers.TabSetController;
export class TabSet implements ng.IDirective{
priority = 0;
restrict = 'E';
transclude = true;
scope = {};
templateUrl = 'layout/tabset.html';
controller = TabSetController;
controllerAs = 'tabsetCtrl';
bindToController = true;
}
}
module app.controllers {
'use strict';
export class TabSetController {
tabs:Array<any>;
tab:any;
selectedTab:any;
constructor(tab:any, selectedTab:any) {
this.tabs = [];
this.addTab(tab);
this.select(selectedTab);
console.log("in constructor");
}
addTab(tab?:any) {
if(tab){
this.tabs.push(tab);
if (this.tabs.length === 1) {
tab.active = true
}
}
};
select(selectedTab?:any) {
if (selectedTab){
angular.forEach(this.tabs, function (tab:any) {
if (tab.active && tab !== selectedTab) {
tab.active = false;
}
});
selectedTab.active = true;
}
}
}
}
The issue isn't with your directives, as far as I can tell, it's with your controller class. Angular assumes that any parameters in the constructor method of a controller are services that you want to inject - seeing as you're trying to pass in parameters called tab and selectedTab, it's searching for services to inject with those names. There's no service named tab, so you get an unknown provider error.

calling a function when AngularUI Bootstrap modal has been dismissed and animation has finished executing

I'm using the Angular UI bootstrap modal and I ran into a bit of a problem.
I want to call a function when the bootstrap modal dismiss animation is finished. The code block below will call the cancel() function as soon as the modal starts to be dismissed - and NOT when the modal dismiss animation has finished.
Angular UI does not use events, so there is no 'hidden.bs.modal' event being fired (at least, not to my knowledge).
var instance = $modal.open({...});
instance.result.then(function(data) {
return success(data);
}, function() {
return cancel();
})
The cancel() block immediately runs when the modal starts to close. I need code to execute when the closing animation for the Bootstrap modal finishes.
How can I achieve this with angular UI?
Component for reference:
https://angular-ui.github.io/bootstrap/#/modal
Thanks!
A little late but hope it still helps! You can hijack the uib-modal-window directive and check when its scope gets destroyed (it is an isolated scope directive). The scope is destroyed when the modal is finally removed from the document. I would also use a service to encapsulate the functionality:
Service
app.service('Modals', function ($uibModal, $q) {
var service = this,
// Unique class prefix
WINDOW_CLASS_PREFIX = 'modal-window-interceptor-',
// Map to save created modal instances (key is unique class)
openedWindows = {};
this.open = function (options) {
// create unique class
var windowClass = _.uniqueId(WINDOW_CLASS_PREFIX);
// check if we already have a defined class
if (options.windowClass) {
options.windowClass += ' ' + windowClass;
} else {
options.windowClass = windowClass;
}
// create new modal instance
var instance = $uibModal.open(options);
// attach a new promise which will be resolved when the modal is removed
var removedDeferred = $q.defer();
instance.removed = removedDeferred.promise;
// remember instance in internal map
openedWindows[windowClass] = {
instance: instance,
removedDeferred: removedDeferred
};
return instance;
};
this.afterRemove = function (modalElement) {
// get the unique window class assigned to the modal
var windowClass = _.find(_.keys(openedWindows), function (windowClass) {
return modalElement.hasClass(windowClass);
});
// check if we have found a valid class
if (!windowClass || !openedWindows[windowClass]) {
return;
}
// get the deferred object, resolve and clean up
var removedDeferred = openedWindows[windowClass].removedDeferred;
removedDeferred.resolve();
delete openedWindows[windowClass];
};
return this;
});
Directive
app.directive('uibModalWindow', function (Modals) {
return {
link: function (scope, element) {
scope.$on('$destroy', function () {
Modals.afterRemove(element);
});
}
}
});
And use it in your controller as follows:
app.controller('MainCtrl', function ($scope, Modals) {
$scope.openModal = function () {
var instance = Modals.open({
template: '<div class="modal-body">Close Me</div>' +
'<div class="modal-footer"><a class="btn btn-default" ng-click="$close()">Close</a></div>'
});
instance.result.finally(function () {
alert('result');
});
instance.removed.then(function () {
alert('closed');
});
};
});
I also wrote a blog post about it here.

Resources