I am very excited with ES2015 modules in Meteor 1.3. We have written an app with medium complexity with Meteor 1.2. As we have very large number of templates and files, it is taking a bit of time to download the content on client side. so I am interested in the lazy loading feature using import. From the meteor night talk, they say that the Blaze templates are still global and cannot be imported (or lazy loaded), I have tried using React inside Blaze.
Added react-template-helper package using meteor add react-template-helper
Create imports folder and added testComponent.jsx file which exports 'TestComponent'
//testComponent.jsx
import React from 'react';
export default class TestComponent extends React.Component {
render() {
return (
<div>
<h1>TestComponent</h1>
<div>This is from Test Component</div>
</div>
);
}
}
After in the Blaze template outside imports folder,
<!-- homeReact template html-->
<template name="homeReact">
<div>
{{> React component=TestComponent}}
</div>
</template>
In the template's js file which is also outside of imports folder
// homeReact template js
import { Template } from 'meteor/templating';
import TestComponent from '/imports/testComponent.jsx`;
Template.homeReact.helpers({
TestComponent() {
return TestComponent;
}
});
This worked but the imports/testComponent.jsx is downloaded on the client (checked using chrome dev tools - sources tab), even if the current route doesn't require homeReact template.
Then I have used require instead of import like this,
// homeReact template js
import { Template } from 'meteor/templating';
Template.homeReact.onCreated(function () {
this.TestComponent = require('/imports/testComponent.jsx').TestComponent;
});
Template.homeReact.helpers({
TestComponent() {
return Template.instance().TestComponent;
}
});
This code also downloads the imports/testComponent.jsx file but in addition I also got an error
In template "homeReact", call to {{> React ... }} missing component argument.
So, my question is, is it possible to lazy load (download) files only when required?
Related
The Package
I have a shared library as Meteor package with the following structure:
ui
- componentA.html
- componentA.js
- loader.js
- ...
tools
- toolXY.js
- ...
and while tools are shared among many of my apps (and the ui), the ui part is only used by one app. Consider the template to be totally simple:
componentA.html
<template name="componentA">
<span>yea component A</span>
</template>
componentA.js
import { Template } from 'meteor/templating'
import { toolXY } from '../tools/toolXY'
import './componentA.html'
// ... template code
loader.js (little helper, think the package has 100 ui components)
export const loader = {
componentA: {
template: 'componentA',
load: async function () {
return import('./componentA')
}
}
}
Because the ui is used in one app only, I made the templating and dynamic-import a weak dependency:
Package.onUse(function (api) {
api.versionsFrom('1.6')
api.use('ecmascript')
api.use('dynamic-import', ['client'], { weak: true })
api.use('templating', ['client'], { weak: true })
})
The Problem
I add my package and the weak dependencies to a project via
$ meteor add dynamic-import templating me:mypackage
and import the ui on the client like the following:
client/main.js
import { loader } from 'meteor/me:mypackage/ui/loader'
Meteor.startup(() => {
loader.componentA.load()
.then(() => console.log('loaded'))
.catch(e => console.error(e))
})
It will result in the following error:
Error: "Cannot find module './componentA.html'"
makeMissingError http://localhost:5050/packages/modules-runtime.js?hash=23fe92393aa44a7b01bb53a510a9cab5fb43037c:232
resolve http://localhost:5050/packages/modules-runtime.js?hash=23fe92393aa44a7b01bb53a510a9cab5fb43037c:238
moduleLink http://localhost:5050/packages/modules.js?hash=88e9e724ccc8459066fbe9e3889ef37c7bb7067f:353
module /node_modules/meteor/me:mypackage/ui/componentA.js:16
makeModuleFunction http://localhost:5050/packages/dynamic-import.js?hash=cf582bcc349503492678c9fd3f7bba4a610f70e5:138
fileEvaluate http://localhost:5050/packages/modules-runtime.js?hash=23fe92393aa44a7b01bb53a510a9cab5fb43037c:346
require http://localhost:5050/packages/modules-runtime.js?hash=23fe92393aa44a7b01bb53a510a9cab5fb43037c:248
moduleLink http://localhost:5050/packages/modules.js?hash=88e9e724ccc8459066fbe9e3889ef37c7bb7067f:360
getNamespace http://localhost:5050/packages/dynamic-import.js?hash=cf582bcc349503492678c9fd3f7bba4a610f70e5:187
dynamicImport http://localhost:5050/packages/dynamic-import.js?hash=cf582bcc349503492678c9fd3f7bba4a610f70e5:40
Checking for Package.templating inside loader.js, as well as componentA.js resolves to Object { Template: Template(viewName, renderFunction) } (because it has been installed as package to the project).
The fix that isn't really a fix
If I turn off the weak dependency on templating it will work fine:
api.use('templating')
This, however, will cause all my applications to load templating, allthough they do not require it.
The question
Why is it behaving like this? Shouldn't this work with weak dependency when the dependency has been added to the project?
I'm trying to use the BotUI framework in my meteor project and following the installation guide, but can't seem to get it to work. No matter which setup I try, I always get various errors, for the following I get Uncaught TypeError: BotUI is not a constructor.
My client/main.js file:
//import Meteor stuff
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
//NPM packages
import Vue from 'vue'; //not necessary according to installation guide
import BotUI from 'botui';
//files
import './main.html';
Meteor.startup(() => {
var botui = new BotUI('my-botui-app');
botui.message.bot({
content: "Hello there!"
});
});
My client/main.html file:
<head>
<title>BotUI with Meteor</title>
</head>
<body>
<h1>Welcome to Meteor!</h1>
<div id="my-botui-app">
<bot-ui>
</bot-ui>
</div>
</body>
I also tried including the files locally in /imports or using CDN with $.getScript, but with no success.
What am I missing?
For people who will encounter the "BotUI is not a constructor" issue in the future, this is related to missing scripts and css in the index.html, and recommended to flow the BotUi instructions.
I've installed a Meteor phone authentication package mys:accounts-phone, which should add a phone.number subfield into users collection. I try to access this field as follows:
Meteor.user().phone.number
but typescript shows error
Property 'phone' does not exist on type 'User'.
On the other hand, I have custom props in users.profile, and can easily access them in this way.
Insecure is not yet removed. Autopublish is ON.
this happens sometime when our angular component is initialized but our meteor data is not reached from server.
try to use user injection in place of Meteor.user()
import {Component} from "#angular/core";
import { InjectUser } from 'angular2-meteor-accounts-ui';//<--**** import this****
#Component({
selector: "login-buttons",
template
})
#InjectUser('user') //<--*** add this***
export class LoginButtonsComponent {
user: Meteor.User; //<--*** add this ***
constructor(private router: Router) {}
}
now in user variable you will have all values of Meteor.User
if you want to print in html part use this
<div *ngIf="user">
{{user.phone.number}}
</div>
don't forget to install
meteor add accounts-password
meteor npm install --save angular2-meteor-accounts-ui
and in app.module.ts file
import { AccountsModule } from 'angular2-meteor-accounts-ui';
#NgModule({
imports: [
... other modules here
AccountsModule
],
hope this will work. if this not work let me know i will tell you one other solution
Got probable answer from Meteor docs.
It explains why username property appears. By default, Meteor publish only a number of fields considered to be public. To exposure any additional fields they must be published explicitly.
Not yet have time to test, but think it should work.
The other reason, with the same sympthoms, when publication code do not executed at server side. Try to put
Meteor.startup(() => {
// code to run on server at startup
Meteor.publish('userData', function() {
if(!this.userId) return null;
return Meteor.users.find(this.userId
//, {fields: {lastname: 1,}}
);
});
});
in a file within /server folder of your application.
This code:
//hello.js
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './hello.html';
Template.hello.onCreated(function helloOnCreated() {
// counter starts at 0
this.counter = new ReactiveVar(0);
});
Template.hello.helpers({
counter() {
return Template.instance().counter.get();
},
});
Template.hello.events({
'click button'(event, instance) {
// increment the counter when button is clicked
instance.counter.set(instance.counter.get() + 1);
},
});
<!-- hello.html -->
<body>
<h1>Welcome to Meteor!</h1>
{{> hello}}
{{> info}}
</body>
<template name="hello">
<button>Click Me</button>
<p>You've pressed the button {{counter}} times.</p>
</template>
<template name="info">
<h2>Learn Meteor!</h2>
<ul>
<li>Do the Tutorial</li>
<li>Follow the Guide</li>
<li>Read the Docs</li>
<li>Discussions</li>
</ul>
</template>
Returns this error:
Error: No such template: hello
at lookup.js:189
at Blaze.View.<anonymous> (spacebars-runtime.js:32)
at view.js:199
at Function.Template._withTemplateInstanceFunc (template.js:465)
at view.js:197
at Object.Blaze._withCurrentView (view.js:538)
at viewAutorun (view.js:196)
at Tracker.Computation._compute (tracker.js:311)
at new Tracker.Computation (tracker.js:201)
at Object.Tracker.autorun (tracker.js:576)
To prevent this for getting absurdly long, here is the GitHub repo if needed.
In the tutorial the template comes after the call. But for some reason, it won't on my page. If I move the templates above the call, it works, but not after. I'm not sure what I'm doing wrong.
Here is the directory structure as requested. I've just started this project, and hello is the only page at the moment.
You should also add a comment to the top of each file which merely indicates the working directory.
I assume the first snippet of code is the contents of hello.js and the second snippet to be the content of main.html. I also assume that you are NOT showing us the contents of hello.html.
If all these are true, I can see that you are importing hello.html into hello.js BUT you are ALSO referencing hello.html WITHIN main.html. I assume your error is Meteor being confused as to which is the right reference, and grabbing the wrong one.
Clean code tends to separate Meteor templates into their own directories and files anyways. I'd get rid of them from main.html.
UPDATE:
I think your issue is in your main.js, or rather, your routing file.
For starters, it's bad JS practise to import in the middle of the code. Good practise is:
1) Import all absolute paths
2) Add a spacing
3) Import all relative paths
4) Never import after that
Take a good look # BlazeLayout.render which you have added to your project, but haven't used. Attempting to just brazenly import the JS file is never going to work. You need to pass arguments to the render method, which include the template you desire to render.
I have a ASP.NET 4 Web Forms project written in C#. I would like to add Angular 2. I've seen a lot of examples on the web using ASP.NET 5 but I can't figure out how to do it in my project.
Have faced similar requirement and did some POC on this and definitely moving forward with it . I worked on with each .aspx page as stand alone angular 2 SPA app. This means each aspx page will have its own App.Component.ts and main.ts file(file name based on Angular 2 documentation). main.ts file contains the code to bootstrap the application (sample code below) so there will be a main.ts file for each .aspx page.
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_BINDINGS} from 'angular2/http';
import {HomeComponent} from './home.component';
bootstrap(HomeComponent, [HTTP_BINDINGS])
.catch(err => console.error(err));
There will be one app.component.ts(named it home.component.ts for my home.aspx) file for each aspx page.
Now in the config file which contains Sytem.config details i defined new map and package entry for my home.aspx as shown below:
System.config({
transpiler: 'typescript',
typescriptOptions: {
emitDecoratorMetadata: true,
experimentalDecorators: true
},
map: {
app: '../../Javascript/angular/app',
home: '../../Javascript/angular/home'
},
packages: {
app: { main: './main.ts', defaultExtension: 'ts'},
home: { defaultExtension: 'ts' }
}
});
And finally I moved the System.import code part to .aspx page(code below). Each page will import its own package defined in sytem.config.
<script>
System
.import('home/main-home')
.catch(console.error.bind(console));
</script>
here i have named my main.ts as main-home.ts.
Hopefully this helps you and others. Also i am open for suggestion and review/alternate solution of this approach.
For reference please see: bootstrapping-multiple-angular-2-applications-on-the-same-page
I totally agree with #pawan's answer but yes #pawan i found a nice solution of this. This solution definitely helped me and hope it will help all of you too.
We don't need to create main.ts and AppComponent.ts for each and every page.
We need to make our main component which we are bootstrapping dynamic. In our case, in app.component.ts, i am bootstrapping our component dynamically based on url of current page.
Let's say if your page is home.aspx then boostrap HomeComponent or about.aspx then boostrap AboutComponent
I am doing it by implementing ngDoBootstrap method as following.
export class AppModule {
url : string;
constructor(#Inject(DOCUMENT) private document: any){
this.url = this.document.location.href.toLowerCase();
}
ngDoBootstrap(app:ApplicationRef){
if(this.url.indexOf("home.aspx") > 0){
app.bootstrap(HomeComponent);
}
else if(this.url.indexOf("about.aspx") > 0){
app.bootstrap(AboutComponent);
}
}
}
This is how based on page url, we can dynamically bootstrap our Component and save a lot files of main.ts and app.component.ts for each page.
For this, you need to add entry for each component into entryComponents array of NgModule as below:
#NgModule({
entryComponents : [
HomeComponent,
AboutComponent,
]
})
Hope this helps.
change your index.html base route to your webpage
<base href="/YourWebPageRoute">
include the page inside the webform
<% Response.WriteFile("index.html"); %>