Render HTML string template that refers internal angular components - javascript

I'm trying to render an HTML string template that refers internal angular components & directives which are part of the app.
The question is that bind is lost in components and I don't know how to "preload" directives just like we do with components through entryComponents.
I created a demo project to better explain/demonstrate what I'm trying to achieve with dynamic templates:
There are 3 samples in my demo project:
Sample #1 - Common usage scenario - components are referenced inside my main component and work as expected
Sample #2 - [innerHTML] which obviously doesn't meet the requirements.
Sample #3 - Dynamic render which doesn't work for components since bind is broken and directives are not processed.
https://stackblitz.com/edit/angular-dynamic-html-1pcrbx?file=app/app.component.html
#Component({
})
class MyComponent {
constructor() {}
private myTemplate = `
<hello name="TEST"></hello>
<div test>My div referencing a custom directive</div>
`;
}
So, is it possible to do what I'm tying to do?
What am I doing wrong?

Related

How to specify template for Glimmer Component?

I have a typical Glimmer "base" component:
import Component from '#glimmer/component';
export default class BaseComponent extends Component { ... }
It has a template like normally, but the actual implementations of that component are child componenents, that override some of the template getters and parameters so that it works with various different data types.
export default class TypeAComponent extends BaseComponent { ... }
export default class TypeBComponent extends BaseComponent { ... }
etc.
My question is: How do I specify that all the child components should use the parent class template, so I don't have to duplicate the same fairly complex HTML for all child components? Visually the components are supposed to look identical so any changes would have to be replicated across all child component types. Therefore multiple duplicated templates isn't ideal.
In Ember Classic components there was layout and layoutName properties so I could just do:
layoutName: 'components/component-name'
in the base component and all child components did automatically use the defined template.
Now that I'm migrating to Glimmer components I can't seem to figure out how to do this. I have tried:
layout property
layoutName property
template property
Using the child components without a template in hope that they would automatically fall back to the parent class template.
Only thing that seems to work is creating Application Initializer like this:
app.register('template:components/child1-component', app.lookup('template:components/base-component'));
app.register('template:components/child2-component', app.lookup('template:components/base-component'));
But that feels so hacky that I decided to ask here first if there is a proper way to do this that I have missed?
How to specify template for Glimmer Component?
tl;dr: you should avoid this.
There are two answers to two, more specific, questions:
What is the recommended way to manage complex components with shared behaviors?
Typically, you'll want to re-work your code to use either composition or a service.
Composition
<BaseBehaviors as |myAPI|>
<TypeAComponent #foo={{myAPI.foo}} #bar={{myAPI.bar}} />
<BaseBehaviors>
Where BaseBehaviors' template is:
{{yield (hash
foo=whateverThisDoes
bar=whateverThisBarDoes
)}}
Service
export default class TypeAComponent extends Component {
#service base;
}
and the service can be created with
ember g service base
then, instead of accessing everything on this, you'd access everything on this.base
Ignoring all advice, how do I technically do the thing?
Co-located components (js + hbs as separate files), are combined into one file at build time, which works like this:
// app/components/my-component.js
import Component from '#glimmer/component';
export default class MyComponent extends Cmoponent {
// ..
}
{{! app/components/my-component.hbs }}
<div>{{yield}}</div>
The above js and hbs file becomes the following single file:
// app/components/my-component.js
import Component from '#glimmer/component';
import { hbs } from 'ember-cli-htmlbars';
import { setComponentTemplate } from '#ember/component';
export default class MyComponent extends Cmoponent {
// ..
}
setComponentTemplate(hbs`{{! app/components/my-component.hbs }}
<div>{{yield}}</div>
`, MyComponent);
So this means you can use setComponentTemplate anywhere at the module level, to assign a template to a backing class.
Why is this not recommended over the other approaches?
All of this is a main reason the layout and related properties did not make it in to Octane.
Formally supported Component inheritance results in people getting "clever"
this in of itself, isn't so much of a problem, as it is what people can do with the tool. Bad inheritance is the main reason folks don't like classes at all -- and why functional programming has been on the rise -- which is warranted! Definitely a bit of an over-correction, as the best code uses both FP and OP, when appropriate, and doesn't get dogmatic about this stuff.
Component Inheritance is harder to debug
Things that are a "Foo" but are a subclass of "Foo" may not actually work like "Foo", because in JS, there aren't strict rules around inheritance, so you can override getters, methods, etc, and have them provide entirely different behavior.
This confuses someone who is looking to debug your code.
Additionally, as someone is trying to do that debugging, they'll need to have more files open to try to under stand the bigger picture, which increases cognitive load.
Component inheritance allows folks to ignore boundaries
This makes unit testing harder -- components are only tested as "black boxes" / something you can't see in to -- you test the inputs and outputs, and nothing in between.
If you do want to test the in-between, you need to extract either regular functions or a service (or more rendering tests on the specific things).
I would say this is the classic case for a composition, where TypeAComponent and TypeBComponent use the BaseComponent.
So you have your BaseComponent with all the HTML, that basically is your template. I think its important here to think a bit more of Components also as possible Templates, not only full Components. So lets call this the TemplateComponent.
So you have your TemplateComponent which could also be a template-only component. Then you have as template for TypeAComponent and TypeBComponent:
<TemplateComponent
#type={{#title}}
#title={{#title}}
#onchange={{#onchange}}
#propertyThatIsChanged={{this.propertyThatIsChanged}}
...
/>
this allows you to have a getter propertyThatIsChanged to overwrite pieces. Common behaviour can also be placed on the TemplateComponent, or, if its common code, maybe on a BaseCodeComponent, that only contains shared code, while I would rather not do this.
For areas you want to replace this also opens the possibility to use Blocks.
The TemplateComponent, for example, could use has-block to check if a :title exists, use this block then ({{yield to="default"}}), and if not just use {{#title}}.
So, to the only obvious downside of this: you have to proxy all params. This seems ugly at first, but generally I think its better for components not to have too many arguments. At some point an options or data argument could be better, also because it can be built with js when necessary. It should also be mentioned that there is an open RFC that would address this issue. With the upcoming SFCs, I think this is the much more future-proof solution overall.

Pass global styles down to Svelte "custom element" components

I am "attempting" to use Sveltejs as a front-end web framework for a project built on Django (relevant as it defines the structure of my application). I am attempting to include Svelte components onto the various templates I have built for my Django application. Now, using the customElement API, I was able to compile and use my Svelte components as custom elements directly in my HTML templates which was great except for one major problem: my global styles weren't propagating to the components. This is, as I found out, because all custom elements are actually compiled as web components, meaning they wrap their internal HTML to prevent styles from affecting them. This is not the behavior I want. However, it seems to be the only real way to use my Svelte components in a way that works cohesively with my HTML templates. How do I solve this problem?
I am using webpack as my module bundler. I can comment a gist link to my webpack.config.js if it would be helpful, but as you can imagine, I am compiling for multiple Django "apps" and so my config is a bit messy. For reference, I have gotten everything including local styling with Sass and custom elements to work as intended. This is simply a question of "how to do something" which despite me spending literal hours Googling, I have been unable to find a clear answer to.
I know you can use the client API directly to access components, but not only is this tedious and messy (especially if I need to compose components on different HTML templates together), but I can't seem to get webpack to expose my Svelte components as Javascript classes. Here is how I am doing this approach (which is not working):
<!-- in the head -->
<script src="bundle.js"></script>
<!-- in the body -->
<script>new bundle.App{target: ...}</script>
It is telling me that App is not defined. Here is what my individual output configs in my webpack.config.js look like:
output: {
library: 'bundle',
// I do not understand at all what these two lines do -- I just found
// them somewhere on the interwebs as a suggestion to solve this problem
libraryTarget: 'umd',
umdNamedDefine: true,
// path, filename, etc.
}
To boil this down, I really have three, intertwined questions:
Can I use the customElement API (which I much prefer) and still apply global styles?
If I can't use the customElement API, is there a better approach to this problem that would allow for global styles?
If there is no other option, how do I properly use the client API with webpack?
TL;DR: There is no clean/perfect answer for this.
For once, there is no way to inject global styles into the Shadow Dom. Having said that there are few things you can try.
First, if you are not using slots, then you can write your own custom element registration function and use that to register elements. You will have to write your own adapter for web components that extends from HTMLElement class. In this approach, each Svelte component would be independent app that you are simply initializing from your web component. This is the best alternative you can explore
Additionally, you can use Constructable Stylesheets. It allows you to programmatically construct a stylesheet object and attach it to a Shadow DOM. Of course, this work only when you have flat components. When your web components are nested within one-another, each would have its Shadow DOM. You would have to create a common global style as a constructable stylesheet and attach to each component. Look here for the example:
const sheet = new CSSStyleSheet();
// Replace all styles synchronously for this style sheet
sheet.replaceSync('p { color: green; }');
class FancyComponent1 extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
// Attaching the style sheet to the Shadow DOM of this component
shadowRoot.adoptedStyleSheets = [sheet];
shadowRoot.innerHTML = `
<div>
<p>Hello World</p>
</div>
`;
}
}
class FancyComponent2 extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
// Same style sheet can also be used by another web component
shadowRoot.adoptedStyleSheets = [sheet];
// You can even manipulate the style sheet with plain JS manipulations
setTimeout(() => shadowRoot.adoptedStyleSheets = [], 2000);
shadowRoot.innerHTML = `
<div>
<p>Hello World</p>
</div>
`;
}
}
In above example, the sheet is a common stylesheet which is being used in two separate web components. But again, you will have to write you own web component wrapper to achieve this.
Is there any performance (or otherwise ) downside to using the #import directive inside custom elements?
This is how I pass global styles to svelte custom elements:
/assets/theme.css
---
:root,:host{
--some-var:1rem;
}
Then inside the component:
CustomElement.svelte
---
<svelte:options tag="custom-element"/>
...
<style>
#import "/assets/theme.css";
:host{
padding:var(--some-var)
}
</style>

Shared functions across multiple angular components called on click

I have an Angular website that has several components. Many of these components include displaying text that links to other components. I route to these other components with an html element like this:
<a (click)='routeToTeamSeason(team_season_id)' [routerLink]>{{team}}</a>
and then the function looks like:
routeToTeamSeason(team_season_id: string): void{
console.log("Routing to: " + team_season_id)
this.router.navigate(['/teams/season', team_season_id])
}
Where router is an instance of the #angular/router class.
Several of my components all can use this same routing logic, and right now I have the same exact function copy and pasted into each component's .ts file. How can I create a shared function that will be recognized by each component's html template?
Not sure this is the best method, but here is the solution I found:
create a separate file route-to-team-season.ts:
export function routeToTeamSeason(team_season_id: string): void{
console.log("Routing to: " + team_season_id)
this.router.navigate(['/teams/season', team_season_id])
}
Then import that function into any component and declare it locally:
myRouteToTeamSeason = routeToTeamSeason
Finally, update the template to call myRouteToTeamSeason(...)
This seems a little ugly to me, so if someone has a nicer solution, let me know!

passing reference of angular 5 component to angular js

I have made a directive in angular 5 which uses ngTemplateOutlet, thus my directive then requires the name of a component, which it then renders dynamically.
<back-container [componentOutlet]='dynamicComponent' [componentOutletContext]="{'data' : data}"></back-container>
Now this directive i am using in a component, which takes the reference of the component to be rendered as input.
<tile [dynamicComponent]='instance>
where instance is
instance = SpeedometerComponent;
This component i want to use in angular js application, thus i am using downgradeComponent to downgrade the component and use it in angular js.
.directive(
'Tile',
downgradeComponent({ component: TileComponent }) as angular.IDirectiveFactory)
How to pass component as an input in angular js application?
The component which i have to pass in angular js is another angular 5 component

Angular 2 component inside Angular 1?

I want to add an Angular 2 component inside my Angular 1 controller, so the Angular 1 controller is a parent and Angular 2 component is a child. I would like a child and parent be able to exchange data between each other like using #Input and #Output in Angular 2. Does anyone know how to do that?
This should be done using Upgrade Adapter module (shipped with Angular 2).
The steps should be:
1. Bootstrap you app using the adapter, instead of ng-app
2. Downgrade your angular 2 component and wrap it with Angular 1 directive.
You can use my super simple Todo app example (just look into the commits for the steps inside 'upgrade' branch):
Todo-app example
This is how your bootstrap file look like:
declare var angular: any;
import {UpgradeAdapter} from 'angular2/upgrade';
import {TodoList} from "./components/todo-list";
import {TodoInput} from "./components/todo-input";
import {TodoApp} from "./components/todo-app";
let adapter = new UpgradeAdapter();
angular.module('todoListWorkshopApp').directive('todoList', adapter.downgradeNg2Component(TodoList));
angular.module('todoListWorkshopApp').directive('todoInput', adapter.downgradeNg2Component(TodoInput));
angular.module('todoListWorkshopApp').directive('todoApp', adapter.downgradeNg2Component(TodoApp));
adapter.bootstrap(document.body, ['todoListWorkshopApp']);
And this is an example of the controller's template (Angular 1):
<div>
<todo-input (on-item-added)="add($event)"></todo-input>
<todo-list [todos]="todos"></todo-list>
</div>

Categories