passing Parameter from ASP-Page to Angularjs-module - asp.net-mvc

In an ASP Page I have a AngularJS modules directly applied. The ASP-page get from the server a parameter, that is to be used in the module. Like this (foo is the parameter):
<script>
var myApp = angular.module('myApp', []);
myApp.controller('myController', function($scope ) {
$scope.foo = #Model.foo;
...
Now I would like to outsource the module in a separate JS file.
How can I inject the parameter into the module then?

Just define parameters in global scope, and use it in angular app.
Index.cshtml:
<!-- this shoud be initialized before angular appplication -->
<script type='text/javascript'>
window.backendParams = {
foo: #Model.foo ,
bar: #Model.bar
}
</script>
app.js:
myApp.controller('myController', function($scope ) {
$scope.foo = window.backendParams.foo;
...

Related

Vue3 with ASP.NET MVC: Props not being passed in

I have an ASP.NET MVC application which I've inherited. The most recent new bits of functionality use custom Vue2 components. Each MVC view is effectively a Single Page Application as I understand it. Each view has its own index.js file e.g.
import Vue from "vue"
window.Vue = Vue
import Test from '#/components/test'
const vm = new Vue({
el: "#test",
components: {
Test
},
data() {
return {}
}
})
The top level component is a single file component e.g. Test.vue:
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
message: {
type: String,
default: 'This has not worked'
}
}
}
</script>
The MVC view itself looks like:
#{
ViewBag.Title = "Test";
Layout = "~/Views/Shared/_MentorNet.cshtml";
}
<div id="test" v-cloak>
<test message="This has worked"></test>
</div>
#section scripts
{
<script src="#Url.Content("~/dist/js/test.js")" defer></script>
}
The /dist/js/test.js file is created as a result of running Webpack with vue-loader.
With Vue2 this works fine. In this example the text "This has worked" appears when the controller returns the view.
However I'm trying to upgrade to Vue3 and this isn't working. The index.js file now looks like:
import { createApp } from "vue"
import Test from '#/views/admin/test'
const app = createApp(Test);
app.mount("#test");
The component and the view are the same. The component is displayed but the props are not passed through - the view returns the text "This has not worked".
I've looked at the Vue2 migration guide and can't see any breaking changes that would affect this. Can anyone help please?
I've got the answer thanks to a response on the Vue Land Discord channel.
The problem was with the code in the index.js file:
const app = createApp(Test);
app.mount("#test");
When passing a component with a template/render function to createApp it completely overwrites the content of the mount target, in this case
<div id="test">
<test message="This has worked"></test>
</div>
so the
<test message="This has worked"></test>
isn't used. The correct way to do it is either to pass the props as the second argument of create App:
const app = createApp(Test, {
message: "This has worked"
});
or, and the way I'm going to be doing it:
const app = createApp({
components: { Test }
});

angular directive compile order

I was trying to write a simple directive to generate a (potentially) more complex dom element. I am quite confused about what is going on here but I think the directive I use inside my directive get linked first? Anyway the element I am generating is not visible where it should.
Sorry for all that confusion, here is the plunkr:
http://plnkr.co/edit/vWxTmA1tQ2rz6Z9dJyU9?p=preview
I think the directive I use inside my directive get linked first?
Yes. A child directive's link function will execute before the parent's link function.
Here is a fiddle that shows two nested directives,
<div d1>
<div d2></div>
</div>
and it logs when the directives' controller and link functions are called.
There are a few issues with your Plunker:
Since you are using # for your isolate scopes, you need to use {{}}s in your attribute values:
<visible value='{{visible}}'>plop</visible>
<invisible value='{{visible}}'>plop</invisible>
Since $scope.visible is defined in your controller, I assume you meant to use that value, and not test.
In the invisible directive, you need to use isolate scope property value in your link function. Property visible is available to the transcluded scope (which is in affect if you use a template in your directive like #Langdon has) but not the isolate scope, which is what the link function sees.
var template = "<span ng-show='value'>{{value}}</span>";
Plunker.
If you want a simple directive, you're better off letting Angular do most of the work through ngTransclude, and $watch.
http://plnkr.co/edit/xYTNIUKYuHWhTrK80qKJ?p=preview
HTML:
<!doctype html>
<html ng-app="app">
<head>
<meta charset="utf-8">
<title>trying to compile stuff</title>
<script src="http://code.angularjs.org/1.1.1/angular.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="AppCtrl">
<input type="checkbox" ng-model="test" id="test" /><label for="test">Visibility (currently {{test}})</label>
<br />
<br />
<visible value='test'>visible tag</visible>
<invisible value='test'>invisible tag</invisible>
</div>
</body>
</html>
JavaScript:
angular
.module('app', [])
.controller('AppCtrl', function($scope) {
$scope.test = false;
})
.directive('visible', function() {
return {
restrict: 'E',
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
value: '='
},
link: function(scope, element, attrs) {
console.log(attrs);
scope.$watch('value', function (value) {
element.css('display', value ? '' : 'none');
});
console.log(attrs.value);
}
};
})
.directive('invisible', function() {
return {
restrict: 'E',
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
value: '='
},
link: function(scope, element, attrs) {
scope.$watch('value', function (value) {
element.css('display', value ? 'none' : '');
});
}
};
});

How can I have an AngularJS module routes use its own controllers?

I'm trying to learn AngularJS' view and routing mechanism, following AngularJS' own tutorial.
My problem is the tutorial is declaring all its controllers in the global scope, and I belive this is a bad practice because we're polluting it as we add more controllers.
This is a quick working page I've been able to build following the aforementioned tutorial (there's a fiddle, too):
<!doctype html>
<html>
<head>
<title>Test</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js"></script>
<script>
"use strict";
var MyFirstController = function ($scope) {
// Do something here.
};
var MySecondController = function ($scope) {
// Do something here.
};
var myModule = angular.module("MyModule", []);
myModule.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/first-page", {
template: "<p>My first controller.</p>",
controller: MyFirstController
});
$routeProvider.when("/second-page", {
template: "<p>My second controller.</p>",
controller: MySecondController
});
}]);
$(document).ready(function () {
angular.bootstrap(document, ["MyModule"]);
});
</script>
</head>
<body>
<h1>Test</h1>
<div data-ng-view></div>
<p>Click me!</p>
<p>Click me too!</p>
</body>
</html>
Being naïve, I tried to move the controllers inside the module:
myModule.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/first-page", {
template: "<p>My first controller.</p>",
controller: MyFirstController
});
$routeProvider.when("/second-page", {
template: "<p>My second controller.</p>",
controller: MySecondController
});
}]);
myModule.controller("MyFirstController", ["$scope", function ($scope) {
// Do something here.
}]);
myModule.controller("MySecondController", ["$scope", function ($scope) {
// Do something here.
}]);
Alas, it doesn't (obviously) work, throwing a ReferenceError: MyFirstController is not defined exception.
How can I have an AngularJS module use its own controllers in its own routes configuration?
Once you know the solution, it's really simple: just specify the controller as strings instead of objects:
myModule.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/first-page", {
template: "<p>My first controller.</p>",
controller: "MyFirstController"
});
$routeProvider.when("/second-page", {
template: "<p>My second controller.</p>",
controller: "MySecondController"
});
}]);
This way AngularJS will resolve the controller name to the one you've defined inside the module.
And it's minification safe too!
I've created a fiddle demonstrating it.

Underscore.js template with Backbone.js error

The line below is completely failing.
template: _.template($('#test').html()),
Trying to follow a simple example from https://github.com/ccoenraets/backbone-jquerymobile to use jQuery mobile along with Backbone.js. The error I'm getting in the web inspector is: TypeError: 'null' is not an object (evaluating 'str.replace') which is in line 913 of the underscore.js. Using is _.template in this fashion:
template: _.template("<h1>To Do</h1>"),
works, but in order to incorporate the jQuery mobile styles, that way won't do.
todo.js
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template($('#test').html()),
render: function() {
$(this.el).html(this.template());
return this;
}
});
main.html
<script type = 'text/template' id = 'test'> <h1>To Do</h1> </script>
The DOM isn't ready when your view is being parsed:
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template($('#test').html()),
//...
});
so $('#test').html() is null and you're actually doing this:
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template(null),
//...
});
The internals of _.template use replace while converting the template to a JavaScript function.
You have a few options:
Put the TodoBd.Views.ComCentersTodo definition inside a $(document).ready() handler:
$(function() {
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template($('#test').html()),
//...
});
});
Don't compile the template until you need it:
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
//... no template in here
render: function() {
var html = _.template($('#test').html(), this.model.toJSON());
this.$el.html(html);
return this;
},
//...
});
As a variation of 2, you could cache the compiled template functions somewhere and only call _.template($('#test').html()) the first time you use it.

How do I write unencoded Json to my View using Razor?

I'm trying to write an object as JSON to my Asp.Net MVC View using Razor, like so:
<script type="text/javascript">
var potentialAttendees = #Json.Encode(Model.PotentialAttendees);
</script>
The problem is that in the output the JSON is encoded, and my browser doesn't like it. For example:
<script type="text/javascript">
var potentialAttendees = [{"Name":"Samuel Jack"},];
</script>
How do I get Razor to emit unencoded JSON?
You do:
#Html.Raw(Json.Encode(Model.PotentialAttendees))
In releases earlier than Beta 2 you did it like:
#(new HtmlString(Json.Encode(Model.PotentialAttendees)))
Newtonsoft's JsonConvert.SerializeObject does not behave the same as Json.Encode and doing what #david-k-egghead suggests opens you up to XSS attacks.
Drop this code into a Razor view to see that using Json.Encode is safe, and that Newtonsoft can be made safe in the JavaScript context but is not without some extra work.
<script>
var jsonEncodePotentialAttendees = #Html.Raw(Json.Encode(
new[] { new { Name = "Samuel Jack</script><script>alert('jsonEncodePotentialAttendees failed XSS test')</script>" } }
));
alert('jsonEncodePotentialAttendees passed XSS test: ' + jsonEncodePotentialAttendees[0].Name);
</script>
<script>
var safeNewtonsoftPotentialAttendees = JSON.parse(#Html.Raw(HttpUtility.JavaScriptStringEncode(JsonConvert.SerializeObject(
new[] { new { Name = "Samuel Jack</script><script>alert('safeNewtonsoftPotentialAttendees failed XSS test')</script>" } }), addDoubleQuotes: true)));
alert('safeNewtonsoftPotentialAttendees passed XSS test: ' + safeNewtonsoftPotentialAttendees[0].Name);
</script>
<script>
var unsafeNewtonsoftPotentialAttendees = #Html.Raw(JsonConvert.SerializeObject(
new[] { new { Name = "Samuel Jack</script><script>alert('unsafeNewtonsoftPotentialAttendees failed XSS test')</script>" } }));
alert('unsafeNewtonsoftPotentialAttendees passed XSS test: ' + unsafeNewtonsoftPotentialAttendees[0].Name);
</script>
See also:
Does the output of JsonConvert.SerializeObject need to be encoded in Razor view?
XSS Prevention Rules
Using Newtonsoft
<script type="text/jscript">
var potentialAttendees = #(Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.PotentialAttendees)))
</script>

Resources