updating render when ng-include changes angular - javascript

I am new to Angular - my issue - when I call $scope.editProfile() the value of panels.left is changed in the factory but the content on the page does not change. In the html file I am using ng-include like this:
<div ng-include src="leftPanel"></div>
When first loading the page views/profile.html is rendered in the div. But later when I call $scope.editProfile() I want the div to refresh with the content of views/edit.html - the value does change in the factory (console.log proved it) but the page content does not update.
myApp.factory("panels", function () {
return {
left: "views/profile.html",
center: "views/msgs.html",
right: "views/friends.html"
};
});
myApp.controller("homeCtrl", function ($scope, api, panels) {
$scope.leftPanel = panels.left;
$scope.centerPanel = panels.center;
$scope.rightPanel = panels.right;
});
myApp.controller("profileCtrl", function ($scope, api, panels) {
$scope.user = api.bio.get();
$scope.editProfile = function () {
panels.left = "views/edit.html";
};
});

You would be better off using named views in ui-router its a much better way to deal with routing and states than what you have right now.
However if you want your current logic to work you need to wrap a factory assignment in a function in your homeCtrl.
i.e,:
$scope.leftPanel = function() { return panel.left; }

Related

Struggling to pass a parameter as an event in an Angular 1.6 component

I have been tasked with developing an app in Angular 1.6, and I have not done any Angular 1.x for quite some time. Been mostly doing 2.x. In fact, never done 1.6 at all.
I have two components: a tile container, and a tile. The tiles are selectable, so I imagined having a tile container component that would keep track of which tile was selected, and a tile component which is primarily a UI component.
My tile component looks like this:
// dp-claim-filter-tile.component.js
function ClaimFilterTileController() {
var ctrl = this;
ctrl.isDisabled = () => {
return ctrl.claimCount == 0;
}
ctrl.test = function () {
console.log('ctrl = ' + JSON.stringify(ctrl));
ctrl.onTileClicked(ctrl);
}
}
angular.module('dpApp').component('dpClaimFilterTile', {
templateUrl: '/templates/dp-claim-filter-tile.tmpl.html',
controller: ClaimFilterTileController,
bindings: {
tileTitle: '#',
claimCount: '#',
isAdd: '<',
isActive: '<',
onTileClicked: '&'
}
});
And the template looks like this:
// dp-claim-filter-tile.tmpl.html
<div ng-if="$ctrl.isAdd===true" layout="column" layout-align="space-around center">
<div><h2 class="md-title">{{$ctrl.tileTitle}}</h2></div>
<div>
<md-icon md-font-icon="fa-plus" class="fa fa-2x"></md-icon>
</div>
</div>
<div ng-if="$ctrl.isAdd!==true" layout="column" layout-align="space-around center" ng-click="$ctrl.test()">
<div><h2 class="md-title tile-text">{{$ctrl.tileTitle}}</h2></div>
<div class="md-display-1 tile-text">{{$ctrl.claimCount}}</div>
</div>
The tile container component looks like this:
// dp-claim-filter-tiles.component.js
function ClaimFilterTilesController() {
var ctrl = this;
ctrl.tileClicked = function(tile) {
console.log('tile = ' + JSON.stringify(tile));
}
}
angular.module('dpApp').component('dpClaimFilterTiles', {
templateUrl: '/templates/dp-claim-filter-tiles.tmpl.html',
controller: ClaimFilterTilesController
});
And an extract of the container UI looks like this:
// dp-claim-filter-tiles.tmpl.html
<md-grid-tile>
<dp-claim-filter-tile is-active="true" tile-title="Links Sent" claim-count="7" on-tile-clicked="$ctrl.tileClicked($event)"></dp-claim-filter-tile>
</md-grid-tile>
What I am expecting, and hoping for, is for the $event parameter I am supplying to surface as a parameter to the ClaimFilterTilesController.tileClicked() function.
How do I accomplish this?
The click $event will be available in the ngClick directive by itself, you won't be able to carry it around before the actual ngClick directive gets triggered.
Therefore, you could access the click event exclusively in dp-claim-filter-tile.tmpl.html template, like this:
ng-click="$ctrl.test($event, otherParam)"
Try utilizing ng-click rather than the on-tile-clicked. This way you can get at the $event object which can be passed into other functions within your component definition.
<md-grid-tile>
<dp-claim-filter-tile-is-active="true" tile-title="Links Sent" claim-count="7" ng-click="$ctrl.tileClicked($event)"></dp-claim-filter-tile>
</md-grid-tile>
I found the answer on the AngularJS site: Intercomponent Communication. The trick lies in using the require property when defining the component. You can essentially create a reference to the enclosing component. Shall implement tomorrow.
Thanks for your answers guys.

Access Global javascript variables inside AngularJS

I'm working on a site, and I started building it before I realized I needed some dynamic framework. After learning about AngularJS, I decided to use it, where I needed (not the whole site).
I have a very long script in JS, and I want to be able to get and set the variables from within AngularJS directives and controllers.
I found this answer, and it was quite good - I was able to get the variable from within the function. But when the variable changed outside the function, AngularJS' variable won't update.
My code looked something like this:
JS:
var app = angular.module('someName', []);
var currentPage = 'Menu';
app.controller('PageController', ['$window','$scope', function($window,$scope){
this.currentPage = $window.currentPage;
this.isPage = function(page){
return (page == this.currentPage);
};
}]);
function button1onClick(){
currentPage = 'Game';
}
HTML:
<div ng-controller="PageController">
<div id="Game" ng-show="page.isPage('Game')">
...
</div>
<div id="Menu" ng-show="page.isPage('Menu')">
...
</div>
</div>
(button1onClick was called when I clicked some button on the page)
The idea is that I have two dives I want to switch between, using a globle variable. 'Menu' page was visible at first but upon clicking I was supposed to see only the 'Game' div.
The variable inside the controller didn't upadte, but was only given the initial value of currentPage.
I decided to use the $window service inside the isPage function, but this didn't work either. Only when I called a function that tested the $window.currentPage variable, the pages switched - like I wanted:
JS:
var app = angular.module('someName', []);
var currentPage = 'Menu';
app.controller('PageController', ['$window','$scope', function($window,$scope){
this.isPage = function(page){
return (page == $window.currentPage);
};
this.button2onClick = function() {
$window.alert($window.currentPage);
}
}]);
function button1onClick(){
currentPage = 'Game';
}
HTML:
<button onclick="button1onClick()">CLICK ME</button> //Button 1
<div ng-controller="PageController">
<button ng-click="page.button2onClick">CLICK ME</button> //Button 2
<div id="Game" ng-show="page.isPage('Game')">
...
</div>
<div id="Menu" ng-show="page.isPage('Menu')">
...
</div>
</div>
So the only way I was able to update the pages is to call a function that tests the variable, thus updating the variable in AngularJS.
Is there a way to access a global variable without needing to test it to update it?
Am I doing something wrong? I don't want to convert my whole site to AngularJS-style, I like the code the way it is. Is AngularJS not the framework for me?
EDIT:
some things to clear out:
I'm new to AngularJS, so if you could explain what your answer does it would be great.
The whole reason why I do this instead of redirecting to another page is not to shut down socket.io 's connection
OP, take a look at UI Router.
var app = angular.module("app", ['ui.router']);
app.config(['$urlRouterProvider', '$stateProvider', function($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/main');
$stateProvider.state('main', {
controller: 'MainCtrl',
templateUrl: 'main.html',
url: '/main/'
}).state('game', {
controller: 'GameCtrl',
url: '/game/',
templateUrl: 'game.html'
});
}]);
HTML links:
<a ui-sref="main">Go to Main</a>
<a ui-sref="game">Go to Game</a>
View injection
<div ui-view="">
</div>
You should not use $window as a map object.
You should probably create a PageService:
angular.module('someName')
.factory('Page', [function(){
var currentPage = 'Menu';
return {
getPage: function() {
return currentPage;
},
isPage: function(page) {
return page === currentPage;
},
setPage: function(page) {
currentPage = page;
}
}
}]);
app.controller('PageController', ['Page','$scope', function(Page,$scope){
this.currentPage = Page.getPage();
this.isPage = Page.isPage;
this.button10Click = function(){
Page.setPage('Game');
}
}]);
HTML
<div class="button" ng-click="page.button10Click()">Game</div>
After reading malix's answer and KKKKKKKK's answer, and after researching a bit, I was able to solve my problem, and even write a better looking code.
To switch divs as I wanted in the example, I used ui-router, almost exactly the way KKKKKKKK did. The only difference is that I change state programmaticly - $state.go('menu')
To access global variables in other places in my code, I had to re-structure my whole code to fit AngularJS's structure, and used a Service, similarly to malix's answer:
app.factory('Data', function(){
var Data = {};
//DEFINE DATA
Data.stateChange = function(){};
Data.menuData = {};
Data.randomColors = ['#35cd96', '#6bcbef', '#E8E1DA', '#91ab01'];
/RETURN DATA
return Data;
});
It can be done using $rootScope. Variable initialize once can be accessible in other controllers.
function Ctrl1($scope, $rootScope) {
$rootScope.GlobalJSVariableA= window.GlobalJSVariable; }
Any controller & any view can access it now
function CtrlN($scope, $rootScope) {
$scope.GlobalJSVariableA= $rootScope.GlobalJSVariableA;
}

How to setup a service from a base module to call a function on a service in a module that uses the base module in angularjs

I an writing a service that is to be used in multiple independent websites. However, at some points it needs to trigger different code depending on what website it is used in. I want to keep this per website code separate from the base service.
Here is some example code demonstrating the design I want (although it isn't working):
var baseModule = angular.module('baseModule', []);
baseModule.service('baseService', function() {
this.func = function() {
return ["first",
/* TODO somehow get from appropriate
service in website module */
"FIXME",
"end"];
};
});
var website1 = angular.module('website1', ['baseModule']);
website1.service('website1Service', function() {
this.someCustomValue = function() {
// Note that while this is a constant value, in
// the real app it will be more complex,
// so replacing this service with a constant provider won't work.
return "someValue";
}
});
// TODO : somehow link website1Service.someCustomValue to baseService
var website2 = angular.module('website2', ['baseModule']);
website2.service('website2Service', function() {
this.anotherValue = function() { return "anotherValue"; }
});
// TODO : somehow link website2Service.anotherValue to baseService
// Testing code:
function makeTestController(expected) {
return ['$scope', 'baseService', function($scope, baseService) {
var result = baseService.func();
if (angular.equals(result, expected)) {
$scope.outcome = "Test Passed!";
} else {
$scope.outcome = 'Test failed...\n' +
"Expected: " + angular.toJson(expected) + '\n' +
"But got : " + angular.toJson(result);
}
}];
}
website1.controller('TestController1',
makeTestController(['first', 'someValue', 'end']));
website2.controller('TestController2',
makeTestController(['first', 'anotherValue', 'end']));
// since this test uses multiple angular apps, bootstrap them manually.
angular.bootstrap(document.getElementById('website1'), ['website1']);
angular.bootstrap(document.getElementById('website2'), ['website2']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h3>Website 1</h3>
<div id='website1'>
<div ng-controller='TestController1'>
<pre>{{outcome}}</pre>
</div>
</div>
<div id='website2'>
<h3>Website 2</h3>
<div ng-controller='TestController2'>
<pre>{{outcome}}</pre>
</div>
</div>
I've thought of a few solutions to this, but none seem optimal.
The most obvious way would be to replace the baseService service with a provider, and allow it to be configured in each module. This seems to be the standard way of configuring services in other modules. However, I cannot access the website1Service and website2Service in in the provider functions, as services cannot be accessed in provider functions. This is noted in the docs:
During application bootstrap, before Angular goes off creating all services, it configures and instantiates all providers. We call this the configuration phase of the application life-cycle. During this phase, services aren't accessible because they haven't been created yet.
Another solution to work around this is use angular.injector to find the right service. However, the docs for angular.injector imply that you really only need this for interacting with third party libraries. So it appears there is a better way.
Finally, I could add a dependency to a nonexistant service (eg "baseServiceActions") in baseModule, and require a service with that name be implemented in website1 and website2. The dependency injection should then bind it all together when baseService is used. However, this is a pretty weird way of working, and would result in poor error messages if the baseServiceActions module wasn't implemented in a new website that used the baseModule module.
Is there a better way of doing this? If so, is it possible to change the example code I posted to get all the tests passing? Ideally none of the testing code should be changed.
I eventually worked out a fairly good solution to this. I created a service named "<serviceName>Settings", and added a setup function to it. I then call that setup function in a module run block in the module I want to use it in. Finally I have a validate method that is called in the service that uses the settings to ensure it is setup, and throws a nice error message if it isn't. This solved all the problems I had with the other solutions.
This is how my example problem would look with this solution:
var baseModule = angular.module('baseModule', []);
baseModule.service('baseService', ['baseServiceSettings', function(baseServiceSettings) {
baseServiceSettings.validate();
this.func = function() {
return ["first",
baseServiceSettings.getValue(),
"end"];
};
}]);
baseModule.service('baseServiceSettings', function() {
this.setup = function(getter) {
this.getValue = getter;
};
this.validate = function() {
if (!this.getValue) {
throw "baseServiceSettings not setup! Run baseServiceSettings.setup in a module run block to fix";
}
};
});
var website1 = angular.module('website1', ['baseModule']);
website1.run(['baseServiceSettings', 'website1Service', function(baseServiceSettings, website1Service) {
baseServiceSettings.setup(website1Service.someCustomValue);
}]);
website1.service('website1Service', function() {
this.someCustomValue = function() {
// Note that while this is a constant value, in
// the real app it will be more complex,
// so replacing this service with a constant provider won't work.
return "someValue";
}
});
var website2 = angular.module('website2', ['baseModule']);
website2.service('website2Service', function() {
this.anotherValue = function() { return "anotherValue"; }
});
website2.run(['baseServiceSettings', 'website2Service', function(baseServiceSettings, website2Service) {
baseServiceSettings.setup(website2Service.anotherValue);
}]);
// Testing code:
function makeTestController(expected) {
return ['$scope', 'baseService', function($scope, baseService) {
var result = baseService.func();
if (angular.equals(result, expected)) {
$scope.outcome = "Test Passed!";
} else {
$scope.outcome = 'Test failed...\n' +
"Expected: " + angular.toJson(expected) + '\n' +
"But got : " + angular.toJson(result);
}
}];
}
website1.controller('TestController1',
makeTestController(['first', 'someValue', 'end']));
website2.controller('TestController2',
makeTestController(['first', 'anotherValue', 'end']));
// since this test uses multiple angular apps, bootstrap them manually.
angular.bootstrap(document.getElementById('website1'), ['website1']);
angular.bootstrap(document.getElementById('website2'), ['website2']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h3>Website 1</h3>
<div id='website1'>
<div ng-controller='TestController1'>
<pre>{{outcome}}</pre>
</div>
</div>
<div id='website2'>
<h3>Website 2</h3>
<div ng-controller='TestController2'>
<pre>{{outcome}}</pre>
</div>
</div>

Is it possible to reference Angular custom directive inside itself?

Looking to implement folder hierarchy in Angular:
I'm implementing this via custom directive that references itself inside its template.
Currently it's going into infinite loop with this setup:
<!-- index.html -->
<subfolders folder="default_folder"></subfolders>
This is the <subfolders> directive:
//subfoldersDirective.js
angular.module('app').directive('subfolders', subfolders);
function subfolders() {
var directive = {
restrict: 'AE',
scope: {
folder: '=',
},
templateUrl: '/pathto/subfoldersDirective.html',
controller: DirCtrl,
controllerAs: 'vm'
};
return directive;
function DirCtrl($scope) {
var vm = this;
vm.folder = $scope.folder;
}
}
and its template:
<!-- folderDirective.html -->
<div ng-repeat="folder in vm.folder.subfolders">
{{ folder.name }}
<button ng-click="folder.is_open = true">Open folder</button>
<div ng-if="folder.is_open">
<!-- This is the problem line -->
<subfolders folder="folder"></subfolders>
</div>
</div>
In the template, <subfolders> should only get rendered after the button is clicked which triggers ng-if="folder.is_open". I guess Angular does not know this when it compiles the directive. It goes into infinite loop, even though it technically should not.
Is there a way to make it work with the directive? The logic is a bit more complex in the real app, this is why I'm looking to make it work with the directive.
I'm currently using Angular 1.2.26.
You can do this, but you need to override the compile behavior of the directive. You need to remove contents of the directive element during the compilation step, and then compile and reattach it during the post-link step. I have used the compile function below with great success:
function compile(element) {
var contents = element.contents().remove();
var contentsLinker;
return function (scope, iElement) {
if (angular.isUndefined(contentsLinker)) {
contentsLinker = $compile(contents);
}
contentsLinker(scope, function (clonedElement) {
iElement.append(clonedElement);
});
};
}
I based this function on this post, which is probably more comprehensive if you need a pre-link function.

Trying to show a image from Angularjs controller

We've decided to use Mean.io in order to get a quick MEAN stack installation, but we're finding some troubles to get things done.
I'm trying to show a picture from my header js controller. But it doesn't show up. In fact, what i see when i open the inspector is just:
<img ng-src>
This is my HTML code, located in header.html:
<div class="page-header" data-ng-controller="HeaderController">
<div class="logo pull-left">
<a class="navbar-brand" ui-sref="home"><img ng-src="{{image.logo}}"/></a>
</div>
</div>
As you may see, i've put "ng-src" and the var taken from the js controller.
This is the HeaderController:
'use strict';
angular.module('mean.system').controller('HeaderController', ['$scope', '$rootScope', 'Global', 'Menus',
function($scope, $rootScope, Global, Menus) {
$scope.global = Global;
$scope.menus = {};
$scope.image = {
logo: 'assets/img/logo.png'
};
// Default hard coded menu items for main menu
var defaultMainMenu = [];
// Query menus added by modules. Only returns menus that user is allowed to see.
function queryMenu(name, defaultMenu) {
Menus.query({
name: name,
defaultMenu: defaultMenu
}, function(menu) {
$scope.menus[name] = menu;
});
}
// Query server for menus and check permissions
queryMenu('main', defaultMainMenu);
$scope.isCollapsed = false;
$rootScope.$on('loggedin', function() {
queryMenu('main', defaultMainMenu);
$scope.global = {
authenticated: !! $rootScope.user,
user: $rootScope.user
};
});
}
]);
The template var is working correctly because if i put {{image.logo}} elsewhere it prints "assets/img/logo.png".
Suggestions? What am i missing?
Thanks in advance!
I had the similar problem, I did the same as you have done and was getting 404 error for the PNG. When reviewed the other requests that are made, I came up with,
you must pass the "package name" in the parameter logo as well.
Just as;
$scope.image = {
logo: 'system/assets/img/logo.png'
};
Than the logo is displayed as desired

Categories

Resources