Angularjs 1.5 - CRUD pages and Components - javascript

I'm working in a small Angularjs 1.5 project and I'm quite new.
I have to create an object or edit an existing object and the template is the same, the only difference in the logic is the call to an http service for data retrieve in case of update.
I use ngRoute.
How can I do to use the same component for both operations ?
EDIT
JS COMPONENT CODE
angular.
module('editProject').
component('editProject', {
templateUrl: 'app/edit-project/edit-project.template.html',
controller:['$http','$routeParams', function EditProjectController($http,$routeParams) {
var self=this;
$http.get('rest/projects/' + $routeParams.id ).then(function(response) {
console.log(JSON.stringify(response.data));
self.project = response.data;
});
}
]
});
ROUTE CONFIG
angular.
module('myApp').
config(['$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$locationProvider.hashPrefix('!');
$routeProvider.
when('/projects', {
template: '<project-list></project-list>'
}).
when('/projects/:id', {
template: '<project-detail></project-detail>'
}).
when('/projects/edit/:id', {
template: '<edit-project></edit-project>'
}).
when('/projects/new', {
template: '<edit-project></edit-project>'
}).
otherwise('/projects');
}
]);

1. Quick fix
Simple solution would be using a conditional checking if the $routeParams.id param is defined, if so, then it need a request to feed the project informations, otherwise not.
if($routeParams.id){
$http.get('rest/projects/' + $routeParams.id ).then(function(response) {
console.log(JSON.stringify(response.data));
self.project = response.data;
});
}
2. Component Router
Even though the previous solution looks simple and functional, it might not be the best. A propper solution is to use a separate component for each route, but you can reuse the form part of it. Also, assuming that you are building a completelly component-based app you should use ngComponentRoute instead of ngRoute.
.component('app', {
template: '<ng-outlet></ng-outlet>',
$routeConfig: [
{path: '/projects', name: 'Projects', component: 'projectList', useAsDefault: true},
{path: '/projects/:id', name: 'Project Detail', component: 'projectDetail' },
{path: '/projects/edit/:id', name: 'Edit Project', component: 'editProject' },
{path: '/projects/new', name: 'New Project', component: 'newProject' }
]
});
Then you can create a project editor component and reuse it on both edit and new project page by just adding <project-editor-form project="{}" on-save="$ctrl.mySave()"></project-editor-form>. For example:
.component('projectEditorForm', {
template:
'Project Name: <input type="text" ng-model="$ctrl.project.name">' +
'<button ng-click="$ctrl.onSaveProject($ctrl.project)">Save</button>',
bindings: {
project: '<',
onSave: '&'
},
controller: function () {
var $ctrl = this;
$ctrl.onSaveProject = function (project) {
// general save logic goes here
// if you want to reuse this too
// then emit the on save for onSave binding
$ctrl.onSave($ctrl.project);
}
},
})
.component('editProject', {
template:
'<project-editor-form project="$ctrl.project" on-save="$ctrl.mySave()">' +
'</project-editor-form>',
bindings: {
project: '<',
onSave: '&'
},
controller: function ($http) {
var $ctrl = this;
// consider using a Service to do such task
// instead of request directly from the controller
$http.get('rest/projects/' + $routeParams.id).then(function(response) {
$ctrl.project = response.data;
});
$ctrl.mySave = function (project) {
// save logic here
}
},
})
3. ngRoute with resolve
Another approach that doesn't deppend on ngComponentRoute, is to use the route resolve property, it's very usefull when using component as route template. You add a resolve property to your route, that would be the project, and bind to your form component.
$routeProvider
.when('/projects/edit/:id', {
template: '<project-editor-form project="$resolve.project"></project-editor-form>',
resolve: {
project: function ($route, $q) {
var id = $route.current.params.id;
// again, consider using a service instead
return $http.get('rest/projects/' + id).then(function (response) {
return response.data;
});
}
}
})
.when('/projects/new', {
template: '<project-editor-form project="$resolve.project"></project-editor-form>',
resolve: {
project: {
id: 0,
name: ''
}
}
})

Related

UI-Router: how does the params map to the url

I am new to Angular and UI-Router and I have to work with an Angular codebase. I am having difficulty understanding how the params gets mapped to the url. Specifically how topics get injected as topic in /test?topic&manualMode&advancedMode I cannot seem to find the place where I can log out the intermediate value to see how the url gets constructed.
function StateConfig(
$stateProvider: angular.ui.IStateProvider,
breadcrumbsService: BreadcrumbsService
) {
$stateProvider.state("testHub", {
params: {
topics: null
},
url: "/test?topic&manualMode&advancedMode",
views: {
main: {
template:
'<div class="hubContent"><iot-mqtt-client-config></iot-mqtt-client-config></div>'
},
sidebar: {
controller: HelpControllerTutorial,
controllerAs: "vm",
templateUrl: "pages/mqtt/mqtt-client-help.html"
}
}
});
breadcrumbsService.addConfig("testHub", {
name: (i18n: I18nService) => i18n("text.test"),
parentState: null,
reconstructBreadcrumbPath: true
});
}
This should work for you in app.js:
.state('testHub', {
url: "/test/:topic/:manualMode/:advancedMode", // added for dynamic ID params
})
Then your ui-sref in substances.html will append the ( topic or manualMode or advancedMode ) to the url, and you will be able to access that in your controller in $stateParams

Angular 1.x - Pass component state resolve data to other component

I have a dummy component based application, which has the following components:
- App
-- Page
---- Breadcrumb
What i want is to pass the Page resolve data to the Breadcrumb.
This is how the Page config looks like:
$stateProvider.state('page', {
url: '/page',
component: 'page',
resolve: {
routes: ($stateParams) => {
"ngInject";
return [
{url: "/", name: "Home"},
{url: "/page", name: 'Page'}
];
}
}
})
The Page controller:
class PageController {
constructor($stateParams) {
"ngInject";
this.themeColor = "#ff8fd1";
// When i use static data it's works fine..
//this.routes = [
// {url: "/", name: "Main page"},
// {url: "/page", name: 'Sub page'},
//];
this.routes = $stateParams.routes;
}
}
And this is my Breadcrumb component:
let breadcrumbComponent = {
restrict: 'E',
bindings: {
themeColor: "<",
routes: "<"
},
template,
controller
};
The DOM of the Breadcrumb component:
<breadcrumb theme-color="$ctrl.themeColor" routes="$ctrl.routes"></breadcrumb>
Everytime the routes are undefined when i want to define it from the Page controller resolve method.
How do i wait the data, or bind the Breadcrumb again when the data arrives?
One simple way is to use ngIf directive to render breadcrumb component only when routes are resolved:
<breadcrumb
ng-if="$ctrl.routes"
theme-color="$ctrl.themeColor"
routes="$ctrl.routes"></breadcrumb>
I figured out.
If you're want to pass resolve data to your component, or an other you have to set the resolve data as binding in the parent component as well.
// page.component.js
let pageComponent = {
restrict: 'E',
bindings: {
routes: "<"
},
template,
controller
};

Inject a library in angular ui-router on state resolve

I'm wondering if there is a way to resolve a library dependency on the resolve function of UI-router.
Something like this:
$stateProvider.state('app.cart', {
title : "CART.TITLE",
url : '/cart',
views : {
'content#app': {
templateUrl: 'app/main/cart/cart.html',
controller : 'CartController as vm'
},
},
bodyClass: 'cart',
resolve: {
//fetch a library, ex. moment js
var moment = $http.get('moment.js');
body.append(moment);
}
});
Not sure why you would do that but you can easily fetch a JS file within resolve. Demo
$stateProvider
.state('state1', {
url: 'state1',
templateUrl: 'state1.tpl.html'
})
.state('state2', {
url: 'state2',
templateUrl: 'state2.tpl.html',
resolve: {
external: ['$http', function($http) {
var someJs = 'https://gist.githubusercontent.com/jonaszuberbuehler/952c2f9a0c9e28557ed89c1e4d6fe8b7/raw/e6664811e1b34d9761500f4c46bfd2e1e2439d23/gistfile1.txt';
return $http.get(someJs).then(function(result) {
alert(result.data);
});
}]
}
});
Adding it to the DOM is another question.

AngularJS Provider wont inject into angular.config

I'm trying to set up some constants for an Angular config through a provider, but for some reason I can't see, I keep getting the error:
Unknown provider: myprovider
I have plenty of dependency injection throughout my project, but I can't figure out why this one will not work.
The order of the code below is the same order as in my config.js.
Provider
var trybConfig = angular.module('trybConfig', []);
trybConfig.provider('myprovider', function() {
this.Routes = {
EventList: {
Location: "/Event",
Template: "views/eventView.html",
Controller: "eventController"
}
}
this.$get = function () {
return this.Routes;
}
});
Config
trybConfig.config(function($routeProvider, myprovider) {
$routeProvider
.when('/Event', {
templateUrl: 'views/eventView.html',
controller: 'eventController'
})
.otherwise({
redirectTo: '/Event'
});
});
Don't add "provider" to the name of your service prodiver, just do:
trybConfig.provider('my', function() {
And inject it:
trybConfig.config(function($routeProvider, myProvider) {
FYI - In your current state you need to inject:
trybConfig.config(function($routeProvider, myproviderProvider) {

AngularJS ui-router: how to resolve typical data globally for all routes?

I have an AngularJS service which communicates with the server and returns
translations of different sections of the application:
angular
.module('utils')
.service('Translations', ['$q','$http',function($q, $http) {
translationsService = {
get: function(section) {
if (!promise) {
var q = $q.defer();
promise = $http
.get(
'/api/translations',
{
section: section
})
.success(function(data,status,headers,config) {
q.resolve(result.data);
})
.error(function(data,status,headers,config){
q.reject(status);
});
return q.promise;
}
}
};
return translationsService;
}]);
The name of the section is passed as the section parameter of the get function.
I'm using AngularJS ui-router module and following design pattern described here
So I have the following states config:
angular.module('app')
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('users', {
url: '/users',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('users');
}
]
},
templateUrl: '/app/users/list.html',
controller: 'usersController',
controllerAs: 'vm'
})
.state('shifts', {
url: '/shifts',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('shifts');
}
]
},
templateUrl: '/app/shifts/list.html',
controller: 'shiftsController',
controllerAs: 'vm'
})
This works fine but as you may notice I have to explicitly specify translations in the resolve parameter. I think that's not good enough as this duplicates the logic.
Is there any way to resolve translations globally and avoid the code duplicates. I mean some kind of middleware.
I was thinking about listening for the $stateChangeStart, then get translations specific to the new state and bind them to controllers, but I have not found the way to do it.
Any advice will be appreciated greatly.
Important note:
In my case the resolved translations object must contain the translations data, not service/factory/whatever.
Kind regards.
Let me show you my approach. There is a working plunker
Let's have a translation.json like this:
{
"home" : "trans for home",
"parent" : "trans for parent",
"parent.child" : "trans for child"
}
Now, let's introduce the super parent state root
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view=""></div>',
resolve: ['Translations'
, function(Translations){return Translations.loadAll();}]
});
This super root state is not having any url (not effecting any child url). Now, we will silently inject that into every state:
$stateProvider
.state('home', {
parent: 'root',
url: "/home",
templateUrl: 'tpl.html',
})
.state('parent', {
parent: 'root',
url: "/parent",
templateUrl: 'tpl.html',
})
As we can see, we use setting parent - and do not effect/extend the original state name.
The root state is loading the translations at one shot via new method loadAll():
.service('Translations', ['$http'
,function($http) {
translationsService = {
data : {},
loadAll : function(){
return $http
.get("translations.json")
.then(function(response){
this.data = response.data;
return this.data;
})
},
get: function(section) {
return data[section];
}
};
return translationsService;
}])
We do not need $q at all. Our super root state just resolves that once... via $http and loadAll() method. All these are now loaded, and we can even place that service into $rootScope:
.run(['$rootScope', '$state', '$stateParams', 'Translations',
function ($rootScope, $state, $stateParams, Translations) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.Translations = Translations;
}])
And we can access it anyhwere like this:
<h5>Translation</h5>
<pre>{{Translations.get($state.current.name) | json}}</pre>
Wow... that is solution profiting almost from each feature coming with UI-Router... I'd say. All loaded once. All inherited because of $rootScope and view inheritance... all available in any child state...
Check that all here.
Though this is a very old question, I'd like to post solution which I'm using now. Hope it will help somebody in the future.
After using some different approaches I came up with a beautiful angularjs pattern by John Papa
He suggest using a special service routerHelperProvider and configure states as a regular JS object. I'm not going to copy-paste the entire provider here. See the link above for details. But I'm going to show how I solved my problem by the means of that service.
Here is the part of code of that provider which takes the JS object and transforms it to the states configuration:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
$stateProvider.state(state.state, state.config);
});
I transformed it as follows:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
var resolveAlways = {
translations: ['Translations', function(Translations) {
if (state.translationCategory) {
return Translations.get(state.translationCategory);
} else {
return {};
}
}],
};
state.config.resolve =
angular.extend(state.config.resolve || {}, resolveAlways || {});
$stateProvider.state(state.state, state.config);
});
});
And my route configuration object now looks as follows:
{
state: ‘users’,
translationsCategory: ‘users’,
config: {
controller: ‘usersController’
controllerAs: ‘vm’,
url: ‘/users’.
templateUrl: ‘users.html'
}
So what I did:
I implemented the resolveAlways object which takes the custom translationsCategory property, injects the Translations service and resolves the necessary data. Now no need to do it everytime.

Categories