Use of jQuery code in Angular app.component.ts - javascript

I am working on Angular app. I imported jQuery package using following code.
$ npm install jquery --save
I am trying to transfer my .js data of html web page to app.component.ts of angular. My setup for JQuery is done in angular app and I even tested it.
I am having checkbox in my web page, which will be selected to remove file using button click. To achieve this, I used the following code in JS.
function RemoveFile() {
var files = document.getElementById("files").children;
for (var i = 1; i < files.length; i++) {
if (files[i].children[0].children[0].checked) {
files[i].remove();
i--;
}
}
}
It is working fine in normal html page but when I used it in app.component.ts. It is giving me the following
Property 'checked' does not exist on type 'Element'
How can I rectify it or used something like this?
selectedFile.children[4].children[0].checked = $(this)[0].checked;

jQuery should only be used in Angular in the most exceptional of cases. Angular is a javascript framework for managing the DOM dynamically based on your model. If you manage the DOM outside of Angular you will more than likely suffer unexpected results.
In your case, I'm going to assume you've got some array of files in your component.
component.ts
export class MyComponent {
files: any[] = [];
ngOnInit() {
// somehow get the files
this.files = [];
}
}
And I'm going to assume that in your HTML you've built some kind of list containing a nested checkbox for each file.
The user will check some checkboxes, and then press a button to remove those files from the list (and probably perform some other action).
component.html
<div id="files">
<div *ngFor="let file of files">
<input type="checkbox" />
</div>
</div>
<button (click)="RemoveFile()">Remove files</button>
So you have used Angular to build the list, but now you want to use jQuery to "unbuild" the list? Why would you do that?
The power of Angular is in its simplicity - the HTML will reflect whatever's in your files array. So if you want to "unbuild" your list, then simply remove items from the array.
Admittedly, it's a little trickier when you have to consider things like an array of checkboxes, but that's no excuse to resort to using jQuery.
Since you are effectively going to build a form with a dynamic array, the best way to do handle this would be to use a reactive form with a form array.
Import the reactive forms module in your app module:
app.module.ts
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
#NgModule({
imports: [
FormsModule,
ReactiveFormsModule
]
}
In your component you will need to inject FormBuilder from #angular/forms. Then build a form once you have your model (which might be inside a service subscription).
You will then delete the files in the form's submit handler. You do have to splice two arrays, but the form array is built from the files array, so they have matching indexes.
component.ts
import { FormBuilder, FormGroup, FormArray } from '#angular/forms';
// ... other imports
export class MyComponent {
constructor(private formBuilder: FormBuilder) {}
files: any[] = [];
form: FormGroup;
private filesFormArray: FormArray;
ngOnInit(): void {
// somehow get the files
this.files = [];
// Build an array of form groups if you want each file to have multiple controls.
// Build an array of form controls if you want each file to have a single control
// I will just add one control per file - an unchecked checkbox,
// but use a form group per file to show how it is done
const filesFormGroups = this.files.map(x => this.formBuilder.group({
delete: this.formBuilder.control(false)
}));
// Build the form array. Store a reference to it for easy access later
this.filesFormArray = this.formBuilder.array(filesFormGroups);
// Build the whole form. At the moment there will only be files.
this.form = this.formBuilder.group({
files: this.filesFormArray
});
}
onSubmit(): void {
// start at the end of the array and work backwards
for (let i = this.files.length - 1; i >= 0; i--) {
// get the checkbox from the form array
const checkBox = this.filesFormArray.controls[i].get('delete') as FormControl;
if (checkbox.value) {
// remove the item from the model and the form
this.files.splice(i, 1);
this.filesFormArray.controls.splice(i, 1);
}
}
}
}
In your HTML, you still build from your files, but also bind to the form. You use the directives formGroupName, formArrayName, and formControlName to match the structure of the form group you built in your component.
component.html
<form [formGroup]="form" (submit)="onSubmit()">
<div formArrayName="files">
<div *ngFor="let file of files; let i = index" [formGroupName]="i">
<input type="checkbox" formControlName="delete" />
</div>
</div>
<button>Remove files</button>
</form>

// First install jQuery
npm install --save jquery
// and jQuery Definition
npm install -D #types/jquery
add jquery path in angular.json script: [] array under build like
script: [ "node_modules/jquery/dist/jquery.min.js"]
then add declare var $:any; after import statement and jquery ready to use

Related

Elixir Phoenix - How to create and import javascript files into specific templates

I'm currently experimenting with the Elixir Phoenix framework together with Liveview. For my project, I would like to write some Javascript code that is only imported on certain pages (templates). Although this seems like something very trivial, I am struggling to get it working.
At this moment I created a seperate Javascript file as such assets/js/custom.js. After doing this, I added the following line to my root.html.heex as a first test to see if this already works. For this line, I simply looked at how app.js is imported.
<script defer phx-track-static type="text/javascript" src={Routes.static_path(#conn, "/assets/custom.js")}></script>
The next step would then be to figure out how to import it in a seperate template instead of the root. However, this first test already failed resulting in the following error:
[debug] ** (Phoenix.Router.NoRouteError) no route found for GET /assets/custom.js (MyAppWeb.Router)
(my_app 0.1.0) lib/phoenix/router.ex:405: MyAppWeb.Router.call/2
(my_app 0.1.0) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app 0.1.0) lib/plug/debugger.ex:136: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app 0.1.0) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(phoenix 1.6.15) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy 2.9.0) c:/Users/arnod/Desktop/phoenixtut/my_app/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.9.0) c:/Users/arnod/Desktop/phoenixtut/my_app/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.9.0) c:/Users/arnod/Desktop/phoenixtut/my_app/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Could somebody help me figure this one out? How does one add seperate Javascript files and only import them in specific templates?
You can import all your custom javascript once in app.js, assign them as hooks which you can then use in your (live) views, wherever needed, for example;
custom.js
export const SomeFunction = {
mounted() {
alert("some function ran!");
}
}
app.js snippet
...
import {SomeFunction} from "./custom.js"
let Hooks = {}
Hooks.SomeFunction = SomeFunction
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: Hooks})
...
Then in your live view render function (or template) add the hook
...
def render(assigns) do
~H"""
...
<div id="my-hook" phx-hook="SomeFunction"></div>
...
end
...
More about javascript interoperability can be found on the Phoenix hex page here. You can tie them to all sorts of phoenix events.
nb. Also note that #conn isn't available in live views, only #socket is.

How to pass a parameter from html page to angular custom element

I've created an angular custom element from an angular component that I want call from a normal html page.
The component requires a parameter, which works when it's called as a component from within an angular project, but I cannot figure out how to pass the parameter from the html tag to the custom element.
Current I'm trying to use:
#Input() crs: string
in the component and:
<station-info crs="btn"></station-info>
in the html tag, but the crs never makes it into the component.
What I'd like to know is the correct way to pass the parameter from the html tag to the component after it has been converted to a custom element?
I eventually worked it out, so here's my write up of how to do it:
How To Create And Use A Custom Element
Install angular elements
ng add #angular/elements
npm i --save-dev concat fs-extra
In tsconfig.json, ensure target uses es2015 or newer:
"target": "es2015",
Add the following to app.module.ts
==> Add the imports
import { Injector} from '#angular/core';
import { createCustomElement } from '#angular/elements';
==> Add the component to the bootstrap array
bootstrap: [AppComponent]
==> Change the constructor to the following:
constructor(
private injector: Injector
) {
const el = createCustomElement(AppComponent, { injector });
customElements.define('station-info', el); // <-- 'station-info' is the name of the html tag you want to use
}
ngDoBootstrap() {}
To enable parameters to be passed from the HTML tag to the custom element, do the following steps
In app.component.ts:
Accept any input parameters using #Input and change any parameters retrieved from ActivatedRoute to use the #Input variables
Note: To ensure the input variables on the HTML tag are bound to the #Input properties, you must pass the tag attribute name to the #Input method i.e. #Input('crs') (see example below)
e.g.
Delete these:
this.crs = this.activatedRouteService.snapshot.params.crs.toLowerCase();
this.stationName = this.activatedRouteService.snapshot.params.stationName.toLowerCase();
Add these (insert after export class):
#Input('crs') crs: string
#Input('stationName') stationName: string;
In ngOnInit(), navigate to the component, passing on any input parameters:
e.g.
this.router.navigateByUrl('/station-details/' + this.crs + '/' + this.stationName);
In app-routing.module.ts
Delete (so there is no automatic routing):
{ path: '', redirectTo: 'station-details', pathMatch: 'full' },
Pass any parameters on the appropriate path:
{ path: 'station-details/:crs/:stationName', component: StationDetailsComponent,
In the custom HTML tag
Add the parameters as follows:
<station-info crs="btn" station-name="Brighton" client="tl" has-template="true"></station-info>
Create a build-component.js file in the root folder with the following content
Note: dist/dev is the folder structure automatically created by the compiler
const fs = require('fs-extra');
const concat = require('concat');
build = async () =>{
const files = [
'./dist/dev/runtime.js',
'./dist/dev/polyfills.js',
'./dist/dev/scripts.js',
'./dist/dev/main.js'
];
await fs.ensureDir('station-info'); // <-- Use the same name here as in customElements.define
await concat(files, 'station-info/station-info.js'); // <-- this is where you want to save the resulting files
}
build();
Compile and concatenate the resulting files
==> Add build-component.js script to package.json
"scripts": {
"build:component": "ng build --configuration production --output-hashing none && node build-component.js",
}
==> Run the script
npm run build:component
To use the custom element, add the following to an html page
Note: You also have to ensure any external assets that the custom element requires are present e.g. images and files
If you want to pass a parameter:
You can use property binding to pass data from parent to child.
Like so,
Use property binding to bind the item property in the child to the currentItem property of the parent.
<app-item-detail [item]="currentItem"></app-item-detail>
In the parent component class, designate a value for currentItem:
export class AppComponent {
currentItem = 'Television';
}
Link to angular docs:
https://angular.io/guide/inputs-outputs

How to include external JavaScript libraries in Angular 2?

I am trying to include an external JS library in my Angular 2 app and trying to make all the methods in that JS file as a service in Angular 2 app.
For eg: lets say my JS file contains.
var hello = {
helloworld : function(){
console.log('helloworld');
},
gmorning : function(){
console.log('good morning');
}
}
So I am trying to use this JS file and reuse all the methods in this object and add it to a service, so that my service has public methods, which in turn calls this JS methods. I am trying to reuse the code, without reimplementing all the methods in my typescript based Angular 2 app. I am dependent on an external library, which I cant modify.
Please help, thank you in advance.
With ES6, you could export your variable:
export var hello = {
(...)
};
and import it like this into another module:
import {hello} from './hello-module';
assuming that the first module is located into the hello-module.js file and in the same folder than the second one. It's not necessary to have them in the same folder (you can do something like that: import {hello} from '../folder/hello-module';). What is important is that the folder is correctly handled by SystemJS (for example with the configuration in the packages block).
When using external libs which are loaded into the browser externally (e.g. by the index.html) you just need to say your services/component that it is defined via "declare" and then just use it. For example I recently used socket.io in my angular2 component:
import { Component, Input, Observable, AfterContentInit } from angular2/angular2';
import { Http } from 'angular2/http';
//needed to use socket.io! io is globally known by the browser!
declare var io:any;
#Component({
selector: 'my-weather-cmp',
template: `...`
})
export class WeatherComp implements AfterContentInit{
//the socket.io connection
public weather:any;
//the temperature stream as Observable
public temperature:Observable<number>;
//#Input() isn't set yet
constructor(public http: Http) {
const BASE_URL = 'ws://'+location.hostname+':'+location.port;
this.weather = io(BASE_URL+'/weather');
//log any messages from the message event of socket.io
this.weather.on('message', (data:any) =>{
console.log(data);
});
}
//#Input() is set now!
ngAfterContentInit():void {
//add Observable
this.temperature = Observable.fromEvent(this.weather, this.city);
}
}

Referencing in a simple way using AngularJS and TypeScript

I'm using VS2015 with Gulp and I'm trying to build AngularJS with TypeScript.
In my index.html, I have a script tag to my "app.js" (the output and bundle of the TS build).
However if I want to reference my controller, and any other services - how can I avoid having to put the relevant script tags in each and every HTML file - is there a way I can just reference the app.js file and be done with it? What's the recommended approach?
Cheers,
if you want to refrence only one file in html via script tag and other files just reference in other js files then you can use requirejs. It is a nice tool to load scripts. in production it is a good approach to concat and minify all your scripts to reduce number of requests.
I managed to resolve it using experimental typescript decorators, requirejs and a little of imagination.
So, in resume I wrote two decorators one called AppStartup and other called DependsOn. So at angular bootstrap I can get and register all dependencies.
I ended up with something like it:
TodoListController.ts
import {DependsOn, AppStartup, ngControllerBase} from "../infra/core";
import {taskManagerDirective} from "../directives/TaskManagerDirective";
#AppStartup({
Name: "TodoSample"
}).Controller({
DependsOn: [taskManagerDirective]
})
export class TodoListController extends ngControllerBase {
public ByControllerAs: string;
constructor() {
super(arguments);
let $httpPromise = this.$get<ng.IHttpService>("$http");
$httpPromise.then(function ($http) {
$http.get('http://www.google.com').success(function (body) {
console.info("google.com downloaded, source code: ", body);
});
});
this.ByControllerAs = "This was included by controllerAs 'this'";
}
}
rowListItemDirective.ts
import {DependsOn, ngDirectiveBase} from "../infra/core";
import {rowListItemDirective} from "./RowListItemDirective";
#DependsOn([rowListItemDirective])
export class taskManagerDirective extends ngDirectiveBase{
public template: string = `
Parent Directive
<table>
<tbody>
<tr row-list-item-directive></tr>
</tbody>
</table>
`;
}
You can see what I did below at my github repository:
https://github.com/klaygomes/angular-typescript-jasmine-seed

How can I export 2 items from a Typescript Module

Scenario
I'm tasked with creating a Knockout Components based UI using Typescript.
This is something I've done hundreds of times in vanilla JS, but I just can't seem to get TS to generate a JS module in the correct format to be consumed by Require JS.
Ideally, what I'd like is for Typescript to generate identical output to that written in JS, but right now I'd just like to get things working.
The Javascript
This is the Javascript that I'm trying to get TS to generate, this is Javascript from a DIFFERENT project that does not use TS, and in that project when the JS is in this format, everything works fine.
define(["knockout", "text!./menubar.html"], function (ko, menubarTemplate)
{
function menubarViewModel()
{
var self = this;
self.menuBrand = ko.observable("Menu Brand");
self.menuItems = ko.observableArray([]);
self.load();
return self;
}
menubarViewModel.prototype.load = function ()
{
var self = this;
$.getJSON("data/menudata.json", function (data)
{
self.menuItems(data);
});
};
return { viewModel: menubarViewModel, template: menubarTemplate };
});
In my actual JS file that uses the component all I need to do is:
define(["jquery", "knockout", "bootstrap"], function ($, ko)
{
ko.components.register("menubar",
{
require: "application/components/menubar"
});
ko.applyBindings();
});
The HTML for the menubar component is just a simple chunk of plain HTML markup sprinkled with "data-bind" attributes where needed to inject the data into the component.
As I say, this JavaScript version works perfectly, but the client I'm working for at the moment wants this in Typescript, so the first challenge I need to tackle is how to return
return { viewModel: menubarViewModel, template: menubarTemplate };
from a typescript module.
Typescript so far
Iv'e had a small amount of success, for instance if I do:
import ko = require("knockout");
module HelloComponent {
export class HelloViewModel {
helloText = ko.observable<string>("Hello World");
}
}
That produces a JS class, that ko tries to load, but complains that it has no template.
This says to me that if I can take the TS class above and export the require text HTML from the same class, then I might just make this work.
If I further expand that class as follows:
import ko = require("knockout");
import helloTemplate = require("text!application/components/hello.html");
module HelloComponent {
export class HelloViewModel {
helloText = ko.observable<string>("Hello World");
}
var tmp = helloTemplate;
}
I've been trying to solve this for a couple of days now, and most of the experimentation I've tried has either failed, or appears to run in the chrome debugger, but produces no output in the component.
There are dozens of posts here on SO, but none of them apply to Knockout Components, all the others apply to page level standard binding, which is different from KO components, it's the same scenario with the various blog posts I've been reading.
If anyone has an insight on how to implement this as per the advice in the KnockoutJS docs but using TS rather than JS, then I'd love to hear your ideas.
Update 12-08-2015 (Based on James Reply)
After changing one of my components to match 'James Brantly' s answer, I now see the following in Visual Studio:
Update 13-08-2015 (Post testing James Reply)
Even with the errors shown above, I've now put together several components, all using the same methodology, and everything works perfectly.
Visual studio still flags these files as having errors, but it still allows me to compile the project, and Typescript still does what it needs to and compiles to JavaScript.
At this point, am marking the question as answered, as the initial question has been solved.
I think the key to your question is that you want the module to return something like { viewModel: menubarViewModel, template: menubarTemplate };. You do that like this:
import menubarTemplate = require('text!./menubar.html');
class MenubarViewModel{
}
export = {
viewModel: MenubarViewModel,
template: menubarTemplate
}
In the code :
import ko = require("knockout");
module HelloComponent {
You are mixing internal modules (the module keyword now called namespaces) and file based modules (the import keyword).
DON'T
Use External modules only:
import ko = require("knockout");
export class HelloViewModel {
helloText = ko.observable<string>("Hello World");
}
More : https://www.youtube.com/watch?v=KDrWLMUY0R0&hd=1

Categories

Resources