Change the view based on screen size - angularjs

I am aware of a similar question being asked before:
Change the templateUrl of directive based on screen resolution AngularJS
This was first asked over a year ago and since then AngularJS got changed a bit. I am curious to find out if there are any other ways to achieve something similar as I haven't found many information about templateUrl swapping, so maybe I am barking up the wrong tree here.
I have a single page app without any routes.
html:
<body ng-app="App">
// lots of html same for both desktop/mobile
<my-dir></my-dir>
// even more here
</body>
template 1:
<p>Mobile</p>
template 2:
<p>Desktop</p>
I would like to render template 1 when the screen goes below 700px and template 2 otherwise. The templates change just what is inside my-dir directive. For example Template 1 renders list and template 2 renders table.
Another requirement would be to make it responsive if possible(aka templates would change as you resize the window)
At the moment I can use the solution from the above questions but are there any other ways to do it?

In your controller:
$scope.includeDesktopTemplate = false;
$scope.includeMobileTemplate = false;
var screenWidth = $window.innerWidth;
if (screenWidth < 700){
$scope.includeMobileTemplate = true;
}else{
$scope.includeDesktopTemplate = true;
}
html template:
<body ng-app="App">
<p ng-if="includeMobileTemplate">Mobile</p>
<p ng-if="includeDesktopTemplate">Desktop</p>
</body>
Hope it helps

You can add window resize and scroll event listener on my-dir directive:
angular.module("App").directive('myDir', ['$window', '$timeout', function($window, $timeout){
return {
restrict: 'EA',
scope: {},
template:'<div>
<p ng-if="showFirstTemplate">Mobile</p>
<p ng-if="showSecondTemplate">Desktop</p>
</div>',
link: function(scope, element, attr){
function checkTemplateVisible(event){
//use $timeout to make sure $apply called in a time manner
$timeout(function(){
//pageYoffset is equal to window scroll top position
if($window.pageYOffset > 700){
scope.showFirstTemplate = true;
scope.showSecondTemplate = false;
}else{
scope.showFirstTemplate = false;
scope.showSecondTemplate = true;
}
})
})
//scroll event make sure checkTemplateVisible called on browser scrolling
$window.on('scroll', checkTemplateVisible)
//resize event make sure checkTemplateVisible called on browser resizing
$window.on('resize', checkTemplateVisible)
}
}
}])

Related

Angular - How to use a dynamic templateUrl for a directive?

So, for whatever reason, I am trying to create a slider, where the contents of each slide are different HTML templates. So instead of an image slider, you could say it's a HTML slider.
So in my HTML I just have this code, and the controls for the slider are also inside this HTML template:
<slide-template></slide-template>
And here is my entire slide module:
(function() {
'use strict';
angular
.module('slideCtrl', [])
.directive('slideTemplate', function() {
return {
restrict: 'E',
templateUrl: 'views/slides/slide-1.html',
replace: true,
controller: 'slideController',
controllerAs: 'slides'
}
})
.controller('slideController', function() {
var vm = this;
});
})();
I'm not sure how to move forward with this, I've tried looking around but haven't found anything that I felt I could use. Inside the controller, I would like to have an array of slide template URLs and a corresponding variable to indicate the current slide:
slideUrl = [ 'views/slides/slide-1.html', 'views/slides/slide-2.html'];
slideNum = 0;
Ideally, I would then like my directive to use these variables to determine what variable it will use for templateUrl. So by default, you can see that slideNum is 0, meaning that I want to use slide1.html or slideUrl[0]. So, my templateUrl would be something like slideUrl[slideNum]. Of course, this can't be done as the directive wouldn't be able to access that data, so I'm not sure how to do this.
The end result would be that if you clicked one of the slide navigation icons, you would be updating the slideNum variable, which would instantly change the templateUrl used.
I guess I am essentially wanting a slider which doesn't rely on some images or something like that for content, but instead is a slider of actual HTML content.
Any ideas? If I haven't explained myself well enough, please let me know.
hi I would solve it like this by creating a "main.html" template and in that:
//main.html
<div ng-if="slide == 1">
<ng-include src="'slide1.html'"/>
</div>
<div ng-if="slide == 2">
<ng-include src="'slide2.html'"/>
</div>
<div ng-if="slide == 3">
<ng-include src="'slide3.html'"/>
</div>
//controller
.controller('slideController', function() {
$scope.slide = 1
//logic to switch slides
});
for animations on the slide transitions take a look at this code pen
animations
I would suggest a main directive, where you would place the different slides on one page.
For instance, the main directive:
<div ng-include src="'slider0.html'" ng-if="slider%4==0"></div>
<div ng-include src="'slider1.html'" ng-if="slider%4==1"></div>
<div ng-include src="'slider2.html'" ng-if="slider%4==2"></div>
<div ng-include src="'slider3.html'" ng-if="slider%4==3"></div>
And then in the controller of the directive you set:
$scope.slider = 0;
// Some more logic like:
$scope.slider++;
You could move this to a link function and replace your compiled slide dynamically by adding them to the slideUrl array. This method is flexible enough to allow you to manage the slides in the controller, also you could potentially pass the slide urls to the directive through an scoped attribute.
.directive('slideTemplate', function($http, $compile, $templateCache) {
return {
restrict: 'E',
replace: true,
controller: 'slideController',
controllerAs: 'slides',
link : function(scope, el, attrs) {
// Bind active slide number to controller scope
scope.slides.num = 0;
// Declare slide urls
var slideUrl = [
'views/slides/slide-1.html',
'views/slides/slide-2.html'
];
// Load a slide and replace the directives inner html
// with the next slide.
function loadSlide(template) {
// Get the template, cache it and append to element
$http.get(template, { cache: $templateCache })
.success(function(content) {
el.replaceWith($compile(content)(scope));
}
);
}
// Progress to the next slide, this is bound to the
// controllers scope and can be called from there.
scope.slides.next = function() {
var next = scope.slides.num + 1;
var slide = slideUrl[next] ? next : slide;
scope.slides.num = slide;
loadSlide(slideUrl[slide]);
}
}
}
});

AngularJS scroll directive - How to prevent re-rendering whole scope

I have an AngularJS Application with a scroll directive implemented as the following:
http://jsfiddle.net/un6r4wts/
app = angular.module('myApp', []);
app.run(function ($rootScope) {
$rootScope.var1 = 'Var1';
$rootScope.var2 = function () { return Math.random(); };
});
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
scope.scrolled = true;
} else {
scope.scrolled = false;
}
scope.$apply();
});
};
});
The HTML looks the following:
<div ng-app="myApp" scroll ng-class="{scrolled:scrolled}">
<header></header>
<section>
<div class="vars">
{{var1}}<br/><br/>
{{var2()}}
</div>
</section>
</div>
I only want the class scrolled to be added to the div once the page is scrolled more than 100px. Which is working just fine, but I only want that to happen! I don't want the whole scope to be re-rendered. So the function var2() should not be executed while scrolling. Unfortunately it is though.
Is there any way to have angular only execute the function which is bound to the window element without re-rendering the whole scope, or am I misunderstanding here something fundamentally to AngularJS?
See this fiddle:
http://jsfiddle.net/un6r4wts/
Edit:
This seems to be a topic about a similar problem:
Angularjs scope.$apply in directive's on scroll listener
If you want to calculate an expression only once, you can prefix it with '::', which does exactly that. See it in docs under One-time binding:
https://docs.angularjs.org/guide/expression
Note, this requires angular 1.3+.
The reason that the expressions are calculated is because when you change a property value on your scope, then dirty check starts and evaluates all the watches for dirty check. When the view uses {{ }} on some scope variable, it creates a binding (which comes along with a watch).

AngularJS template switching

Our client wants a responsive website, but he wants to change and move so much content that we will run into bootstrap limitations.
With bootstrap you can show and hide blocks and move them around with offset, but somehow it has it's limitations. It is a demanding client that will not respect such limitations so we are looking for other options.
To avoid creating duplicate content and still have the ability to give the mobile/desktop experience our team came up with AngularJS.
Our JSON data and Angular controllers can stay the same, but we only need to switch views if it is on mobile/tablet/desktop.
Is there a good stable solution to get this working?
And can we test it like we test responsive design by resizing the browser, or is useragent detection the only solution?
That would be a pain during testing, since we need then many devices or emulators to test.
You can create a custom directive for this.
app.directive('responsiveTemplate', function() {
return {
restrict: 'E',
template: '<ng-include src="template"></ng-include>',
scope: true,
link: function(scope, elem, attr) {
var mobile = attr.mobile;
var desktop = attr.desktop;
scope.template = desktop;
$(window).resize(function() {
if (windowSizeIsDesktop() && scope.template != desktop) {
scope.template = desktop;
scope.$apply();
}
else if (windowSizeIsMobile() && scope.template != mobile) {
scope.template = mobile;
scope.$apply();
}
});
}
}
})
Use as an element
<responsive-template desktop="desktop.html" mobile="mobile.html"></responsive-template>
I have not defined the windowSize functions though they are trivial to implement
I'd probably just use ng-if for this, but I'd make sure you need it first and can't simply use css / media queries for what you're describing. Here's an example of the ng-if logic:
<body ng-app="myApp">
<div ng-controller="ctrl" >
<div ng-if="isWide()">
<p>Wide Content</p>
</div>
<div ng-if="!isWide()">
<p>Narrow Content</p>
</div>
</div>
</body>
And the js:
angular.module('myApp', []).controller('ctrl', function($scope, $window) {
$scope.isWide = function() {
return $window.innerWidth > 500; //your breakpoint here.
}
angular.element($window).on('resize', angular.bind($scope, $scope.$apply));
});
http://jsfiddle.net/gq2obdcq/8/
Just drag the split pane to see the results in the fiddle.
$routeProvider.
when('/:menu/:page', {
controller:HandleCtrl,
template:'<div ng-include="templateUrl">Loading...</div>'
}).
Combined with:
function HandleCtrl($scope, $routeParams){
$scope.templateUrl = $routeParams.menu +'/'+ $routeParams.page + '.html'
}
Would this be safe?
Inside the controller I can decide what html file I want to use as template

How to Capture Touch Events with Angular Directive

I would like to be able to capture the fact that a user moved their finger through a set of DOM elements on a touch device. This example works fine on a desktop browser but does not fire all the expected events when viewed in mobile Safari.
Working Plunkr (demonstrates the issue on mobile safari):
http://plnkr.co/edit/J8sfuJ9o6DorMSFlK9v2
HTML:
<body ng-controller="MainCtrl">
<p>Hello {{name}}! This works from a desktop browser but not from mobile Safari. I would simply like to be able to drag my finger down, across all four letters, and have their events fire. I thought that touchMove would work in place of mouseMove when running this Plunkr on iOS, but it doesn't.</p> Current letter: {{currentLetter}}
<div swipe-over="swipeOver()">A</div>
<div swipe-over="swipeOver()">B</div>
<div swipe-over="swipeOver()">C</div>
<div swipe-over="swipeOver()">D</div>
</body>
Javascript:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $log) {
$scope.name = 'World';
$scope.currentLetter = "";
$scope.swipeOver = function() {
$log.info("In swipeOver");
};
});
app.directive('swipeOver', function($log) {
return {
restrict: "A",
scope: true,
link: function(scope, element, attrs) {
// For touch devices
element.bind("touchmove", function() {
scope.$apply(function(evt) {
$log.info("in touchmove - " + element.text());
scope.$parent.currentLetter = element.text();
});
});
// For desktops
element.bind("mousemove", function(evt, e2) {
scope.$apply(function() {
$log.info(evt);
$log.info(e2);
$log.info("in mousemove - " + element.text());
scope.$parent.currentLetter = element.text();
});
});
}
};
});
I have tried the ng-touch library but it does not support vertical touch movements, amazingly. Any help would be massively appreciated at this point . . .
This is a limitation of touchmove currently, and I'm afraid there's no really good answer.
The best solution is to bind touchmove to a parent element and then calculate which child element is under the current touch point. It's answered in a jQuery context here Crossing over to new elements during touchmove.
You'll need to cycle through each of the child elements on the parent and check if the touch point is within their bounds.
Use Hammer.JS, it's the best I know. Angular-Hammer can be easily modified to support version 2.

Angular binding inside an inline ckeditor

I'm using inline editing with CKEditor, and I'd like to bind an element to an angular scope value.
<div contentEditable="true">
<p>Here is the value: {{testval}}</p>
</div>
testval should update in the same manner as it would outside the editor.
To protect this text in the editor, I'd like to do something similar to the placeholder plugin. In other words I plan to have a placeholder, dynamically displaying the final text rather than just the placeholder.
I've seen several examples of how to bind the entire contents with angular, but not individual elements. I'm still fairly new to both angular and ckeditor, so any help or pointers would be much appreciated.
It sounds to me like you will need to use a directive for what you want. I might be soewhat off because I'm not completely familiar, but goig by what you've provided, let's assume this example.
html
<body ng-app="myApp">
<div content-editable content="content"></div>
</body>
javascript
angular.module('myApp', [])
.directive('contentEditable', function() {
restrict: 'A',
replace: true,
scope: {
// Assume this will be html content being bound by the controller
// In the controller you would have:
// $scope.content = '<div>Hello World</div>'
content: "="
},
template: '<div contentEditable="true"><p>Here is the value {{ content }}</p></div>'
});
Still not sure if I completely comprehend, but let me know if I'm getting closer.
I assume that you want to bind the HTML text in model to the element. I used ng-bind-html to render what is in the model and I created the directive ck-inline to add the inline feature and bind the model to the changes that happen in the inline editor. This directive requires a ng-bind-html to work and you also need to have ngSanitize added to your module. Add directive ck-inline to your element and
I also use $timeout because I noticed that if I don't the text is rendered and then ckeditor somehow deletes all the values which messes up the model (this does not happen with the non-inline option). Here is the code.
yourModule.directive('ckInline', ['$sce', '$timeout', function($sce, $timeout){
return{
require : '?ngBindHtml',
scope:{value:"=ngBindHtml"},
link : function(scope, elm, attr, ngBindHtml)
{
$timeout(function()
{
var ck_inline;
elm.attr("contenteditable", "true");
CKEDITOR.disableAutoInline = true;
ck_inline = CKEDITOR.inline(elm[0]);
if (!attr.ngBindHtml)
return;
ck_inline.on('instanceReady', function()
{
ck_inline.setData(elm.html());
});
function updateHtml()
{
scope.$apply(function()
{
scope.value = $sce.trustAsHtml(ck_inline.getData());
});
}
ck_inline.on('blur', updateHtml);
ck_inline.on('dataReady', updateHtml);
});
}
};
}]);

Resources