Forcing AngularJS directive to link outside digest cycle - javascript

This refers to an Angular 1 application.
If the DOM is modified outside the context of my angular application, I know I can use angular.element(document.body).scope().$apply() to force the whole app to re-render, including the newly injected content.
However my directives never seem to link.
So in the example below, the markup <message></message> should render Hello World, but when it is injected manually, then digest applied, the link method never appears to run.
https://jsbin.com/wecevogubu/edit?html,js,console,output
javascript
var app = angular.module('app', [])
app.directive('message', function() {
return {
template: 'Hello, World!',
link: function() {
console.log('message link')
}
}
})
document.getElementById('button').addEventListener('click', function() {
document.getElementById('content').innerHTML = '<message>default content</message>'
var scope = window.angular.element(document.body).scope()
scope.$apply()
})
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
</head>
<body ng-app="app">
inside app:
<message></message>
outside app:
<button id="button">Print another message</button>
<div id="content"></div>
</body>
</html>

According to the docs, you can do this with angular.injector
angular.injector allows you to inject and compile some markup after the application has been bootstrapped
So the code for your example could be:
document.getElementById('button').addEventListener('click', function() {
var $directive = $('<message>default message</message>');
$('#content').append($directive);
angular.element(document.body).injector().invoke(function($compile) {
var scope = angular.element($directive).scope();
$compile($directive)(scope);
});
})
Hope this is what you are looking for!

Related

Jsdom load javascript code but doesn't run correctly

What is wrong with this code? It displays the says: hello bar from the out.js console.log but does not run the rest of the script doesn't add the link inside <div id="link"></div>
If I put the script directly in the code it works, but not in a .js file
teste2.js
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="link"></div>
<p>Hello world</p>
<script src="http://localhost/2022/jsdom/out.js"></script>
</body>
</html>`, { resources: "usable", runScripts: "dangerously"});
const document = dom.window.document;
console.log(document.getElementsByTagName('html')[0].innerHTML);
out.js
let T = document.getElementById('link');
T.innerHTML = 'LINK';
console.log('bar says: hello');
JSDOM loads sub-resources asynchronously, so your Node.js code is accessing the DOM before the <script> code has executed.
This is also why the log of document.getElementsByTagName('html')[0].innerHTML appears before the log of 'bar says: hello'.
You can handle this by explicitly waiting for the load event:
const document = dom.window.document;
document.addEventListener('load', () => {
console.log(document.getElementsByTagName('html')[0].innerHTML);
});
You'll need more complex logic if the script itself does anything asynchronously. Firing a custom event that you can listen for is a good approach.

Defining Polymer element after importing ES6 code via System.js

I'm creating an HTML element using Polymer, and I want it to be able to work with an ES6 class I've written. Therefore, I need to import the class first and then register the element, which is what I do:
(function() {
System.import('/js/FoobarModel.js').then(function(m) {
window.FoobarModel = m.default;
window.FoobarItem = Polymer({
is: 'foobar-item',
properties: {
model: Object // instanceof FoobarModel === true
},
// ... methods using model and FoobarModel
});
});
})();
And it works well. But now I want to write a test HTML page to display my component with some dummy data:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/bower_components/webcomponentsjs/webcomponents.js"></script>
<script src="/bower_components/system.js/dist/system.js"></script>
<script>
System.config({
map:{
traceur: '/bower_components/traceur/traceur.min.js'
}
});
</script>
<link rel="import" href="/html/foobar-item.html">
</head>
<body>
<script>
(function() {
var data = window.data = [
{
city: {
name: 'Foobar City'
},
date: new Date('2012-02-25')
}
];
var view;
for (var i = 0; i < data.length; i++) {
view = new FoobarItem();
view.model = data[i];
document.body.appendChild(view);
}
})();
</script>
</body>
</html>
Which isn't working for one simple reason: the code in the <script> tag is executed before Polymer registers the element.
Thus I'd like to know if there's a way to load the ES6 module synchronously using System.js or even better, if it's possible to listen to a JavaScript event for the element registration (something like PolymerElementsRegistered)?
I've tried the following without success:
window.addEventListener('HTMLImportsLoaded', ...)
window.addEventListener('WebComponentsReady', ...)
HTMLImports.whenReady(...)
In the app/scripts/app.js script from the polymer starter kit, they use auto-binding template and dom-change event
// Grab a reference to our auto-binding template
var app = document.querySelector('#app');
// Listen for template bound event to know when bindings
// have resolved and content has been stamped to the page
app.addEventListener('dom-change', function() {
console.log('Our app is ready to rock!');
});
Also check this thread gives alternatives to the polymer-ready event.

AngularJS Custom directives Not Showing

Learning AngularJS and I can't seem to see what's wrong with my custom directives. Using a modified w3 school code to show a simpler example of my problem.
When I lunch the demo.html in google chrome I only get a white screen.
Top snip - demo.html
Bottom snip - app.js
(function(){
var app = angular.module("myApp", []);
var direc = function()
{
return
{
restrict : 'A',
template : "<h1>Made by a directive!</h1>"
};
};
app.directive("w3TestDirective", direc);
})();
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="app.js" type="text/javascript"></script>
<body ng-app="myApp">
<div w3-test-directive></div>
</body>
</html>
Syntax error: You do not need a closing parantheses while defininig direc function.
(function(){
var app = angular.module("myApp", []);
var direc = function()
{
return
{
restrict : 'A',
template : "<h1>Made by a directive!</h1>"
}
};// You had extra closing parantheses here
app.directive("w3TestDirective", direc);
})();

Component gets error this.getView is not a function

I'm going over the sapui5 walkthrough tutorials and have managed to get to step 9 where it teaches you how to use Component.js file in your app.
Now, prior to using Component.js everything in the app was working fine. However, once I try to use component I get this error:
Uncaught TypeError: this.getView is not a function
Referring to UIComponent.js line 6. Even though my component file is just called Component.js. I also get:
GET http://localhost:58736/InvoicesApp/invoicesapp/Component-preload.js 404 (Not Found)
But I'm not sure they're related
Here is my index.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8'/>
<script src="resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
>
</script>
<!-- only load the mobile lib "sap.m" and the "sap_bluecrystal" theme -->
<script>
sap.ui.localResources("invoicesapp");
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name : "invoicesapp"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content"/>
</html>
My Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (UIComponent, JSONModel, ResourceModel) {
"use strict";
return UIComponent.extend("invoicesapp.Component", {
metadata: {
rootView:"invoicesapp.view.App"
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model on view
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
// set i18n model on view
var i18nModel = new ResourceModel({
bundleName: "invoicesapp.i18n.i18n"
});
this.getView().setModel(i18nModel, "i18n");
}
});
});
My controller
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (Controller, MessageToast, JSONModel, ResourceModel) {
"use strict";
return Controller.extend("invoicesapp.controller.App", {
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
}
});
});
In the component you can't call this.getView() because there is no getView(), the api docs at https://openui5beta.hana.ondemand.com/#docs/api/symbols/sap.ui.core.Component.html
Instead, you set the model directly on the component itself. In other words, just call
this.setModel(oModel);
and
this.setModel(i18nModel, "i18n");
By the way: in the walktrough it's done the same way.
this.getView() will not be defined in component.js as view is yet to be instantiated.
You can simply set model with just this.setModel(oModel) and you can set names model by this.setModel(oModel,"modelName");
I also had the same problem while going through the walk through.
View is not instantiated in the Component.
You can rewrite as
this.setModel("myModel);
And for i18n
this.setModel(i18nModel,"i18n");
This error was there in walk through but now its fixed it seems. There were few errors with the SAP UI5 Tutorials while I was learning but they have fixed most of them now.
This is also a good source to start with UI5. Almost similar to Demokit.
UI Development Toolkit for HTML5 (SAPUI5)
Happy Coding
Febin Dominic

binding variables to compiled elements in angular Js

I am dynamically creating an element with angular. When the app initally runs using app.run() the element is created and I compile the element, after compiling the element I add an ng-show="active" so I can toggle the visibility of the element and possibly animate a transition later using that directive. The only problem is the compiled element only binds once it looks like to the variable active. Once its set to true it won't hide again. Do I need to use digest or some other method to get it the binding to work properly?
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<script src="lib/angular/angular.min.js"></script>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
<style>
.message-app { width:100px; height:30px; background-color:#005cab; color:white; text-align:center; line-height:30px; margin-bottom:5px; }
.app-console { width:300px; height:30px; border:1px solid red; }
</style>
</head>
<body ng-controller="AppCtrl">
<div class="message-app" val=''>click</div>
<div class="message-app" val='true'>click</div>
</body>
</html>
<script>
var app = angular.module("messageApp", []);
app.run(function($rootScope){
$rootScope.message = angular.element("<div class='app-console' ng-show='active'></div>");
angular.element(document.body).append($rootScope.message);
})
function AppCtrl($scope, $compile, $rootScope){
$scope.active = false;
$compile($rootScope.message)($scope);
$scope.setActive = function(val){
$scope.active = val;
$scope.$apply();
}
}
app.directive("messageApp", function(){
return {
restrict:"C",
controller:"AppCtrl",
scope:{
val:"#"
},
link:function(scope, element, attr){
element.bind("click", function(){
scope.setActive(Boolean(scope.val));
})
}
}
})
angular.element(document).ready(function(){
angular.bootstrap(document.querySelector("html"), ["messageApp"])
})
</script>
To make this simple I didn't put the js in a separate file. Is there something that I am doing wrong with this?
Glossing over the downsides of doing DOM manipulation in .run the issue you're having is related to your directive's isolated scopes.
When you call setActive() from the directive all the scope references inside setActive() are to the calling object's scope (which is the directive's isolate scope). So you're currently manipulating a property active on each directive's isolate scope. What you want, however, is to change active on the appCtrl scope.
To accomplish this I'd get rid of setActive() and do this entirely in your directive with these 3 steps:
1) Add active to your directive's scope:
scope:{
val:"#",
isactive: "="
},
2) Pass active to your directive:
<div class="message-app" val='' isactive="active">click</div>
<div class="message-app" val='true' isactive="active">click</div>
3) Change active directly in your click handler:
element.bind("click", function(){
scope.$apply(function() {
scope.isactive =Boolean(scope.val);
});
});
Now you don't need to worry about having your directive talk to your controller, or about which scope you're on. And you've got a clear interface to your directive.
Here's a working fiddle

Categories

Resources