Compile directives from generated HTML by Treant-js in Angular2 Project - javascript

TL;DR
I need to make Angular evaluate/parse HTML elements with Angular markup that are dynamically added to the DOM by treant-js.
[UPDATE 30/09/17]
I've followed the solution proposed there (which involves dynamic module/component creation). However, there are more and more issues with this implementation.
First, I'm injecting to the sub-component the needed dependances declared for the parent module (Router, ActivatedRoute, etc.):
this.addComponent(
this.treeContainer.nativeElement.outerHTML,
{},
this.conversation.threads,
this.route,
this.router,
this.ws
);
This seems wrong to me, because the most logical way would be to inject these dependancies directly into the dynamic component (with imports in the dynamic module), but I couldn't manage to do this.
Also, because treant-js needs to directly attach to the DOM the new HTML components it creates (I guess in order to bind the events properly), I'm unable to retrieve the HTML string before it's in the DOM. So, the ugly fix I found was:
to extract the HTML string with this.treeContainer.nativeElement.outerHTML(see the code snippet above) then send it as the template parameter for the dynamic component ;
since there would be 2 trees (because of 1.), to remove the initial treeContainer handled by treant-js: $(this.treeContainer.nativeElement).remove();.
Where then are we?
Besides the issues mentionned earlier, the solution I've tried is not at all acceptable. It seems that treant-js needs to attach its HTML elements to the DOM for event bindings and tree storage/management, so when I duplicate the HTML string of the tree to form the template of a dynamic component, this 2nd tree is well evaluated by Angular, but not under treant-js framework anymore (e.g.: some functionalities like collapsable handles would no longer work).
To sum up: I'm unable to have both Angular and treant-js working at the same time to handle my tree markup.
Moreover, the ugly trick I use to avoid having duplicate trees causes other issues, when it comes to re-generate the tree, whether it be from a dynamic update (new node created by user interaction), or from navigating to the same page multiple times (routing).
Last but not least, I can't even use some Angular markup like ngModel which come from imported modules (e.g. FormsModule): I get an error saying ngModelisn't bound to input element. The module is of course imported in my parent component, and I tried to import it in the dynamic module as well:
private addComponent(template: string, properties: any = {}, threads: Thread[],
route: ActivatedRoute, router: Router, ws: WebSocketService) {
#Component({template})
class TemplateComponent implements OnInit {
//...
#NgModule({
declarations: [TemplateComponent],
imports: [FormsModule]
})
class TemplateModule {}
const module = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = module.componentFactories.find(componentFactory =>
componentFactory.componentType === TemplateComponent
);
const component = this.container.createComponent(factory);
Object.assign(component.instance, properties);
Unless I find soon a way to make both Angular and treant-js work together smoothly, I may have to use another library such as angular-tree-component or ng2-tree. The big downside then is that I may miss some visual functionalities (like tree orientation) and interactivity I found easy to implement with treant-js.
[First post]
I'm new in Angular2, so there are certainly concepts I still don't understand correctly. I'm using treant-js in my own Angular2 project, and I'm stuck at making Angular2 evaluate/compile the directives in the generated HTML by treant-js.
I've seen this : Integrating Treant-js in Angular2 Project to integrate treant-js into the project, but I can only use some static HTML instead of routerLink, ngIf or ngFor for instance.
I call buildConversationTree in the ngInit of my component :
buildConversationTree() {
const config = {
container: '#tree',
connectors: {
type: 'step'
},
node: {
HTMLclass: 'thread-node',
collapsable: true
},
levelSeparation: 45
};
let treeConfig = this.buildNodes(this.conversation.threads);
treeConfig.unshift(config);
const tree = new Treant(treeConfig, this.onTreeLoaded, $);
}
Basically, the buildNodesmethod generate the HTML for each node of the tree, by calling the nodeHTMLmethod :
nodeHTML(data) {
return `
<div id="thread${data.id}" class="thread-node-wrapper">
<a class="main-link" routerLink="../thread/${data.id}">
Lorem ipsum
</a>
</div>
`;
}
I've got a simple template for my component:
<div class="tree-container">
<div id="tree"></div>
</div>
which becomes after the tree generation:
<div class="tree-container">
<div id="tree" class=" Treant Treant-loaded"><svg height="598" version="1.1" width="633" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="overflow: hidden; position: relative;"><desc style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">Created with Raphaël 2.1.4</desc><defs style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></defs></svg><div class="node thread-node" style="left: 191.5px; top: 224px;">
<div id="thread60" class="thread-node-wrapper">
<a class="main-link" routerlink="../thread/60">
Lorem ipsum
</a>
</div>
</div></div>
</div>
And the routerlink="../thread/60"remains uncompiled/recognized by Angular.
In Angular.js:
Back in Angular.js (which was used for the project before), we would solve the problem using $compile :
var treeIsLoaded = function () {
// ...
var e = $('#tree')[0];
$compile(e)($scope);
if (!$scope.$$phase) {
$rootScope.safeApply();
}
// ...
};
and all the directives like ng-click, ng-show, etc., worked out, so I'm a bit puzzled about how hard it is to make it work in Angular2.
What I tried:
https://angular.io/guide/dynamic-component-loader, but unsuccessfully, even by importing the tree from another component didn't solve the issue. My guess is that the directives are not directly coded in the template itself but generated by treant-js, which is a 3rd library manipulating the DOM outside of the Angular framework ;
How can I use/create dynamic template to compile dynamic Component with Angular 2.0?, but I'm not sure this solution shall solve my issue, I'm a bit afraid of its complexity to be honest ;
angular 2 html binding, I tried to manually copy/paste the view element into a named template with ViewContainerRef members in my component, but it would work.

Related

Is it possible to render a PHP frontend into a Vue node?

I have a legacy PHP app which I would like to slowly migrate to Vue. The PHP app renders a bunch of HTML and javascript files in quite a tangled fashion, i.e.
foo.js.php
...
<script src="mysite.com/some_js_file.js" />
...
const a = '<?=$variable_from_php?>';
so in the end, the browser obviously doesn't know how the js files are constructed, but can run them. What I'd like to do is from the outer layer Vue app, request the index page for a certain sub-section of the legacy app, and render that to a Vue node, as a micro-frontend of sorts. When I request each index, it will of course, contain a header with numerous other imports (scripts/styles) that that micro-frontend needs to function. So, two parts to this question: 1) what would be the best (or maybe least terrible) way to do this in Vue. Using v-html? iframe? (please say no iframes) And 2) will there be any showstopper security problems with this approach (since I'm basically saying fetch all the JS in the header and run it). Let me know if this question makes sense. Thanks!
Maybe you need like to : a module php or component as template.php(php server)
export const templateOfAdvanceTemplatePage = `
<div class="content edit-page management">
<md-card class="page-card">
<?php echo "My Component" ?>
</md-card>
</div>
And from node server
import * as url from "url";
var templateOfAdvanceTemplatePage = url.parse("http://www.website.com/template.php");
export default {
template: templateOfAdvanceTemplatePage,
...
}
for more information import vue here, and php as javascript file here
Vue.js can be used in two separate ways: For more complex applications you would use a build process and pre-compile the templates from the source, which are usually Single File Components SFC; *.vue files. The templates would then become render functions and no HTML is ending up in the output assets. There is, however, another way of defining Vue components. You can define them inline with the runtime-only bundle of Vue. For migrations and smaller applications this approach would be advised. You would need to include the compiler. See also the Vue documentation about that topic Vue v2 and Vue v3). If you are importing Vue as a module and are missing the compiler, see here.
If you want to render dynamically generated HTML from PHP as a Vue template, you would need the second approach. Keep in mind that, with this approach, you would always need to have the generated PHP output to be in sync with the Vue components. And you would need to fully trust the HTML, you are generating with PHP, otherwise you will risk injections.
There is, however, still another problem: You need the generated PHP output HTML as a string within JavaScript and it should not be interpreted by the browser (ideally) or removed again from the DOM. So, you need to decide (based on your project) how you want to generate the HTML so that it can be read in as a JavaScript string. Here are some approaches:
Generate the HTML directly into the page. Then, define which element you want to target, get the HTML with .innerHTML and delete the node from HTML (drawback: you will render the HTML twice, might produce short visual glitches).
Fetch the HTML via XHR from a separate page. You will directly have the HTML as a string in the response (see e.g. fetch).
Render <script type="text/x-template" id="static-html-content"></script> around the generated HTML content. Then, you do not need the HTML as string and you can directly use the id as reference (use template: '#static-html-content'). See the documentation of X-Templates in Vue.
Then, you can use the runtime-only version of Vue and define your components. Here is a live example:
const Counter = {
// retrieve and add your template string here
template: `
<div class="counter">
This is a counter: {{ counter }}
<button #click="counter++">Increase Counter</button>
</div>
`,
data: function() {
return {
counter: 0
}
}
};
const App = {
components: { Counter },
template: `
<div class="app">
This is the app component.
<hr />
<counter />
</div>
`
};
new Vue({
el: '#element',
template: '<App />',
components: { App }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="element"></div>
Another approach would be to just render the HTML string within a component with the v-html attribute. The main drawback of this solution is, however, that the content is then not reactive. You cannot change your internal component data and expect the template to react to the changes. Therefore, you are missing out on the main benefits of Vue, but you are not restricted to a template which matches your components internal structure.
A similar question was also posed in the Vue forum: link

How can I use/create dynamic template to compile dynamic Component with Angular 8?

This question is somewhat similar to the one that reads the same but it is for Angular 2, How can I use/create dynamic template to compile dynamic Component with Angular 2 the solution presented there is archaic at this point and the methods there don't even exist now. So basically my questions is this.
I am loading HTML from a service, the HTML comes with links like this:
<a routerLink='/content' (click)="loadHTMLData(pageId)">
so to load this properly into a container in my view I need to create a component on the fly and set its templateHTML attribute also dynamically to the received HTML, so that the (click) events work.
My idea was to create this dynamic component so they have an event handler that would trigger an event (#Output) with a notification to be then captured by the host component which in turn would perform another call to the service that returns the HTML and would replace this component for a new one with the new loaded HTML and it would repeat the process for each time a link is pressed in the content.
I have setup the dynamic creation of the component according with the instructions in https://angular.io/guide/dynamic-component-loader, so now I have a dynamic component but I don't know how to load the HTML as its template and have it rendering correctly. (right now the template renders as a string literal)
I tried passing the HTML to a static template using [innerHTML] by first sanitizing it with settings to trust the content, but that didn't work either, the html and styles were interpreted but the links weren't working.
The most relevant code looks like this:
#Component({
selector: 'app-content-info',
templateUrl: './content-info.component.html',
styleUrls: ['./content-info.component.css']
})
export class ContentInfoComponent implements OnInit {
#Input() info: ContenInfo;
#ViewChild(ContentInfoDirective,{static:true}) cInfoD: ContentInfoDirective;
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngOnInit() {
this.loadComponent();
}
loadComponent() {
const Item = this.info;
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(Item.component);
const viewContainerRef = this.cInfoD.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
(<AdComponent>componentRef.instance).data = Item.data;
}
}
I adapted this from the Angular documentation site (ad-banner.component.ts), but the standard documentation says nothing on how to add a template dynamically.
So basically what I need to know is how can I inject/insert the HTML template with its (click) events in this dynamic component in Angular 8 to make the events work?
The answer that works will be immediately marked as correct.

What is a factory in Angular

I was reading an article of Max NgWizard K, about how Angular updates the DOM. I came across the following:
For each component that is used in the application Angular compiler generates a factory. When Angular creates a component from a factory Angular uses this factory to instantiate View Definition which in turn is used to create component View. Under the hood Angular represents an application as a tree of views.
In another article from Max NgWizard K I found the definition of a factory:
Factories describe the structure of a component view and are used when instantiating the component.
I'm not really sure what is meant with this.
Questions:
What exactly are factories in Angular(2+)?
Are there scenarios that a developer benefits form knowing how they work?
What exactly are factories in Angular(2+)?
Factory is one of the design patterns mentioned by Gang of Four (Basically they wrote a book on the design patterns they discovered).
Design Patterns help programmers solve common development tasks in a specific way.
And in this case, the Factory pattern helps in instantiation and creation of Objects.
It is also known as the Virtual Constructor.
Think of it, like this:
Say you are making a 2D shooter game, and you have to shoot bullets out of barrels.
Instead of instantiating bullets like new Bullet(), every time trigger is pulled, you can use a factory to create bullets, i.e. WeaponsFactory.createInstance(BulletTypes.AK47_BULLET).
It becomes highly scalable, since all you have to do is change the enum and the factory will make it for you.
You won't have to manually instantiate it.
That is what angular does, it automatically creates factory of all the components. Which makes its job easier.
Are there scenarios that a developer benefits form knowing how they work?
You don't have to know the inner workings of a Factory to use Angular, but it's useful for creating components dynamically!
e.g. A lot of *ngIf, or *ngSwitchCase can be replaced by a simple dynamic generation of components
Components can be created dynamically like this:
createComponent(type) {
this.container.clear();
const factory: ComponentFactory = this.resolver.resolveComponentFactory(AlertComponent);
this.componentRef: ComponentRef = this.container.createComponent(factory);
}
Reference for understanding the above code: Dynamically Creating Components
'A factory' in this case is an instance of ComponentFactory, a class that has create method that implements Factory method pattern.
When componentFactory.create is called (either directly or via ComponentFactoryResolver - which is essential for dynamic components, as linked article explains), new component instance is created.
In general factory is a creational design pattern. It is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class from some method call.
From the Angular docs
#Component({
selector: 'app-typical',
template: '<div>A typical component for {{data.name}}</div>'
)}
export class TypicalComponent {
#Input() data: TypicalData;
constructor(private someService: SomeService) { ... }
}
The Angular compiler extracts the metadata once and generates a
factory for TypicalComponent. When it needs to create a
TypicalComponent instance, Angular calls the factory, which produces a
new visual element, bound to a new instance of the component class
with its injected dependency.
This is something which happens behind the scenes. But you create dynamic components using ComponentFactoryResolver as well (Dynamic component loader)
//Only dynamic component creation logic is shown below
loadComponent() {
this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
const adItem = this.ads[this.currentAdIndex];
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
const viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent<AdComponent>(componentFactory);
componentRef.instance.data = adItem.data;
}
Also read this article about how the component factories work in Ivy.

KnockoutJs Components - add default class

Knockout gives you two ways of instantiating a component, either with a custom html element or with the component binding.
However I have discovered a slight issue when trying to style the root component element. It's fine if you just use the custom element syntax as you can just assign css styles to that - however, if you then use the component binding, the css rules don't match and so they fail.
Ideally I want to support both scenarios as they both have their uses. If I could get knockout to add a class to the root component element which is just the component name it would solve the issue but reading the documentation it isn't clear where it would be best to do this.
I've already got a custom template loader which retrieves the template from an ajax call, but this template is just the inner html of the root node.
Basically I want this:
<my-custom-element>
...
...
<my-custom-element>
To become this:
<my-custom-element class="my-custom-element">
...
...
<my-custom-element>
Anyone got any ideas?
You can use "createViewModel" method and access element in the component (e.g. to add some class):
ko.components.register('some-component', {
viewModel: {
createViewModel: function(params, componentInfo) {
var $element = $(componentInfo.element.children[0]);
// some other code ...
}
},
template: "<div></div>"
});

True custom attributes (e.g. Microdata) in React

The site I am developing makes use of Microdata (using schema.org). As we are shifting development over to use React to render our views I have hit a blocker where React will only render attributes in the HTML spec however Microdata specifies custom attributes such as itemscope.
As I'm relatively new to React and haven't had chance to fully understand the core just yet, my question is what would be the best way to extend the functionality of react.js to allow for defined custom attributes, e.g., Microdata?
Is there a way of extending the attributes/props parser or is it a job for a mixin which checks all passed props and modifies the DOM element directly?
(Hopefully we'll be able to put together a drop in extension for everyone to provide support for this when a solution is clear.)
You can also use "is" attribute. It will disable the attribute white-list of React and allow every attribute. But you have to write class instead of className and for instead of htmlFor if you use is.
<div is my-custom-attribute="here" class="instead-of-className"></div>
Update React 16 custom attributes are now possible
In react 16 custom attributes are now possible
React 16 custom attributes
It looks like these non-standard properties have been added to React
itemProp: MUST_USE_ATTRIBUTE, // Microdata: http://schema.org/docs/gs.html
itemScope: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE, // Microdata: http://schema.org/docs/gs.html
itemType: MUST_USE_ATTRIBUTE, // Microdata: http://schema.org/docs/gs.html
Note that properties have capital letter in the middle:
<div itemProp="whatever..." itemScope itemType="http://schema.org/Offer">
will generate proper lowercase attributes as result.
You should be able to do it with componentDidMount:
...
componentDidMount: function() {
if (this.props.itemtype) {
this.getDOMNode().setAttribute('itemscope', true)
this.getDOMNode().setAttribute('itemtype', this.props.itemtype)
}
if (this.props.itemprop) {
this.getDOMNode().setAttribute('itemprop', this.props.itemprop)
}
}
...
The whole check for Microdata attributes can be wrapped into a mixin for convenient. The problem with this approach is that it won't work for built-in React component (components created by React.DOM). Update: Looking closer at React.DOM, I come up with this http://plnkr.co/edit/UjXSveVHdj8T3xnyhmKb?p=preview. Basically we wrap the built-in components in a custom component with our mixin. Since your components are built upon React 's built-in DOM components, this would work without you having to include the mixin in the components.
The real solution would be injecting a custom config instead of React's DefaultDOMPropertyConfig, however I can't find a way to do so in a drop-in manner (DOMProperty is hidden by the module system).
For those who's still looking for answers:
https://facebook.github.io/react/docs/tags-and-attributes.html
Example:
<div itemScope itemType="http://schema.org/Article"></div>
So far, the best method I've found is based off of some Amp interop code linked from a comment on react's bug tracker thread on the subject. I modified it slightly to work with a newer version of React (15.5.4) and TypeScript.
For regular ES6, you can just remove the type annotation for attributeName. Using require was needed in TS since DOMProperty isn't exposed in react's index.d.ts, but again import could be used in regular ES6.
// tslint:disable-next-line:no-var-requires
const DOMProperty = require("react-dom/lib/DOMProperty");
if (typeof DOMProperty.properties.zz === "undefined") {
DOMProperty.injection.injectDOMPropertyConfig({
Properties: { zz: DOMProperty.MUST_USE_ATTRIBUTE },
isCustomAttribute: (attributeName: string) => attributeName.startsWith("zz-")
});
}
Now you can use any attribute starting with zz-
<div zz-context="foo" />
Normally it'd be a bad idea to use internal parts of react like this, but I think it is better than any of the other methods. It works the same way as existing open-ended attributes like data- and the JSX is even type safe in TS. I believe the next major version of react is going to do away with the whitelist anyway, so hopefully changes won't be needed before we can remove this shim entirely.

Categories