Unable to dynamically reload component - dynamic

I am trying to dynamically load a tab component based on screen resolution. I have a service with an observable with values: xl, md, sm, xs. The component initially loads on xs and then unloads on screen resizes. The problem is when you resize the screen back to under 768 (xs) the component is not fully materialized. I can see that the component is injected into the DOM on the second load, but it appears the directives are not rendered.
Plnkr - Try sizing the display full width, then resize back to smallest size to reload tab component
import {Component, bootstrap, DynamicComponentLoader, ElementRef, ComponentRef} from 'angular2/core'
import {UiTabs, UiPane} from './ui_tabs'
import {TabbedLayout} from './tabbed_layout'
import {ResizeSvc} from './resize_svc';
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<div #location></div>
</div>
`,
providers: [ResizeSvc]
})
export class App implements OnInit{
private resizeSvc: ResizeSvc;
private _children:ComponentRef;
constructor(private _dcl: DynamicComponentLoader, private _e: ElementRef, pResizeSvc:ResizeSvc) {
this.name = 'Angular2'
this.resizeSvc = pResizeSvc;
}
ngOnInit() {
console.log('initialized app.ts');
this.resizeSvc.layout$.subscribe(
value => this.setLayout(value)
);
}
setLayout(pSize:string) {
this.removeAll();
if(pSize === 'xs') {
console.log('loading layout ' + pSize);
//this._dcl.loadIntoLocation(TabbedLayout, this._e, 'location').then((ref) => {
this._dcl.loadNextToLocation(TabbedLayout, this._e).then((ref) => {
ref.instance._ref = ref;
this._children = ref;
});
} else {
}
}
removeAll() {
if(this._children != null) {
console.log('Disposing layout...');
this._children.dispose();
this._children = null;
}
}
}
Here is a picture of the DOM when initially loaded
And here is a picture of the DOM on the second load with missing tabs

The order of <script> imports matters. angular2-polyfills.js has to be after system-polyfills.js. Here is your plunker working
I just moved the import below to the top of the list.
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.20/system-polyfills.js"></script>

Related

Can't use ref to select swiper methods

I am trying to invoke .slideNext() & .slidePrev that come with swiper when the user presses the arrow keys.
I've managed to do this with querySelector like this:
const changeSlide = (event: KeyboardEvent) => {
const slides = document.querySelector('.swiper').swiper
if(event.key === 'ArrowLeft') {
slides.slidePrev();
} else if (event.key === 'ArrowRight') {
slides.slideNext();
}
}
However this method creates warnings and the usage of querySelector is not allowed in general for this project. So instead i wan't to use a ref to select the swiper but i've not yet succeeded at this.
This is what i tried:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { Navigation } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/navigation';
// just some imports that are needed
const swiperRef = ref();
// swiper as ref
const changeSlide = (event: KeyboardEvent) => {
const slides = swiperRef.value.swiper
// instead of querySelector i use the ref.value
if(event.key === 'ArrowLeft') {
slides.slidePrev();
} else if (event.key === 'ArrowRight') {
slides.slideNext();
}
}
</script>
<template>
<swiper
ref="swiperRef"
:initial-slide="props.initialSlide"
:slides-per-view="1"
:space-between="0"
:modules="[Navigation]"
navigation
>
<swiper-slide> // left out the attributes since they are a mere distraction
<img/> // left out the attributes since they are a mere distraction
</swiper-slide>
</swiper>
</template>
The error i get from this code is:
From what I'm seeing in the source code, you can't access the swiper instance using ref on the swiper vue component because it's not exposed.
You have others way to do thought:
inside a child component of the <swiper> component, use the useSwiper() component.
on the parent component that instanciate the <swiper> component, use the #swiper event. It sends the swiper object once mounted:
<template>
<swiper #swiper="getRef" />
</template>
<script setup>
const swiper = ref(null)
function getRef (swiperInstance) {
swiper.value = swiperInstance
}
function next () {
swiper.value.slideNext() // should work
}
</script>
You don't have to use this method instead you can just import Keyboard like this:
import { Navigation } from 'swiper';
Add Keyboard to modules and attributes of <swiper> like this:
<swiper :modules="[Navigation, Keyboard]" keyboard />
And add the attribute to <swiper-slide> as well:
<swiper-slide keyboard />

Component stays visible when using props in composition API

I have a vue 3 app which display product information. The home component displays all available products. Each of them has a button which takes me to a component displaying information about said product called Single Box. It gets the product ID from the route params and then fetches additional information via graphql. It looks like this:
<template>
<BoxDisplay :box="box"></BoxDisplay>
</template>
<script>
import { useQuery, useResult } from '#vue/apollo-composable';
import { useRoute } from 'vue-router'
import singleBoxQuery from '../graphql/singleBox.query.gql'
import BoxDisplay from '../components/BoxDisplay'
export default {
components: {
BoxDisplay
},
setup() {
const route = useRoute();
const boxId = route.params.id
const {result } = useQuery(singleBoxQuery, {id: boxId})
const box = useResult(result, null)
return {
boxId,
box
}
}
}
</script>
The BoxDisplay component is then used to show the infos of the current box. There is going to be more going on this page later so the BoxDisplay is needed. It looks like this:
<template>
<div class="p-d-flex p-jc-center">
<div v-if="box">
<h1>{{box.Name}}</h1>
<div v-html="myhtml"></div>
</div>
<div v-else>
<ProgressSpinner />
</div>
</div>
</template>
<script>
export default {
props: {
box: {}
},
setup(props){
const myhtml = "<h1>hello</h1>" + props.box.Name
return {
myhtml
}
}
}
</script>
The problem is now, that as soon as I use props in setup() the component stay visible when I click back in my browser. It stays there until I refresh the page. What am I doing wrong?
Use ref and computed
https://v3.vuejs.org/guide/composition-api-introduction.html#reactive-variables-with-ref
```
import { ref, computed } from 'vue'
```
const myhtml = computed(() => '<h1>hello</h1>' + props.box.Name)
or
const myhtml = ref('<h1>hello</h1>' + props.box.Name)

How to add Vuejs component dynamically to the DOM

I want to add a vue component to the DOM when an option is selected from the drop down list, so it's dynamically displayed
The code that I have tried is as follows
var html = "<component :is=\"dynamickeyvalue\"></component>";
$('#extraOptionsElements').html(html);
is there anyway to load the vue component when it's added as all I am getting now is blank.
I have used the component tag to load the component dynamically. The button click will simulate the component changing feature.
<template>
<button
#click="changeComponent()"
>Click me to change component</button>
<component
:is="theSelectedComponent"
></component>
</template>
<script>
import Component1 from '#/components/Component1'
import Component2 from '#/components/Component2'
import Component3 from '#/components/Component3'
export default {
components: {
Component1,
Component2,
Component3
},
data () {
return {
theSelectedComponent: 'Component1'
}
},
methods: {
changeComponent () {
if (this.theSelectedComponent === 'Component1') {
this.theSelectedComponent = 'Component2'
} else if (this.theSelectedComponent === 'Component2') {
this.theSelectedComponent = 'Component3'
} else {
this.theSelectedComponent = 'Component1'
}
}
}
}
</script>

React Router 4 can not load new content on same component with <Link>

I can't seem to trigger any other react component life cycle method other than render() when I click on a link that leads to a page that loads exactly the same component, even though the url is different. So here's my code
//index.js - the entry point
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import Config from './Settings/Config';
import App from './Components/App';
const c = new Config();
render(
<BrowserRouter basename={c.routerBaseName}>
<App />
</BrowserRouter>
, document.getElementById('root'));
Here's my App JS
// Components/App.js
import React, {Component} from 'react';
import {Route} from 'react-router-dom';
import BlogEntry from './BlogEntry';
export default class App extends Component {
render() {
console.log('app');
return (
<div>
<Route exact path="/blog/:name" component={BlogEntry} />
</div>
)
}
}
And here is my BlogEntry.js
// Components/BlogEntry.js
import React from 'react';
import { Link } from 'react-router-dom';
export default class BlogEntry extends React.Component {
async componentDidMount() {
const [r1] = await Promise.all([
fetch(`http://api.myservice.com/${this.props.match.params.name}`)
]);
this.setState({content:await r1.json()});
console.log('fetch');
}
render() {
console.log('render');
if(!this.state) return <div></div>;
if(!this.state.content) return <div></div>;
const content = this.state.content;
return (
<div id="blog-entry" className="container">
<h1>{content.title}</h1>
<div dangerouslySetInnerHTML={{__html:content.content}}></div>
<div className="related-other">
<h2>Related Content</h2>
<ul>
<li><Link to="/blog/new-york-wins-the-contest">New York Wins the Contest!</Link></li>
<li><Link to="/blog/toronto-with-some-tasty-burgers">Toronto with Some Tasty Burgers</Link></li>
</ul>
</div>
</div>
);
}
}
So what happens is that when I click on the link for Toronto with Some Tasty Burgers or New York Wins the Contest! I see the url in my web browser address bar update accordingly. But my componentDidMount does not fire. And hence no new content is fetched or loaded.
React also won't let me put an onPress event handler to the <Link> object. And even if I did, managing the history state when browser clicks back button would be a nightmare if I were to create my own onpress event handler to load pages.
So my question is, how do I make it so that clicking on one of the links actually causes the component to fetch new data and redraw and also be part of the browser back button history?
I added this to my BlogEntry.js and everything works now:
componentWillReceiveProps(nextProps) {
this.props = nextProps;
}
I don't think your proposed solution, via componentWillReceiveProps (deprecated) is good enough. It's a hack.
Why don't you keep the route id in the state (as in /blog/:id).
Then something like this:
componentDidUpdate() {
const { match: { params: { id: postId } = {} } } = this.props;
if(this.state.postId !== postId) {
// fetch content
}
}

Angular 2 equivalent of ng-bind-html, $sce.trustAsHTML(), and $compile?

In Angular 1.x, we could insert HTML in real-time by using the HTML tag ng-bind-html, combined with the JavaScript call $sce.trustAsHTML(). This got us 80% of th way there, but wouldn't work when Angular tags were used, such as if you inserted HTML that used ng-repeat or custom directives.
To get that to work, we could use a custom directive that called $compile.
What is the equivalent for all of this in Angular 2? We can bind using [inner-html] but this only works for very simple HTML tags such as <b>. It doesn't transform custom angular 2 directives into functioning HTML elements. (Much like Angular 1.x without the $compile step.) What is the equivalent of $compile for Angular 2?
In Angular2 you should use DynamicComponentLoader to insert some "compiled content" on the page. So for example if you want to compile next html:
<div>
<p>Common HTML tag</p>
<angular2-component>Some angular2 component</angular2-component>
</div>
then you need to create component with this html as a template (let's call it CompiledComponent) and use DynamicComponentLoader to insert this component on the page.
#Component({
selector: 'compiled-component'
})
#View({
directives: [Angular2Component],
template: `
<div>
<p>Common HTML tag</p>
<angular2-component>Angular 2 component</angular2-component>
</div>
`
})
class CompiledComponent {
}
#Component({
selector: 'app'
})
#View({
template: `
<h2>Before container</h2>
<div #container></div>
<h2>After conainer</h2>
`
})
class App {
constructor(loader: DynamicComponentLoader, elementRef: ElementRef) {
loader.loadIntoLocation(CompiledComponent, elementRef, 'container');
}
}
Check out this plunker
UPD You can create component dynamically right before the loader.loadIntoLocation() call:
// ... annotations
class App {
constructor(loader: DynamicComponentLoader, elementRef: ElementRef) {
// template generation
const generatedTemplate = `<b>${Math.random()}</b>`;
#Component({ selector: 'compiled-component' })
#View({ template: generatedTemplate })
class CompiledComponent {};
loader.loadIntoLocation(CompiledComponent, elementRef, 'container');
}
}
I personally don't like it, it's look like a dirty hack to me. But here is the plunker
PS Beware that at this moment angular2 is under active development. So situation can be changed at any time.
DynamicComponentLoader is deprecated, you can use ComponentResolver instead
You could use this directive, add pipes if you need additional data manipulation. It also allows for lazy loading, you don't need it in your case, but it's worth mentioning.
Directive(I found this code and made some changes, you can do that too to make it fit your taste or use it as is):
import { Component, Directive, ComponentFactory, ComponentMetadata, ComponentResolver, Input, ReflectiveInjector, ViewContainerRef } from '#angular/core';
declare var $:any;
export function createComponentFactory(resolver: ComponentResolver, metadata: ComponentMetadata): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);
return resolver.resolveComponent(decoratedCmp);
}
#Directive({
selector: 'dynamic-html-outlet',
})
export class DynamicHTMLOutlet {
#Input() htmlPath: string;
#Input() cssPath: string;
constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
}
ngOnChanges() {
if (!this.htmlPath) return;
$('dynamic-html') && $('dynamic-html').remove();
const metadata = new ComponentMetadata({
selector: 'dynamic-html',
templateUrl: this.htmlPath +'.html',
styleUrls: [this.cssPath]
});
createComponentFactory(this.resolver, metadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, 0, injector, []);
});
}
}
Example how to use it:
import { Component, OnInit } from '#angular/core';
import { DynamicHTMLOutlet } from './../../directives/dynamic-html-outlet/dynamicHtmlOutlet.directive';
#Component({
selector: 'lib-home',
templateUrl: './app/content/home/home.component.html',
directives: [DynamicHTMLOutlet]
})
export class HomeComponent implements OnInit{
html: string;
css: string;
constructor() {}
ngOnInit(){
this.html = './app/content/home/home.someTemplate.html';
this.css = './app/content/home/home.component.css';
}
}
home.component.html:
<dynamic-html-outlet [htmlPath]="html" [cssPath]="css"></dynamic-html-outlet>
After reading a lot, and being close of opening a new topic I decided to answer here just to try to help to others. As I've seen there are several changes with the latest version of Angular 2. (Currently Beta9)
I'll try to share my code in order to avoid the same frustration I had...
First, in our index.html
As usual, we should have something like this:
<html>
****
<body>
<my-app>Loading...</my-app>
</body>
</html>
AppComponent (using innerHTML)
With this property you will be able to render the basic HTML, but you won't be able to do something similar to Angular 1.x as $compile through a scope:
import {Component} from 'angular2/core';
#Component({
selector: 'my-app',
template: `
<h1>Hello my Interpolated: {{title}}!</h1>
<h1 [textContent]="'Hello my Property bound: '+title+'!'"></h1>
<div [innerHTML]="htmlExample"></div>
`,
})
export class AppComponent {
public title = 'Angular 2 app';
public htmlExample = ' <div>' +
'<span [textContent]="\'Hello my Property bound: \'+title"></span>' +
'<span>Hello my Interpolated: {{title}}</span>' +
'</div>'
}
This will render the following:
Hello my Interpolated: Angular 2 app!
Hello my Property bound: Angular 2 app!
Hello my Interpolated: {{title}}
AppComponent Using DynamicComponentLoader
There is a little bug with the docs, documented in here. So if we have in mind that, my code should look now like this:
import {DynamicComponentLoader, Injector, Component, ElementRef, OnInit} from "angular2/core";
#Component({
selector: 'child-component',
template: `
<div>
<h2 [textContent]="'Hello my Property bound: '+title"></h2>
<h2>Hello my Interpolated: {{title}}</h2>
</div>
`
})
class ChildComponent {
title = 'ChildComponent title';
}
#Component({
selector: 'my-app',
template: `
<h1>Hello my Interpolated: {{title}}!</h1>
<h1 [textContent]="'Hello my Property bound: '+title+'!'"></h1>
<div #child></div>
<h1>End of parent: {{endTitle}}</h1>
`,
})
export class AppComponent implements OnInit{
public title = 'Angular 2 app';
public endTitle= 'Bye bye!';
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
// dynamicComponentLoader.loadIntoLocation(ChildComponent, elementRef, 'child');
}
ngOnInit():any {
this.dynamicComponentLoader.loadIntoLocation(ChildComponent, this.elementRef, 'child');
}
}
This will render the following:
Hello my Interpolated: Angular 2 app!
Hello my Property bound: Angular 2 app!
Hello my Property bound: ChildComponent title
Hello my Interpolated: ChildComponent title
End of parent: Bye bye!
I think all you have to do is set the element you want to have compiled html with the [innerHTML]="yourcomponentscopevar"
Angular provided DynamicComponentLoader class for loading html dynamically. DynamicComponentLoader have methods for inserting components. loadIntoLocation is one of them for inserting component.
paper.component.ts
import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';
#Component({
selector: 'paper',
templateUrl: 'app/views/paper.html'
}
})
export class PaperComponent {
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');
}
}
bulletin.component.ts
import {Component} from 'angular2/core';
#Component({
selector: 'bulletin',
templateUrl: 'app/views/bulletin.html'
}
})
export class BulletinComponent {}
paper.html
<div>
<div #child></div>
</div>
Few things you need to take care of :
Don't call loadIntoLocation inside the constructor of class . Component view is not yet created when component constructor is called. You will get error -
Error during instantiation of AppComponent!. There is no component
directive at element [object Object]
Put anchorName #child in html otherwise you will get error.
Could not find variable child
Have a look at this module https://www.npmjs.com/package/ngx-dynamic-template
After a long research, only this thing helped me. The rest of the solutions seems to be outdated.