Angular 2 - Multiple templates - javascript

So i have an application where i need to be able to load different templates at runtime.
My standard template looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Template 1</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<app-root></app-root>
</body>
</html>
Now at one point i would like to change to the following template:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Template 2</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link type="text/css" rel="stylesheet" href="assets/reveal.js-3.6.0/css/reveal.css">
<link type="text/css" rel="stylesheet" href="assets/reveal.js-3.6.0/css/theme/black.css">
<link type="text/css" rel="stylesheet" href="assets/reveal.js-3.6.0/lib/css/zenburn.css">
</head>
<body>
<app-root></app-root>
<script type="application/javascript" src="assets/reveal.js-3.6.0/lib/js/classList.js"></script>
<script type="application/javascript" src="assets/reveal.js-3.6.0/lib/js/head.min.js"></script>
<script type="application/javascript" src="assets/reveal.js-3.6.0/js/reveal.js"></script>
</body>
</html>
My question is how can i do that? Either using components or modules?

I don't think it's a good practice to dynamically change the content of index.html, From what I can see, you only need to load few css and js files, when certain conditions are met.
Instead of touching the content of index file, how about handling what resources need to be loaded (and load them) inside a separate service while your application is being initialized?
It may look a little bit hacky, but it worked for me, at least for loading some additional css.
app.module.ts
export function ResourceProviderFactory( provider: ResourcesService ) {
return () => provider.loadResources();
}
#NgModule({
imports: [
...
],
declarations: [
...
],
providers: [
ResourcesService,
{
provide: APP_INITIALIZER,
useFactory: ResourceProviderFactory,
deps: [ ResourcesService ],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
resources.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class ResourcesService {
private _resources = {
typeA: [
'assets/reveal.js-3.6.0/css/reveal.css'
'assets/reveal.js-3.6.0/lib/js/classList.js'
]
}
constructor( private _http: HttpClient ) {
}
public loadResources(): Promise<boolean> {
let res;
if( condition_met ) {
for( let r of this._resources.typeA ) {
res = this._resources.typeA[r];
if( res.indexOf('css') >= 0 ) {
loadCSS( res );
}else if ( res.indexOf('js') >= 0 ) {
loadJS( res );
}
}
}
}
public loadCSS( resourcePath: string ) {
const link = document.createElement( 'link' );
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = 'resourcePath;
document.getElementsByTagName( 'head' )[0].appendChild( link );
}
public loadJS( resourcePath: string ) {
const link = document.createElement( 'link' );
link.type = 'application/javascript';
link.href = resourcePath;
document.getElementsByTagName( 'head' )[0].appendChild( link );
}
}

You could divide your application in one main and multiple sub-layouts.
The application to land on the main layout-component and then load sub-layout on condition having its own styles (you can import additional CSS in sub-component's own css file)..
main-lauoyt.component.html: (The application lands here)
<router-outlet></router-outlet>
sub-layout-1.component.html (all layout type 1 components land here)
<router-outlet></router-outlet>
sub-layout-1.component.css
#import 'path to assets/reveal.js-3.6.0/css/reveal.css';
#import 'path to assets/reveal.js-3.6.0/css/theme/black.css';
#import 'path to assets/reveal.js-3.6.0/lib/css/zenburn.css';
(Do the above for type 2 layout component and import the other bunch of css files there)
Set your paths like this:
{path: 'type1', component: layoutType1Component,
children: ["all the child component paths of type-1 layout"],},
{path: 'type2', component: layoutType2Component,
children: ["all the child component paths of type-2 layout"],},

<ng-template [ngIf]="lessons" [ngIfElse]="loading">
<div class="lessons-list">
...
</div>
</ng-template>
<ng-template #loading>
<div>Loading...</div>`enter code here`
</ng-template>

Related

Laravel inertia render blank page on IPhone device

Issue:
I have a laravel 9 app builded with vue3 , inertia-laravel , inertiajs and laravel-vite , it works perfetct on PC but when I try to open it in mobile device It show a blank page without any console log.
NB : when I toggle device toolbar to mobile in a navigator it works correctly but in real mobile no ( I tested on different navigators)
I can't fix this issue come from inertiajs\inertia-vue3 or inertiajs/inertia-laravel
When I render a blade view it works, just when I render inertia object
Versions:
inertiajs/inertia-laravel version: 0.6.3
#inertiajs/inertia version: 0.11.0
#inertiajs/inertia-vue3 version: 0.6.0
Codes:
web.php:
Route::get('/', function() {
return Inertia::render('WelcomeView');
});
app.js
import './bootstrap';
import '../css/main.css'
import { createPinia } from 'pinia'
import { useStyleStore } from '#/Stores/style.js'
import { darkModeKey } from '#/config.js'
import { createApp, h } from 'vue'
import { createInertiaApp } from '#inertiajs/inertia-vue3'
import { InertiaProgress } from '#inertiajs/progress'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m'
import Notifications from 'notiwind'
import * as ConfirmDialog from 'vuejs-confirm-dialog'
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel'
const pinia = createPinia()
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(pinia)
.use(Notifications)
.use(ConfirmDialog)
.use(ZiggyVue, Ziggy)
.mount(el);
},
});
InertiaProgress.init({
color: '#5136a8',
showSpinner:true
})
const styleStore = useStyleStore(pinia)
/* App style */
//styleStore.setStyle(localStorage[styleKey] ?? 'basic')
styleStore.setStyle()
/* Dark mode */
if ((!localStorage[darkModeKey] && window.matchMedia('(prefers-color-scheme: dark)').matches) || localStorage[darkModeKey] === '1') {
styleStore.setDarkMode(true)
}
WelcomeVue.vue (under pages folder) :
<template>
<div>
hellow world
</div>
</template>
and app.blade.php :
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{!! csrf_token() !!}" />
<title inertia>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<!-- Scripts -->
#routes
#vite('resources/js/app.js')
#inertiaHead
</head>
<body class="font-sans antialiased">
#inertia
</body>
</html>

Start embedded react app with the external parameters

I have a react app embedded in a webpage and the app should start with parameters it gets from the page. Now I pass the parameters on index.HTML file in a div but the problem is if the parameters are updated the app won't notice the changes.
index.HTML
<html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Sales flow</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div class="staringId" packageId="2" itemId="5" channelId="9"></div>
</body>
</html>
index.js
import './index.scss';
import App from './App'
const div = document.querySelector('.staringId')
ReactDOM.render(
<App domElement={div} />,
div
);
App.js
const App = ({ domElement }) => {
const package = domElement.getAttribute("packageId")
const item = domElement.getAttribute("itemId")
const channel = domElement.getAttribute("channelId")
const [data, setData] = useState({
packageId: '',
itemId: '',
channelId: '',
})
useEffect(() => {
setSelData({
packageId: package,
itemId: item,
channelId: channel,
})
}, [package, item, channel])
}
javascript file to embed the app
<div class="staringId" packageId="2" itemId="5" channelId="9"</div>
<script src="./dist/runtime-main.8d32315e.js"></script>
<script src="./dist/2.4c89be1e.chunk.js"></script>
<script src="./dist/main.a41deb26.chunk.js"></script>
My question is how I can pass some external parameters to the app and update(restart) the app after each update to the parameters.
The idea is to have only one top-level component in your HTML, and whatever parameters(props) you are trying to pass sit on the other components within the top-level . Following is the rough structure how it would look like.
index.html
<html>
<body>
<div id="app"></div>
</body>
</html>
index.js
import './index.scss';
import App from './App'
ReactDOM.render(
<App packageId="2" itemId="5" channelId="9" />, // you can choose to dynamically pass the value depends on what you get from page
document.getElementById("app")
);
App.js
const App = ( props ) => {
// props.packageId and etc are available here
// you can then choose to render it with these props value
return (
<MyOtherComponent otherProps="ifany" />
);
}
export default App;

Change Meta Title and Description using Vue.js

Is it possible to change anything higher than the body tag in Vue.Js? The contents for both these elements is currently stored in the JSON file that is attached to an element further down the DOM tree.
I need to try and inject a meta title and description that can be crawled by Google (ie. It injects, then renders before it gets crawled) and understand the issues with accessing the body element and higher up the DOM tree, as the current Vue JSON is injected using the App ID on a DIV lower down.
I have previously used some jQuery code to address this issue on a Square Space template in some previous work
jQuery('meta[name=description]').attr('content', 'Enter Meta Description Here');
PAGE HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="{{items[0][0].meta-desc}}">
<meta name="author" content="">
<title>{{items[0][0].meta-title}}</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<!-- Vue.js CDN -->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!-- Page List -->
<div class="container text-center mt-5" id="app">
<h1 class="display-4">Vue Page Output:</h1>
<h2>{{items[0][0].page1}}</h2>
</div>
<div class="container text-center mt-5">
<h3>Other Pages</h3>
Products
Contact Us
</div>
<!-- /.container -->
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
items: []
},
created: function () {
fetch('test.json')
.then(resp => resp.json())
.then(items => {
this.items = items
})
}
});
</script>
</body>
</html>
JSON
[
[
{
"page1": "Company Name",
"meta-title": "Acme Corp"
"meta-desc": "Welcome to Acme Corp"
}
],
[
{
"products": "Product List"
}
],
[
{
"contactus": "Contact Us at Acme Corp"
}
]
Here is the code in action, the incoming JSON file comes in a fixed array format with the meta details alongside the body elements. Making this a bit more tricky.
https://arraydemo.netlify.com/
Since what you want to change is outside the area controlled by Vue, you just use ordinary DOM manipulation. It would be something like
created() {
fetch('test.json')
.then(resp => resp.json())
.then(items => {
this.items = items;
const descEl = document.querySelector('head meta[name="description"]');
const titleEl = document.querySelector('head title');
descEl.setAttribute('content', items[0]['meta-desc']);
titleEl.textContent = items[0]['meta-title'];
})
}
If you are using Vue Router, I believe that cleaner solution is using beforeEach hook:
const routes = [{ path: '/list', component: List, meta: {title:'List'} }]
router.beforeEach((to, from, next) => {
document.title = to.meta.title
next()
})
But it allows you set only static titles.
However, if you are looking for some SEO optimizations, Nuxt will probably solve most of your problems
Vue meta is an NPM package for meta-data management:
https://vue-meta.nuxtjs.org/
Example of how I use it in a vue page component:
export default {
name: "Contact",
metaInfo: function() {
return {
title: "My page meta title",
meta: [
{ name: 'description', content: "My page meta description" }
]
}
}
If you use Vue Router(that's what I'm doing) you can set Vue meta here so all your page can use it:
import Vue from 'vue'
import Router from 'vue-router'
import VueMeta from 'vue-meta'
Vue.use(Router)
Vue.use(VueMeta)
export default new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
name: 'Home',
component: Home
},
If you want to change the title it is easy to change in vuejs.
In router.js file while creating route you can do something like this.
{
path:"/productdetail",
name:"productdetail",
component:ProductDetail,
meta: {
title : localStorage.getItem("title"),
}
}
router.beforeEach((toRoute,fromRoute,next) => {
window.document.title = toRoute.meta.title;
next();
})
use of localStorage will help you to change title dynamically.
unfortunately, meta description is not changing with the same method.

Angular 2 component directive not working

I am a beginner in angular 2 and I want to make my first app working. I am using TypeScript.
I have the app.component.ts in which I have made a directive to another compoent called todos.component but I am getting the following error at compile time:
[0] app/app.component.ts(7,3): error TS2345: Argument of type '{ moduleId: string; selector: string; directives: typeof TodosComponent[]; templateUrl: string; s ...' is not assignable to parameter of type 'Component'.
[0] Object literal may only specify known properties, and 'directives' does not exist in type 'Component'.
My code is like this:
index.html
<html>
<head>
<title>Angular 2 QuickStart</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- 1. Load libraries -->
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<!-- 2. Configure SystemJS -->
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<!-- 3. Display the application -->
<body>
<app-root>Loading...</app-root>
</body>
</html>
app.component.ts
import { Component } from '#angular/core';
import {TodosComponent} from './todos/todos.component';
#Component({
moduleId : module.id,
selector: 'app-root',
directives: [TodosComponent],
templateUrl : 'app.component.html',
styleUrls : ['app.component.css']
})
export class AppComponent {
title: string = "Does it work?";
}
app.component.html:
<h1> Angular 2 application</h1>
{{title}}
<app-todos></app-todos>
todos.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
moduleId : module.id,
selector: 'app-todos',
template: '<h2>Todo List</h2>'
})
export class TodosComponent {
title: string = "You have to do the following today:";
}
Without the directive, the app works fine.
Any help would be appreciated!
Thanks in advance!
In your app.component.ts you define directive: [TodosComponent].
The directive property has been removed in RC6 from the #Component() decorator.
The solution to this, is to:
create an NgModule and
declare the TodosComponent inside the declarations: [] array.
See here for an example of AppModule:
https://angular.io/docs/ts/latest/tutorial/toh-pt3.html
module.id was added in intially when angular2 was at beta version.Since with new version and angular cli support it is not required to add moduleId:module.id,you can remove from .ts files

Angular2 Component communication in JS

I am unable to communicate between two component in Angular 2 using JS. This is link for tutorial.
I think I am missing something with directives.
I tried to change it with declarations as well
This is app.main.js
(function () {
var Component = ng.core.Component
var bootstrap = ng.platformBrowserDynamic.bootstrap
var RandomComponent = Component({
selector: 'random-component',
template: `
<h2> Hsdsdffdsdfi <h2>
`
}).Class({
constructor: function() {
//empty
}
});
var AppComponent = Component({
selector: 'main-app',
directives: [RandomComponent],
template: `
<h1> Hi {{username}}<h1>
<random-component></random-component>
`
}).Class({
constructor: function() {
//empty
this.username = "Username"
}
});
document.addEventListener('DOMContentLoaded', function(){
bootstrap(AppComponent);
});
})();
and this is index.html
<html>
<head>
<title>Angular 2 QuickStart JS</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- 1. Load libraries -->
<!-- IE required polyfill -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/rxjs/bundles/Rx.umd.js"></script>
<script src="node_modules/#angular/core/bundles/core.umd.js"></script>
<script src="node_modules/#angular/common/bundles/common.umd.js"></script>
<script src="node_modules/#angular/compiler/bundles/compiler.umd.js"></script>
<script src="node_modules/#angular/platform-browser/bundles/platform-browser.umd.js"></script>
<script src="node_modules/#angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>
<!-- 2. Load our 'modules' -->
<script src='app/app.main.js'></script>
</head>
<!-- 3. Display the application -->
<body>
<main-app>Loading....</main-app>
</body>
</html>
I am currently using Angular 2 RC5, and do not wish to go to ts. I also could not find any documentation regarding JS as well. If anyone knows a good documentation link for 'JS' (not ts), please help with that as well.
NgModules
Angular2 RC5 introduced NgModules. According to the documentation,
Angular Modules help organize an application into cohesive blocks of functionality.
You need to define at least one module in your Angular application. You also should bootstrap that (root/main) module instead of a component (AppComponent in your case).
Another change is the way you declare your components and directives. Instead of using the directives property in every component, now you list them in the module using the declarations property.
Here's the working solution:
(function () {
var Component = ng.core.Component
var NgModule = ng.core.NgModule;
var BrowserModule = ng.platformBrowser.BrowserModule;
var bootstrap = ng.platformBrowserDynamic;
var AppComponent = Component({
selector: 'main-app',
template: `
<h1>Hi {{username}}</h1>
<random-component></random-component>
`
}).Class({
constructor: function() {
this.username = 'Username';
}
});
var RandomComponent = Component({
selector: 'random-component',
template: `
<h2>Random title<h2>
`
}).Class({
constructor: function() { }
});
AppModule = NgModule({
imports: [
BrowserModule
],
declarations: [
RandomComponent,
AppComponent
],
bootstrap: [
AppComponent
]})
.Class({
constructor: function() { }
});
document.addEventListener('DOMContentLoaded', function() {
bootstrap.platformBrowserDynamic()
.bootstrapModule(AppModule);
});
})();
Tutorials
Unfortunately, the Angular 2.0 docs about Javascript are definitely not as rich as the docs about Typescript. You can check the QuickStart Guide (there's and appendix regarding NgModule) or the corresponding Typescript chapter.

Categories